From ff7e4f7986356fccfd33111ead48a82101b47454 Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Tue, 16 May 2023 11:52:10 +0200 Subject: [PATCH] [Gradle] Implement SourceSetTreeClassifier API - This new API will be configurable for 'External Kotlin Targets' - This change also moves targetHierarchy.android {} into the androidTarget to avoid potential confusion around the KGP managed AndroidTarget vs the Google implementation. KT-58710 --- .../api/kotlin-gradle-plugin-api.api | 13 +- .../gradle/dsl/KotlinTargetHierarchyDsl.kt | 68 ---------- .../api/kotlin-gradle-plugin.api | 44 +++++++ .../dsl/KotlinMultiplatformExtension.kt | 22 ++-- .../plugin/mpp/KotlinMultiplatformPlugin.kt | 23 ++-- .../ExternalKotlinCompilationDescriptor.kt | 6 + .../createExternalKotlinCompilation.kt | 11 +- ...linTargetHierarchy.SourceSetTree+orNull.kt | 43 ------- .../KotlinTargetHierarchyBuilderImpl.kt | 14 +- .../KotlinTargetHierarchyDslImpl.kt | 26 +--- .../SourceSetTreeClassifier.kt | 120 +++++++++++++++++ ...tiplatformLayoutV2DependsOnConfigurator.kt | 11 +- .../targets/android/KotlinAndroidTarget.kt | 121 ++++++++++++++++-- .../KotlinAndroidTargetHierarchyDsl.kt | 55 ++++++++ .../jvm/KotlinJvmAndroidCompilationFactory.kt | 20 ++- .../unitTests/ExternalKotlinTargetApiTests.kt | 92 +++++++++++++ .../KotlinAndroidTargetHierarchyDsl.kt | 12 +- 17 files changed, 501 insertions(+), 200 deletions(-) delete mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/KotlinTargetHierarchy.SourceSetTree+orNull.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTargetHierarchyDsl.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/ExternalKotlinTargetApiTests.kt diff --git a/libraries/tools/kotlin-gradle-plugin-api/api/kotlin-gradle-plugin-api.api b/libraries/tools/kotlin-gradle-plugin-api/api/kotlin-gradle-plugin-api.api index 28e19b109ab..399786878a9 100644 --- a/libraries/tools/kotlin-gradle-plugin-api/api/kotlin-gradle-plugin-api.api +++ b/libraries/tools/kotlin-gradle-plugin-api/api/kotlin-gradle-plugin-api.api @@ -142,16 +142,6 @@ public abstract interface class org/jetbrains/kotlin/gradle/dsl/KaptJavacOption public abstract fun option (Ljava/lang/Object;Ljava/lang/Object;)V } -public abstract interface class org/jetbrains/kotlin/gradle/dsl/KotlinAndroidTargetHierarchyDsl { - public abstract fun getInstrumentedTest ()Lorg/jetbrains/kotlin/gradle/dsl/KotlinAndroidVariantHierarchyDsl; - public abstract fun getMain ()Lorg/jetbrains/kotlin/gradle/dsl/KotlinAndroidVariantHierarchyDsl; - public abstract fun getUnitTest ()Lorg/jetbrains/kotlin/gradle/dsl/KotlinAndroidVariantHierarchyDsl; -} - -public abstract interface class org/jetbrains/kotlin/gradle/dsl/KotlinAndroidVariantHierarchyDsl { - public abstract fun getSourceSetTree ()Lorg/gradle/api/provider/Property; -} - public abstract interface class org/jetbrains/kotlin/gradle/dsl/KotlinArtifact : org/gradle/api/Named, org/gradle/api/plugins/ExtensionAware { public abstract fun getArtifactName ()Ljava/lang/String; public abstract fun getModules ()Ljava/util/Set; @@ -530,11 +520,9 @@ public abstract interface class org/jetbrains/kotlin/gradle/dsl/KotlinNativeXCFr } public abstract interface class org/jetbrains/kotlin/gradle/dsl/KotlinTargetHierarchyDsl { - public abstract fun android (Lkotlin/jvm/functions/Function1;)V public abstract fun apply (Lorg/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchyDescriptor;Lkotlin/jvm/functions/Function1;)V public abstract fun custom (Lkotlin/jvm/functions/Function1;)V public abstract fun default (Lkotlin/jvm/functions/Function1;)V - public abstract fun getAndroid ()Lorg/jetbrains/kotlin/gradle/dsl/KotlinAndroidTargetHierarchyDsl; } public final class org/jetbrains/kotlin/gradle/dsl/KotlinTargetHierarchyDsl$DefaultImpls { @@ -1105,6 +1093,7 @@ public abstract interface class org/jetbrains/kotlin/gradle/plugin/KotlinTargetH public abstract fun withAndroidNativeArm64 ()V public abstract fun withAndroidNativeX64 ()V public abstract fun withAndroidNativeX86 ()V + public abstract fun withAndroidTarget ()V public abstract fun withApple ()V public abstract fun withCompilations (Lkotlin/jvm/functions/Function1;)V public abstract fun withIos ()V diff --git a/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinTargetHierarchyDsl.kt b/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinTargetHierarchyDsl.kt index 8d8f359c671..48564b19d19 100644 --- a/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinTargetHierarchyDsl.kt +++ b/libraries/tools/kotlin-gradle-plugin-api/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinTargetHierarchyDsl.kt @@ -170,72 +170,4 @@ interface KotlinTargetHierarchyDsl { * ``` */ fun custom(describe: KotlinTargetHierarchyBuilder.Root.() -> Unit) - - /** - * Configure Android specific settings within the context of [KotlinTargetHierarchy]. - * The difference between Android and other targets is that the build author is free to choose - * the names of compilations, whereas Android is using predefined SourceSet names. - * - * ### Default dependsOn edges - * By default, Kotlin Multiplatform will set the following default dependsOn edges: - * - `androidMain` -> `commonMain` - * - `androidUnitTest` -> `commonTest` - * - * In this default setup, SourceSets like `androidInstrumentedTest` will *not* dependOn `commonTest`. - * This API can be used to change the default behavior - * - * #### Example 1: Setting up androidInstrumentedTest -> commonTest - * This can be done by putting the 'androidInstrumentedTest' variants into the 'test' [SourceSetTree]: - * ```kotlin - * targetHierarchy.android { - * instrumentedTest.sourceSetTree.set(SourceSetTree.test) - * } - * ``` - * - * #### Example 2: Setting up androidInstrumentedTest -> commonTest and removing 'unitTests' from the 'test' [SourceSetTree] - * ```kotlin - * targetHierarchy.android { - * instrumentedTest.sourceSetTree.set(SourceSetTree.test) - * unitTest.sourceSetTree.set(SourceSetTree.unitTest) // ! <- Anything *other* than 'test' - * } - * ``` - */ - @ExperimentalKotlinGradlePluginApi - fun android(configure: KotlinAndroidTargetHierarchyDsl.() -> Unit) - - /** - * See [android] - */ - @ExperimentalKotlinGradlePluginApi - val android: KotlinAndroidTargetHierarchyDsl } - - -@ExperimentalKotlinGradlePluginApi -interface KotlinAndroidTargetHierarchyDsl { - val main: KotlinAndroidVariantHierarchyDsl - val unitTest: KotlinAndroidVariantHierarchyDsl - val instrumentedTest: KotlinAndroidVariantHierarchyDsl -} - -@ExperimentalKotlinGradlePluginApi -interface KotlinAndroidVariantHierarchyDsl { - /** - * Configures under which [SourceSetTree] the currently configured Android Variant shall be placed. - * e.g. - * - * ```kotlin - * kotlin { - * targetHierarchy.android { - * instrumentedTest.sourceSetTree.set(SourceSetTree.test) - * } - * } - * ``` - * - * Will ensure that all android instrumented tests (androidInstrumentedTest, androidInstrumentedTestDebug, ...) - * will be placed into the 'test' SourceSet tree (with 'commonTest' as root) - * - * See [KotlinTargetHierarchyDsl.android] - */ - val sourceSetTree: Property -} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/api/kotlin-gradle-plugin.api b/libraries/tools/kotlin-gradle-plugin/api/kotlin-gradle-plugin.api index 75f8d9eeb75..19c00aa3550 100644 --- a/libraries/tools/kotlin-gradle-plugin/api/kotlin-gradle-plugin.api +++ b/libraries/tools/kotlin-gradle-plugin/api/kotlin-gradle-plugin.api @@ -231,6 +231,7 @@ public abstract interface class org/jetbrains/kotlin/gradle/plugin/mpp/external/ public abstract fun getConfigure ()Lkotlin/jvm/functions/Function1; public abstract fun getDefaultSourceSet ()Lorg/jetbrains/kotlin/gradle/plugin/KotlinSourceSet; public abstract fun getFriendArtifactResolver ()Lorg/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor$FriendArtifactResolver; + public abstract fun getSourceSetTreeClassifier ()Lorg/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier; } public abstract interface class org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor$CompilationAssociator { @@ -254,6 +255,7 @@ public final class org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotli public final fun getCompileTaskName ()Ljava/lang/String; public final fun getDefaultSourceSet ()Lorg/jetbrains/kotlin/gradle/plugin/KotlinSourceSet; public final fun getFriendArtifactResolver ()Lorg/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor$FriendArtifactResolver; + public final fun getSourceSetTreeClassifier ()Lorg/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier; public final fun setCompilationAssociator (Lorg/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor$CompilationAssociator;)V public final fun setCompilationFactory (Lorg/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor$CompilationFactory;)V public final fun setCompilationName (Ljava/lang/String;)V @@ -261,6 +263,7 @@ public final class org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotli public final fun setCompileTaskName (Ljava/lang/String;)V public final fun setDefaultSourceSet (Lorg/jetbrains/kotlin/gradle/plugin/KotlinSourceSet;)V public final fun setFriendArtifactResolver (Lorg/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor$FriendArtifactResolver;)V + public final fun setSourceSetTreeClassifier (Lorg/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier;)V } public final class org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptorKt { @@ -312,3 +315,44 @@ public final class org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotli public static final fun ExternalKotlinTargetDescriptor (Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinTargetDescriptor; } +public abstract class org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier { +} + +public final class org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier$Default : org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier { + public static final field INSTANCE Lorg/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier$Default; + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier$Name : org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier { + public fun (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lorg/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier$Name; + public static synthetic fun copy$default (Lorg/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier$Name;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier$Name; + public fun equals (Ljava/lang/Object;)Z + public final fun getName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier$None : org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier { + public static final field INSTANCE Lorg/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier$None; + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier$Property : org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier { + public fun (Lorg/gradle/api/provider/Property;)V + public final fun getProperty ()Lorg/gradle/api/provider/Property; + public fun toString ()Ljava/lang/String; +} + +public final class org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier$Value : org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier { + public fun (Lorg/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchy$SourceSetTree;)V + public final fun component1 ()Lorg/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchy$SourceSetTree; + public final fun copy (Lorg/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchy$SourceSetTree;)Lorg/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier$Value; + public static synthetic fun copy$default (Lorg/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier$Value;Lorg/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchy$SourceSetTree;ILjava/lang/Object;)Lorg/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier$Value; + public fun equals (Ljava/lang/Object;)Z + public final fun getTree ()Lorg/jetbrains/kotlin/gradle/plugin/KotlinTargetHierarchy$SourceSetTree; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt index b189caff93f..e054662e28a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinMultiplatformExtension.kt @@ -53,7 +53,7 @@ abstract class KotlinMultiplatformExtension(project: Project) : } internal val internalKotlinTargetHierarchy by lazy { - KotlinTargetHierarchyDslImpl(project.objects, targets, sourceSets) + KotlinTargetHierarchyDslImpl(targets, sourceSets) } @ExperimentalKotlinGradlePluginApi @@ -76,13 +76,13 @@ abstract class KotlinMultiplatformExtension(project: Project) : fun targetFromPreset( preset: KotlinTargetPreset, name: String = preset.name, - configure: T.() -> Unit = { } + configure: T.() -> Unit = { }, ): T = configureOrCreate(name, preset, configure) fun targetFromPreset( preset: KotlinTargetPreset, name: String, - configure: Action + configure: Action, ) = targetFromPreset(preset, name) { configure.execute(this) } fun targetFromPreset(preset: KotlinTargetPreset) = targetFromPreset(preset, preset.name) { } @@ -100,37 +100,37 @@ interface TargetsFromPresetExtension : NamedDomainObjectCollection fun fromPreset( preset: KotlinTargetPreset, name: String, - configureAction: T.() -> Unit = {} + configureAction: T.() -> Unit = {}, ): T fun fromPreset( preset: KotlinTargetPreset, - name: String + name: String, ): T = fromPreset(preset, name, {}) fun fromPreset( preset: KotlinTargetPreset, name: String, - configureAction: Action + configureAction: Action, ): T } internal abstract class DefaultTargetsFromPresetExtension @Inject constructor( private val targetsContainer: () -> KotlinTargetsContainerWithPresets, - val targets: NamedDomainObjectCollection + val targets: NamedDomainObjectCollection, ) : TargetsFromPresetExtension, NamedDomainObjectCollection by targets { override fun fromPreset( preset: KotlinTargetPreset, name: String, - configureAction: T.() -> Unit + configureAction: T.() -> Unit, ): T = targetsContainer().configureOrCreate(name, preset, configureAction) override fun fromPreset( preset: KotlinTargetPreset, name: String, - configureAction: Action + configureAction: Action, ) = fromPreset(preset, name) { configureAction.execute(this) } @@ -142,7 +142,7 @@ internal fun KotlinTarget.isProducedFromPreset(kotlinTargetPreset: KotlinTargetP internal fun KotlinTargetsContainerWithPresets.configureOrCreate( targetName: String, targetPreset: KotlinTargetPreset, - configure: T.() -> Unit + configure: T.() -> Unit, ): T { val existingTarget = targets.findByName(targetName) when { @@ -172,7 +172,7 @@ internal fun KotlinTargetsContainerWithPresets.configureOrCre internal fun KotlinTargetsContainerWithPresets.configureOrCreateAndroidTargetAndReportDeprecation( targetName: String, - configure: KotlinAndroidTarget.() -> Unit + configure: KotlinAndroidTarget.() -> Unit, ): KotlinAndroidTarget { val targetPreset = presets.getByName("android") as KotlinAndroidTargetPreset val result = configureOrCreate(targetName, targetPreset, configure) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt index 765ed747861..79581b100d2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/KotlinMultiplatformPlugin.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformPlugin.Companio import org.jetbrains.kotlin.gradle.plugin.mpp.apple.addBuildListenerForXcode import org.jetbrains.kotlin.gradle.plugin.mpp.internal.runDeprecationDiagnostics import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.copyAttributes +import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.orNull import org.jetbrains.kotlin.gradle.plugin.sources.DefaultLanguageSettingsBuilder import org.jetbrains.kotlin.gradle.plugin.sources.checkSourceSetVisibilityRequirements import org.jetbrains.kotlin.gradle.plugin.sources.internal @@ -85,7 +86,7 @@ class KotlinMultiplatformPlugin : Plugin { } private fun exportProjectStructureMetadataForOtherBuilds( - extension: KotlinMultiplatformExtension + extension: KotlinMultiplatformExtension, ) { GlobalProjectStructureMetadataStorage.registerProjectStructureMetadata(extension.project) { extension.kotlinProjectStructureMetadata @@ -168,22 +169,24 @@ class KotlinMultiplatformPlugin : Plugin { private fun configureSourceSets(project: Project) = with(project.multiplatformExtension) { - val production = sourceSets.create(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) - val test = sourceSets.create(KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME) + /* Create 'commonMain' and 'commonTest' SourceSets */ + sourceSets.create(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME) + sourceSets.create(KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME) + + /* Create default 'dependsOn' to commonMain/commonTest (or even common{SourceSetTree}) */ targets.all { target -> project.launchInStage(KotlinPluginLifecycle.Stage.FinaliseRefinesEdges) { /* Only setup default refines edges when no KotlinTargetHierarchy was applied */ if (project.multiplatformExtension.internalKotlinTargetHierarchy.appliedDescriptors.isNotEmpty()) return@launchInStage - target.compilations.findByName(KotlinCompilation.MAIN_COMPILATION_NAME)?.let { mainCompilation -> - mainCompilation.defaultSourceSet.takeIf { it != production }?.dependsOn(production) - } - - target.compilations.findByName(KotlinCompilation.TEST_COMPILATION_NAME)?.let { testCompilation -> - testCompilation.defaultSourceSet.takeIf { it != test }?.dependsOn(test) + target.compilations.forEach { compilation -> + val sourceSetTree = KotlinTargetHierarchy.SourceSetTree.orNull(compilation) ?: return@forEach + val commonSourceSet = sourceSets.findByName(lowerCamelCaseName("common", sourceSetTree.name)) ?: return@forEach + compilation.defaultSourceSet.dependsOn(commonSourceSet) } } + /* Report the platform to tbe build stats service */ val targetName = if (target is KotlinNativeTarget) target.konanTarget.name else @@ -277,7 +280,7 @@ private fun sourcesJarTask( project: Project, sourceSets: Future>>, taskNamePrefix: String, - artifactNameAppendix: String + artifactNameAppendix: String, ): TaskProvider = sourcesJarTaskNamed(lowerCamelCaseName(taskNamePrefix, "sourcesJar"), taskNamePrefix, project, sourceSets, artifactNameAppendix) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor.kt index b379959b9e7..23e33cfbb34 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/ExternalKotlinCompilationDescriptor.kt @@ -12,6 +12,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import org.jetbrains.kotlin.gradle.plugin.mpp.DecoratedKotlinCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilationDescriptor.* +import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.SourceSetTreeClassifier import kotlin.properties.Delegates /** @@ -54,6 +55,7 @@ interface ExternalKotlinCompilationDescriptor val friendArtifactResolver: FriendArtifactResolver? val compilationAssociator: CompilationAssociator? + val sourceSetTreeClassifier: SourceSetTreeClassifier val configure: ((T) -> Unit)? } @@ -70,6 +72,7 @@ fun ExternalKotlinCompilationDescriptor compilationFactory = compilationFactory, friendArtifactResolver = friendArtifactResolver, compilationAssociator = compilationAssociator, + sourceSetTreeClassifier = sourceSetTreeClassifier, configure = this.configure ) } @@ -84,6 +87,8 @@ class ExternalKotlinCompilationDescriptorBuilder by Delegates.notNull() var friendArtifactResolver: FriendArtifactResolver? = null var compilationAssociator: CompilationAssociator? = null + var sourceSetTreeClassifier: SourceSetTreeClassifier = SourceSetTreeClassifier.Default + internal var configure: ((T) -> Unit)? = null fun configure(action: (T) -> Unit) = apply { @@ -101,5 +106,6 @@ private data class ExternalKotlinCompilationDescriptorImpl, override val friendArtifactResolver: FriendArtifactResolver?, override val compilationAssociator: CompilationAssociator?, + override val sourceSetTreeClassifier: SourceSetTreeClassifier, override val configure: ((T) -> Unit)?, ) : ExternalKotlinCompilationDescriptor \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinCompilation.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinCompilation.kt index 9dad556cb95..58f7894c68f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinCompilation.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/external/createExternalKotlinCompilation.kt @@ -12,9 +12,9 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.DefaultKotlinCompi import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCompilationAssociator import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCompilationSourceSetsContainer import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.factory.* -import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.factory.KotlinCompilationImplFactory.KotlinCompilationTaskNamesContainerFactory import org.jetbrains.kotlin.gradle.plugin.mpp.decoratedInstance import org.jetbrains.kotlin.gradle.plugin.mpp.external.DecoratedExternalKotlinCompilation.Delegate +import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.sourceSetTreeClassifier import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider /** @@ -30,7 +30,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider */ @ExternalKotlinTargetApi fun DecoratedExternalKotlinTarget.createCompilation( - descriptor: ExternalKotlinCompilationDescriptor + descriptor: ExternalKotlinCompilationDescriptor, ): T { val compilationImplFactory = KotlinCompilationImplFactory( compilerOptionsFactory = when (platformType) { @@ -42,7 +42,7 @@ fun DecoratedExternalKotlinTarget.creat KotlinPlatformType.wasm -> KotlinMultiplatformCommonCompilerOptionsFactory }, compilationSourceSetsContainerFactory = { _, _ -> KotlinCompilationSourceSetsContainer(descriptor.defaultSourceSet) }, - compilationTaskNamesContainerFactory = KotlinCompilationTaskNamesContainerFactory { target, compilationName -> + compilationTaskNamesContainerFactory = { target, compilationName -> val default = DefaultKotlinCompilationTaskNamesContainerFactory.create(target, compilationName) default.copy( compileTaskName = descriptor.compileTaskName ?: default.compileTaskName, @@ -70,6 +70,7 @@ fun DecoratedExternalKotlinTarget.creat val compilationImpl = compilationImplFactory.create(this, descriptor.compilationName) val decoratedCompilation = descriptor.compilationFactory.create(Delegate(compilationImpl)) + decoratedCompilation.sourceSetTreeClassifier = descriptor.sourceSetTreeClassifier descriptor.configure?.invoke(decoratedCompilation) this.delegate.compilations.add(decoratedCompilation) @@ -83,13 +84,13 @@ fun DecoratedExternalKotlinTarget.creat */ @ExternalKotlinTargetApi fun DecoratedExternalKotlinTarget.createCompilation( - descriptor: ExternalKotlinCompilationDescriptorBuilder.() -> Unit + descriptor: ExternalKotlinCompilationDescriptorBuilder.() -> Unit, ): T { return createCompilation(ExternalKotlinCompilationDescriptor(descriptor)) } private fun DecoratedExternalKotlinTarget.setupCompileTask( - compilation: DecoratedExternalKotlinCompilation + compilation: DecoratedExternalKotlinCompilation, ) { val tasksProvider = KotlinTasksProvider() val compilationInfo = KotlinCompilationInfo(compilation) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/KotlinTargetHierarchy.SourceSetTree+orNull.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/KotlinTargetHierarchy.SourceSetTree+orNull.kt deleted file mode 100644 index 69ad1971439..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/KotlinTargetHierarchy.SourceSetTree+orNull.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2010-2023 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.targetHierarchy - -import org.jetbrains.kotlin.gradle.dsl.kotlinExtension -import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension -import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull -import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation -import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchy.SourceSetTree -import org.jetbrains.kotlin.gradle.plugin.awaitFinalValue -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation -import org.jetbrains.kotlin.gradle.plugin.sources.android.AndroidVariantType -import org.jetbrains.kotlin.gradle.plugin.sources.android.type - -internal suspend fun SourceSetTree.Companion.orNull(compilation: KotlinCompilation<*>): SourceSetTree? = - when (compilation) { - is KotlinJvmAndroidCompilation -> orNull(compilation.target, compilation.androidVariant.type) - else -> when (compilation.name) { - "main" -> main - "test" -> test - else -> SourceSetTree(compilation.name) - } - } - -internal suspend fun SourceSetTree.Companion.orNull( - target: KotlinAndroidTarget, - variantType: AndroidVariantType -): SourceSetTree? { - val multiplatform = target.project.multiplatformExtensionOrNull ?: return null - return when (variantType) { - AndroidVariantType.Main -> - multiplatform.targetHierarchy.android.main.sourceSetTree.awaitFinalValue() ?: main - AndroidVariantType.UnitTest -> - multiplatform.targetHierarchy.android.unitTest.sourceSetTree.awaitFinalValue() ?: test - AndroidVariantType.InstrumentedTest -> - multiplatform.targetHierarchy.android.instrumentedTest.sourceSetTree.awaitFinalValue() ?: instrumentedTest - AndroidVariantType.Unknown -> null - } -} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/KotlinTargetHierarchyBuilderImpl.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/KotlinTargetHierarchyBuilderImpl.kt index 939d2a41118..e787f85cc23 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/KotlinTargetHierarchyBuilderImpl.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/KotlinTargetHierarchyBuilderImpl.kt @@ -61,17 +61,17 @@ private class KotlinTargetHierarchyBuilderRootImpl( override fun sourceSetTrees(vararg tree: KotlinTargetHierarchy.SourceSetTree) { - builder.modules = tree.toHashSet() + builder.sourceSetTrees = tree.toHashSet() } override fun withSourceSetTree(vararg tree: KotlinTargetHierarchy.SourceSetTree) { - builder.modules = builder.modules.orEmpty().plus(tree) + builder.sourceSetTrees = builder.sourceSetTrees.orEmpty().plus(tree) } override fun excludeSourceSetTree(vararg tree: KotlinTargetHierarchy.SourceSetTree) { val modules = tree.toHashSet() if (modules.isEmpty()) return - builder.modules = builder.modules.orEmpty() - modules + builder.sourceSetTrees = builder.sourceSetTrees.orEmpty() - modules } } @@ -84,7 +84,7 @@ private class KotlinTargetHierarchyBuilderImpl( val children = mutableSetOf() val childrenClosure get() = closure { it.children } - var modules: Set? = null + var sourceSetTrees: Set? = null private var includePredicate: ((KotlinCompilation<*>) -> Boolean) = { false } private var excludePredicate: ((KotlinCompilation<*>) -> Boolean) = { false } @@ -104,9 +104,9 @@ private class KotlinTargetHierarchyBuilderImpl( } suspend fun contains(compilation: KotlinCompilation<*>): Boolean { - modules?.let { modules -> - val module = KotlinTargetHierarchy.SourceSetTree.orNull(compilation) ?: return false - if (module !in modules) return false + sourceSetTrees?.let { sourceSetTrees -> + val sourceSetTree = KotlinTargetHierarchy.SourceSetTree.orNull(compilation) ?: return false + if (sourceSetTree !in sourceSetTrees) return false } /* Return eagerly, when compilation is explicitly excluded */ diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/KotlinTargetHierarchyDslImpl.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/KotlinTargetHierarchyDslImpl.kt index e48c8ced4f4..f169c4875ce 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/KotlinTargetHierarchyDslImpl.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/KotlinTargetHierarchyDslImpl.kt @@ -7,16 +7,13 @@ package org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy import org.gradle.api.DomainObjectCollection import org.gradle.api.NamedDomainObjectContainer -import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.Property -import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidTargetHierarchyDsl -import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidVariantHierarchyDsl import org.jetbrains.kotlin.gradle.dsl.KotlinTargetHierarchyDsl -import org.jetbrains.kotlin.gradle.plugin.* -import org.jetbrains.kotlin.gradle.utils.property +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet +import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchyBuilder +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchyDescriptor internal class KotlinTargetHierarchyDslImpl( - objects: ObjectFactory, private val targets: DomainObjectCollection, private val sourceSets: NamedDomainObjectContainer, ) : KotlinTargetHierarchyDsl { @@ -24,8 +21,6 @@ internal class KotlinTargetHierarchyDslImpl( private val _appliedDescriptors = mutableListOf() val appliedDescriptors: List get() = _appliedDescriptors - override val android: KotlinAndroidTargetHierarchyDsl = KotlinAndroidTargetHierarchyDslImpl(objects) - override fun apply( hierarchyDescriptor: KotlinTargetHierarchyDescriptor, describeExtension: (KotlinTargetHierarchyBuilder.Root.() -> Unit)?, @@ -42,21 +37,8 @@ internal class KotlinTargetHierarchyDslImpl( override fun custom(describe: KotlinTargetHierarchyBuilder.Root.() -> Unit) { apply(KotlinTargetHierarchyDescriptor(describe)) } - - override fun android(configure: KotlinAndroidTargetHierarchyDsl.() -> Unit) { - android.configure() - } } private fun KotlinTargetHierarchyDescriptor.extendIfNotNull(describe: (KotlinTargetHierarchyBuilder.Root.() -> Unit)?) = if (describe == null) this else extend(describe) -internal class KotlinAndroidTargetHierarchyDslImpl(objects: ObjectFactory) : KotlinAndroidTargetHierarchyDsl { - override val main: KotlinAndroidVariantHierarchyDsl = KotlinAndroidVariantHierarchyDslImpl(objects) - override val unitTest: KotlinAndroidVariantHierarchyDsl = KotlinAndroidVariantHierarchyDslImpl(objects) - override val instrumentedTest: KotlinAndroidVariantHierarchyDsl = KotlinAndroidVariantHierarchyDslImpl(objects) -} - -internal class KotlinAndroidVariantHierarchyDslImpl(objects: ObjectFactory) : KotlinAndroidVariantHierarchyDsl { - override val sourceSetTree: Property = objects.property() -} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier.kt new file mode 100644 index 00000000000..da5a7fb51c2 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/mpp/targetHierarchy/SourceSetTreeClassifier.kt @@ -0,0 +1,120 @@ +/* + * Copyright 2010-2023 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.targetHierarchy + +import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchy +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchy.SourceSetTree +import org.jetbrains.kotlin.gradle.plugin.awaitFinalValue +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilationFactory +import org.jetbrains.kotlin.tooling.core.extrasLazyProperty + +/** + * Classifier providing the corresponding [SourceSetTree] associated with any given [KotlinCompilation] + * + * ### Example: Overwriting 'test' compilations [SourceSetTreeClassifier]: + * Consider the following setup: + * ```kotlin + * kotlin { + * val testCompilation = jvm().compilations.getByName("test") + * } + * ``` + * + * In this example, we know that the 'jvm/test' compilation will have a 'jvmTest' SourceSet which + * will depend on 'commonTest' and therefore is part of the 'test' [SourceSetTree]. + * + * When another [SourceSetTreeClassifier] is specified, this behaviour is changed. + * Using the External Kotlin Target API: + * ```kotlin + * myTarget.createCompilation { + * compilationName = "test" + * sourceSetTreeClassifier = SourceSetTreeClassifier.Name("unitTest") + * } + * ``` + * + * This will create a compilation called 'test' which however will be considered part of the 'unitTest' SourceSetTree. + * The SourceSet of this 'jvm/test' compilation will still be called 'jvmTest' but since its part of the 'unitTest [SourceSetTree], + * there will not be a dependsOn edge to 'commonTest', but (if present) 'commonUnitTest' + */ +@ExternalKotlinTargetApi +sealed class SourceSetTreeClassifier { + + /** + * Default Classifier: The name of the compilation will be used to infer the [SourceSetTree]: + * 'main' compilations will be part of [SourceSetTree.main] + * 'test' compilations will be part of [SourceSetTree.test] + * ... + */ + @ExternalKotlinTargetApi + object Default : SourceSetTreeClassifier() { + override fun toString(): String = "Default" + } + + /** + * Indicates that the given compilations is not part of any 'named' [SourceSetTree]. + * Neither [KotlinTargetHierarchy] will be applied nor default dependsOn edges shall be set. + */ + @ExternalKotlinTargetApi + object None : SourceSetTreeClassifier() { + override fun toString(): String = "None" + } + + /** + * Predefined [SourceSetTree] using the [tree] specified. + */ + @ExternalKotlinTargetApi + data class Value(val tree: SourceSetTree) : SourceSetTreeClassifier() + + /** + * Predefined [SourceSetTree] using the [name] specified + */ + @ExternalKotlinTargetApi + data class Name(val name: String) : SourceSetTreeClassifier() + + /** + * Wrapper around [org.gradle.api.provider.Property] of a given [SourceSetTree] in order to + * make the [SourceSetTree] configurable. + */ + @ExternalKotlinTargetApi + class Property(val property: org.gradle.api.provider.Property) : SourceSetTreeClassifier() { + override fun toString(): String { + return property.toString() + } + } + + internal suspend fun classify(compilation: KotlinCompilation<*>): SourceSetTree? { + return when (this) { + is Default -> SourceSetTree(compilation.name) + is Property -> property.awaitFinalValue() + is Value -> tree + is Name -> SourceSetTree(name) + is None -> null + } + } +} + +/** + * Returns the classifier configured for a given compilation. + * This is writable, as Android requires overwriting of this default behaviour. + * + * - The KGP maintained target will set the classifier within the [KotlinJvmAndroidCompilationFactory] + * - The external Android target will set this classifier within the 'createCompilation' + * + * It is therefore safe to access this value as soon as a compilation is provided + */ +internal var KotlinCompilation<*>.sourceSetTreeClassifier: SourceSetTreeClassifier by extrasLazyProperty { + SourceSetTreeClassifier.Default +} + + +/** + * Returns the [SourceSetTree] of a given [KotlinCompilation]: + * Uses the [sourceSetTreeClassifier] under the hood. + * See [SourceSetTreeClassifier] + */ +internal suspend fun SourceSetTree.Companion.orNull(compilation: KotlinCompilation<*>) = + compilation.sourceSetTreeClassifier.classify(compilation) 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 index b030851e4b6..cde085557d5 100644 --- 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 @@ -11,10 +11,9 @@ import org.jetbrains.kotlin.gradle.dsl.kotlinExtension import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet -import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchy +import org.jetbrains.kotlin.gradle.plugin.awaitFinalValue import org.jetbrains.kotlin.gradle.plugin.launchInStage import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget -import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.orNull 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 @@ -38,7 +37,13 @@ internal object MultiplatformLayoutV2DependsOnConfigurator : KotlinAndroidSource return@launchInStage } - val sourceSetTree = KotlinTargetHierarchy.SourceSetTree.orNull(target, variantType) ?: return@launchInStage + val sourceSetTree = when (variantType) { + AndroidVariantType.Main -> target.targetHierarchy.main.sourceSetTree.awaitFinalValue() + AndroidVariantType.UnitTest -> target.targetHierarchy.unitTest.sourceSetTree.awaitFinalValue() + AndroidVariantType.InstrumentedTest -> target.targetHierarchy.instrumentedTest.sourceSetTree.awaitFinalValue() + AndroidVariantType.Unknown -> null + } ?: return@launchInStage + val commonSourceSetName = lowerCamelCaseName("common", sourceSetTree.name) val commonSourceSet = target.project.kotlinExtension.sourceSets.findByName(commonSourceSetName) ?: return@launchInStage kotlinSourceSet.dependsOn(commonSourceSet) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTarget.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTarget.kt index c52c61ed54a..11846b87a53 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTarget.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTarget.kt @@ -7,14 +7,13 @@ package org.jetbrains.kotlin.gradle.plugin.mpp import com.android.build.gradle.api.BaseVariant -import org.gradle.api.InvalidUserDataException -import org.gradle.api.Named -import org.gradle.api.NamedDomainObjectContainer -import org.gradle.api.Project +import org.gradle.api.* import org.gradle.api.artifacts.Configuration import org.gradle.api.attributes.Attribute import org.gradle.api.attributes.AttributeContainer +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchy.SourceSetTree import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.util.copyAttributes import org.jetbrains.kotlin.gradle.utils.dashSeparatedName import org.jetbrains.kotlin.gradle.utils.forAllAndroidVariants @@ -26,7 +25,7 @@ import javax.inject.Inject abstract class KotlinAndroidTarget @Inject constructor( final override val targetName: String, - project: Project + project: Project, ) : AbstractKotlinTarget(project) { final override val disambiguationClassifier: String = targetName @@ -37,6 +36,109 @@ abstract class KotlinAndroidTarget @Inject constructor( override val compilations: NamedDomainObjectContainer = project.container(KotlinJvmAndroidCompilation::class.java) + /** + * Configure Android specific settings within the context of [KotlinTargetHierarchy]. + * The difference between Android and other targets is that the build author is free to choose + * the names of compilations, whereas Android is using predefined SourceSet names. + * + * ### Default dependsOn edges + * By default, Kotlin Multiplatform will set the following default dependsOn edges: + * - `androidMain` -> `commonMain` + * - `androidUnitTest` -> `commonTest` + * + * In this default setup, SourceSets like `androidInstrumentedTest` will *not* dependOn `commonTest`. + * This API can be used to change the default behavior + * + * #### Example 1: Setting up androidInstrumentedTest -> commonTest + * This can be done by putting the 'androidInstrumentedTest' variants into the 'test' [SourceSetTree]: + * ```kotlin + * androidTarget().targetHierarchy { + * instrumentedTest.sourceSetTree.set(SourceSetTree.test) + * } + * ``` + * + * #### Example 2: Setting up androidInstrumentedTest -> commonTest and removing 'unitTests' from the 'test' [SourceSetTree] + * ```kotlin + * androidTarget().targetHierarchy { + * instrumentedTest.sourceSetTree.set(SourceSetTree.test) + * unitTest.sourceSetTree.set(SourceSetTree.unitTest) // ! <- Anything *other* than 'test' + * } + * ``` + */ + @ExperimentalKotlinGradlePluginApi + val targetHierarchy: KotlinAndroidTargetHierarchyDsl = KotlinAndroidTargetHierarchyDslImpl(project.objects).apply { + main.sourceSetTree.convention(SourceSetTree.main) + unitTest.sourceSetTree.convention(SourceSetTree.test) + instrumentedTest.sourceSetTree.convention(SourceSetTree.instrumentedTest) + } + + /** + * Configure Android specific settings within the context of [KotlinTargetHierarchy]. + * The difference between Android and other targets is that the build author is free to choose + * the names of compilations, whereas Android is using predefined SourceSet names. + * + * ### Default dependsOn edges + * By default, Kotlin Multiplatform will set the following default dependsOn edges: + * - `androidMain` -> `commonMain` + * - `androidUnitTest` -> `commonTest` + * + * In this default setup, SourceSets like `androidInstrumentedTest` will *not* dependOn `commonTest`. + * This API can be used to change the default behavior + * + * #### Example 1: Setting up androidInstrumentedTest -> commonTest + * This can be done by putting the 'androidInstrumentedTest' variants into the 'test' [SourceSetTree]: + * ```kotlin + * androidTarget().targetHierarchy { + * instrumentedTest.sourceSetTree.set(SourceSetTree.test) + * } + * ``` + * + * #### Example 2: Setting up androidInstrumentedTest -> commonTest and removing 'unitTests' from the 'test' [SourceSetTree] + * ```kotlin + * androidTarget().targetHierarchy { + * instrumentedTest.sourceSetTree.set(SourceSetTree.test) + * unitTest.sourceSetTree.set(SourceSetTree.unitTest) // ! <- Anything *other* than 'test' + * } + * ``` + */ + @ExperimentalKotlinGradlePluginApi + fun targetHierarchy(configure: Action) { + configure.execute(targetHierarchy) + } + + /** + * Configure Android specific settings within the context of [KotlinTargetHierarchy]. + * The difference between Android and other targets is that the build author is free to choose + * the names of compilations, whereas Android is using predefined SourceSet names. + * + * ### Default dependsOn edges + * By default, Kotlin Multiplatform will set the following default dependsOn edges: + * - `androidMain` -> `commonMain` + * - `androidUnitTest` -> `commonTest` + * + * In this default setup, SourceSets like `androidInstrumentedTest` will *not* dependOn `commonTest`. + * This API can be used to change the default behavior + * + * #### Example 1: Setting up androidInstrumentedTest -> commonTest + * This can be done by putting the 'androidInstrumentedTest' variants into the 'test' [SourceSetTree]: + * ```kotlin + * androidTarget().targetHierarchy { + * instrumentedTest.sourceSetTree.set(SourceSetTree.test) + * } + * ``` + * + * #### Example 2: Setting up androidInstrumentedTest -> commonTest and removing 'unitTests' from the 'test' [SourceSetTree] + * ```kotlin + * androidTarget().targetHierarchy { + * instrumentedTest.sourceSetTree.set(SourceSetTree.test) + * unitTest.sourceSetTree.set(SourceSetTree.unitTest) // ! <- Anything *other* than 'test' + * } + * ``` + */ + @ExperimentalKotlinGradlePluginApi + fun targetHierarchy(configure: KotlinAndroidTargetHierarchyDsl.() -> Unit) { + targetHierarchy.configure() + } /** Names of the Android library variants that should be published from the target's project within the default publications which are * set up if the `maven-publish` Gradle plugin is applied. @@ -241,7 +343,7 @@ abstract class KotlinAndroidTarget @Inject constructor( private fun createSourcesElementsIfNeeded( variantName: String, apiElementsConfigurationName: String, - sourcesElementsConfigurationName: String + sourcesElementsConfigurationName: String, ): Configuration { val existingConfiguration = project.configurations.findByName(sourcesElementsConfigurationName) if (existingConfiguration != null) return existingConfiguration @@ -261,7 +363,7 @@ abstract class KotlinAndroidTarget @Inject constructor( /** We filter this variant out as it is never requested on the consumer side, while keeping it leads to ambiguity between Android and * JVM variants due to non-nesting sets of unmatched attributes. */ private fun filterOutAndroidVariantAttribute( - attribute: Attribute<*> + attribute: Attribute<*>, ): Boolean = attribute.name != "com.android.build.gradle.internal.attributes.VariantAttr" && attribute.name != "com.android.build.api.attributes.VariantAttr" @@ -269,7 +371,7 @@ abstract class KotlinAndroidTarget @Inject constructor( private fun filterOutAndroidBuildTypeAttribute( it: Attribute<*>, valueString: String, - isSinglePublishedVariant: Boolean + isSinglePublishedVariant: Boolean, ) = when { PropertiesProvider(project).keepAndroidBuildTypeAttribute -> true it.name != "com.android.build.api.attributes.BuildTypeAttr" -> true @@ -281,6 +383,7 @@ abstract class KotlinAndroidTarget @Inject constructor( } private fun filterOutAndroidAgpVersionAttribute( - attribute: Attribute<*> + attribute: Attribute<*>, ): Boolean = attribute.name != "com.android.build.api.attributes.AgpVersionAttr" } + diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTargetHierarchyDsl.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTargetHierarchyDsl.kt new file mode 100644 index 00000000000..872f2743589 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/android/KotlinAndroidTargetHierarchyDsl.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ +@file:Suppress("PackageDirectoryMismatch") // Old package for compatibility + +package org.jetbrains.kotlin.gradle.plugin.mpp + +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.KotlinTargetHierarchyDsl +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchy +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchy.SourceSetTree +import org.jetbrains.kotlin.gradle.utils.property + +@ExperimentalKotlinGradlePluginApi +interface KotlinAndroidTargetHierarchyDsl { + val main: KotlinAndroidVariantHierarchyDsl + val unitTest: KotlinAndroidVariantHierarchyDsl + val instrumentedTest: KotlinAndroidVariantHierarchyDsl +} + + +@ExperimentalKotlinGradlePluginApi +interface KotlinAndroidVariantHierarchyDsl { + /** + * Configures under which [SourceSetTree] the currently configured Android Variant shall be placed. + * e.g. + * + * ```kotlin + * kotlin { + * targetHierarchy.android { + * instrumentedTest.sourceSetTree.set(SourceSetTree.test) + * } + * } + * ``` + * + * Will ensure that all android instrumented tests (androidInstrumentedTest, androidInstrumentedTestDebug, ...) + * will be placed into the 'test' SourceSet tree (with 'commonTest' as root) + * + * See [KotlinTargetHierarchyDsl.android] + */ + val sourceSetTree: Property +} + +internal class KotlinAndroidTargetHierarchyDslImpl(objects: ObjectFactory) : KotlinAndroidTargetHierarchyDsl { + override val main: KotlinAndroidVariantHierarchyDsl = KotlinAndroidVariantHierarchyDslImpl(objects) + override val unitTest: KotlinAndroidVariantHierarchyDsl = KotlinAndroidVariantHierarchyDslImpl(objects) + override val instrumentedTest: KotlinAndroidVariantHierarchyDsl = KotlinAndroidVariantHierarchyDslImpl(objects) +} + +internal class KotlinAndroidVariantHierarchyDslImpl(objects: ObjectFactory) : KotlinAndroidVariantHierarchyDsl { + override val sourceSetTree: Property = objects.property() +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilationFactory.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilationFactory.kt index aab2c6b8d83..6b71ead30a2 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilationFactory.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/jvm/KotlinJvmAndroidCompilationFactory.kt @@ -12,10 +12,15 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinAndroidCompi import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.factory.AndroidCompilationSourceSetsContainerFactory import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.factory.KotlinCompilationImplFactory import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.factory.KotlinJvmCompilerOptionsFactory +import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.SourceSetTreeClassifier +import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.SourceSetTreeClassifier.Property +import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.sourceSetTreeClassifier +import org.jetbrains.kotlin.gradle.plugin.sources.android.AndroidVariantType +import org.jetbrains.kotlin.gradle.plugin.sources.android.type class KotlinJvmAndroidCompilationFactory internal constructor( override val target: KotlinAndroidTarget, - private val variant: BaseVariant + private val variant: BaseVariant, ) : KotlinCompilationFactory { override val itemClass: Class @@ -34,6 +39,17 @@ class KotlinJvmAndroidCompilationFactory internal constructor( ) override fun create(name: String): KotlinJvmAndroidCompilation { - return project.objects.newInstance(itemClass, compilationImplFactory.create(target, name), variant) + return project.objects.newInstance(itemClass, compilationImplFactory.create(target, name), variant).also { compilation -> + configureSourceSetTreeClassifier(compilation) + } + } + + private fun configureSourceSetTreeClassifier(compilation: KotlinJvmAndroidCompilation) { + compilation.sourceSetTreeClassifier = when (variant.type) { + AndroidVariantType.Main -> Property(target.targetHierarchy.main.sourceSetTree) + AndroidVariantType.UnitTest -> Property(target.targetHierarchy.unitTest.sourceSetTree) + AndroidVariantType.InstrumentedTest -> Property(target.targetHierarchy.instrumentedTest.sourceSetTree) + AndroidVariantType.Unknown -> SourceSetTreeClassifier.None + } } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/ExternalKotlinTargetApiTests.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/ExternalKotlinTargetApiTests.kt new file mode 100644 index 00000000000..ebd21283d18 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/ExternalKotlinTargetApiTests.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +@file:Suppress("FunctionName", "DuplicatedCode") + +package org.jetbrains.kotlin.gradle.unitTests + +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle +import org.jetbrains.kotlin.gradle.plugin.KotlinTargetHierarchy.SourceSetTree +import org.jetbrains.kotlin.gradle.plugin.launchInStage +import org.jetbrains.kotlin.gradle.plugin.mpp.external.* +import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinCompilationDescriptor.CompilationFactory +import org.jetbrains.kotlin.gradle.plugin.mpp.external.ExternalKotlinTargetDescriptor.TargetFactory +import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.SourceSetTreeClassifier +import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.orNull +import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP +import org.jetbrains.kotlin.gradle.util.runLifecycleAwareTest +import org.jetbrains.kotlin.gradle.utils.property +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ExternalKotlinTargetApiTests { + + class FakeTarget(delegate: Delegate) : DecoratedExternalKotlinTarget(delegate) + class FakeCompilation(delegate: Delegate) : DecoratedExternalKotlinCompilation(delegate) + + val project = buildProjectWithMPP() + val kotlin = project.multiplatformExtension + + private fun ExternalKotlinTargetDescriptorBuilder.defaults() { + targetName = "fake" + platformType = KotlinPlatformType.jvm + targetFactory = TargetFactory(::FakeTarget) + } + + private fun ExternalKotlinCompilationDescriptorBuilder.defaults() { + compilationName = "fake" + compilationFactory = CompilationFactory(::FakeCompilation) + defaultSourceSet = kotlin.sourceSets.maybeCreate("fake") + } + + @Test + fun `test - sourceSetClassifier - default`() = buildProjectWithMPP().runLifecycleAwareTest { + val target = kotlin.createExternalKotlinTarget { defaults() } + val compilation = target.createCompilation { defaults() } + + assertEquals(SourceSetTree("fake"), SourceSetTree.orNull(compilation)) + } + + @Test + fun `test - sourceSetClassifier - custom name`() = buildProjectWithMPP().runLifecycleAwareTest { + val target = kotlin.createExternalKotlinTarget { defaults() } + val compilation = target.createCompilation { + defaults() + sourceSetTreeClassifier = SourceSetTreeClassifier.Name("mySourceSetTree") + } + + assertEquals(SourceSetTree("mySourceSetTree"), SourceSetTree.orNull(compilation)) + } + + @Test + fun `test - sourceSetClassifier - custom property`() = buildProjectWithMPP().runLifecycleAwareTest { + val myProperty = project.objects.property() + val nullProperty = project.objects.property() + + val target = kotlin.createExternalKotlinTarget { defaults() } + + val mainCompilation = target.createCompilation { + defaults() + sourceSetTreeClassifier = SourceSetTreeClassifier.Property(myProperty) + } + + val auxCompilation = target.createCompilation() { + compilationName = "aux" + compilationFactory = CompilationFactory(::FakeCompilation) + defaultSourceSet = kotlin.sourceSets.create("aux") + sourceSetTreeClassifier = SourceSetTreeClassifier.Property(nullProperty) + } + + launchInStage(KotlinPluginLifecycle.Stage.FinaliseDsl) { + myProperty.set(SourceSetTree.main) + } + + assertEquals(SourceSetTree.main, SourceSetTree.orNull(mainCompilation)) + assertNull(SourceSetTree.orNull(auxCompilation)) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/KotlinAndroidTargetHierarchyDsl.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/KotlinAndroidTargetHierarchyDsl.kt index 98d3c6b0470..c699d25856a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/KotlinAndroidTargetHierarchyDsl.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/KotlinAndroidTargetHierarchyDsl.kt @@ -15,7 +15,7 @@ import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage.AfterFinal import org.jetbrains.kotlin.gradle.plugin.awaitFinalValue import org.jetbrains.kotlin.gradle.plugin.currentKotlinPluginLifecycle import org.jetbrains.kotlin.gradle.plugin.kotlinPluginLifecycle -import org.jetbrains.kotlin.gradle.plugin.mpp.targetHierarchy.KotlinAndroidVariantHierarchyDslImpl +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidVariantHierarchyDslImpl import org.jetbrains.kotlin.gradle.util.* import org.junit.Test import kotlin.test.assertEquals @@ -38,7 +38,7 @@ class KotlinAndroidTargetHierarchyDsl { afterEvaluate { dsl.sourceSetTree.set(KotlinTargetHierarchy.SourceSetTree("x")) } dsl.sourceSetTree.set(KotlinTargetHierarchy.SourceSetTree("-set-before-after-evaluate-")) assertEquals("x", dsl.sourceSetTree.awaitFinalValue()?.name) - assertEquals(KotlinPluginLifecycle.Stage.FinaliseDsl, currentKotlinPluginLifecycle().stage) + assertEquals(KotlinPluginLifecycle.Stage.AfterFinaliseDsl, currentKotlinPluginLifecycle().stage) } @Test @@ -51,9 +51,7 @@ class KotlinAndroidTargetHierarchyDsl { val kotlin = project.multiplatformExtension project.runLifecycleAwareTest { - kotlin.androidTarget() - - kotlin.targetHierarchy.android { + kotlin.androidTarget().targetHierarchy { unitTest.sourceSetTree.set(KotlinTargetHierarchy.SourceSetTree.test) instrumentedTest.sourceSetTree.set(KotlinTargetHierarchy.SourceSetTree.test) } @@ -75,9 +73,7 @@ class KotlinAndroidTargetHierarchyDsl { val kotlin = project.multiplatformExtension project.runLifecycleAwareTest { - kotlin.androidTarget() - - kotlin.targetHierarchy.android { + kotlin.androidTarget().targetHierarchy { unitTest.sourceSetTree.set(KotlinTargetHierarchy.SourceSetTree("xxx")) instrumentedTest.sourceSetTree.set(KotlinTargetHierarchy.SourceSetTree("yyy")) }