diff --git a/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ComposeCompilerGradlePluginExtension.kt b/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ComposeCompilerGradlePluginExtension.kt index c3ff922ce7910..a7be80b8da56a 100644 --- a/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ComposeCompilerGradlePluginExtension.kt +++ b/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ComposeCompilerGradlePluginExtension.kt @@ -20,6 +20,8 @@ import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.Property +import org.gradle.api.provider.SetProperty +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import javax.inject.Inject abstract class ComposeCompilerGradlePluginExtension @Inject constructor(objectFactory: ObjectFactory) { @@ -114,4 +116,31 @@ abstract class ComposeCompilerGradlePluginExtension @Inject constructor(objectFa * - [composition tracing blog post](https://medium.com/androiddevelopers/jetpack-compose-composition-tracing-9ec2b3aea535) */ val includeTraceMarkers: Property = objectFactory.property(Boolean::class.java).convention(false) + + /** + * A set of Kotlin platforms to which the Compose plugin will be applied. + * + * By default, all Kotlin platforms are enabled. + * + * To enable only one specific Kotlin platform: + * ``` + * composeCompiler { + * targetKotlinPlatforms.set(setOf(KotlinPlatformType.jvm)) + * } + * ``` + * + * To disable the Compose plugin for one or more Kotlin platforms: + * ``` + * composeCompiler { + * targetKotlinPlatforms.set( + * KotlinPlatformType.values() + * .filterNot { it == KotlinPlatformType.native || it == KotlinPlatformType.js } + * .asIterable() + * ) + * } + * ``` + */ + val targetKotlinPlatforms: SetProperty = objectFactory + .setProperty(KotlinPlatformType::class.java) + .convention(KotlinPlatformType.values().asIterable()) } diff --git a/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ComposeCompilerSubplugin.kt b/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ComposeCompilerSubplugin.kt index 55f66eec8890b..815939f19d84b 100644 --- a/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ComposeCompilerSubplugin.kt +++ b/libraries/tools/kotlin-compose-compiler/src/common/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ComposeCompilerSubplugin.kt @@ -19,27 +19,26 @@ class ComposeCompilerGradleSubplugin ) : KotlinCompilerPluginSupportPlugin { companion object { - fun getComposeCompilerGradlePluginExtension(project: Project): ComposeCompilerGradlePluginExtension { - return project.extensions.getByType(ComposeCompilerGradlePluginExtension::class.java) - } - private const val COMPOSE_COMPILER_ARTIFACT_NAME = "kotlin-compose-compiler-plugin-embeddable" private val EMPTY_OPTION = SubpluginOption("", "") } + private lateinit var composeExtension: ComposeCompilerGradlePluginExtension + override fun apply(target: Project) { - target.extensions.create("composeCompiler", ComposeCompilerGradlePluginExtension::class.java) + composeExtension = target.extensions.create("composeCompiler", ComposeCompilerGradlePluginExtension::class.java) registry.register(ComposeCompilerModelBuilder()) } - override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean = true + override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean { + return composeExtension.targetKotlinPlatforms.get().contains(kotlinCompilation.platformType) + } override fun applyToCompilation( kotlinCompilation: KotlinCompilation<*>, ): Provider> { val project = kotlinCompilation.target.project - val composeCompilerExtension = getComposeCompilerGradlePluginExtension(project) // It is ok to check AGP existence without using `plugins.withId` wrapper as `applyToCompilation` will be called in `afterEvaluate` if (project.isAgpComposeEnabled) { @@ -59,34 +58,34 @@ class ComposeCompilerGradleSubplugin val allPluginProperties = project.objects .listProperty(SubpluginOption::class.java) .apply { - add(composeCompilerExtension.generateFunctionKeyMetaClasses.map { + add(composeExtension.generateFunctionKeyMetaClasses.map { SubpluginOption("generateFunctionKeyMetaClasses", it.toString()) }) if (!project.isAgpComposeEnabled) { - add(composeCompilerExtension.includeSourceInformation.map { + add(composeExtension.includeSourceInformation.map { SubpluginOption("sourceInformation", it.toString()) }) } - add(composeCompilerExtension.metricsDestination.map { + add(composeExtension.metricsDestination.map { FilesSubpluginOption("metricsDestination", listOf(it.asFile)) }.orElse(EMPTY_OPTION)) - add(composeCompilerExtension.reportsDestination.map { + add(composeExtension.reportsDestination.map { FilesSubpluginOption("reportsDestination", listOf(it.asFile)) }.orElse(EMPTY_OPTION)) - add(composeCompilerExtension.enableIntrinsicRemember.map { + add(composeExtension.enableIntrinsicRemember.map { SubpluginOption("intrinsicRemember", it.toString()) }) - add(composeCompilerExtension.enableNonSkippingGroupOptimization.map { + add(composeExtension.enableNonSkippingGroupOptimization.map { SubpluginOption("nonSkippingGroupOptimization", it.toString()) }) - add(composeCompilerExtension.enableStrongSkippingMode.map { + add(composeExtension.enableStrongSkippingMode.map { // Rename once the option in Compose compiler is also renamed SubpluginOption("experimentalStrongSkipping", it.toString()) }) - add(composeCompilerExtension.stabilityConfigurationFile.map { + add(composeExtension.stabilityConfigurationFile.map { FilesSubpluginOption("stabilityConfigurationPath", listOf(it.asFile)) }.orElse(EMPTY_OPTION)) - add(composeCompilerExtension.includeTraceMarkers.map { + add(composeExtension.includeTraceMarkers.map { SubpluginOption("traceMarkersEnabled", it.toString()) }) } diff --git a/libraries/tools/kotlin-compose-compiler/src/functionalTest/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/EnabledPlatformsConfigurationTest.kt b/libraries/tools/kotlin-compose-compiler/src/functionalTest/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/EnabledPlatformsConfigurationTest.kt new file mode 100644 index 0000000000000..865f8608592cd --- /dev/null +++ b/libraries/tools/kotlin-compose-compiler/src/functionalTest/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/EnabledPlatformsConfigurationTest.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2010-2024 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.compose.compiler.gradle + +import org.gradle.kotlin.dsl.getByType +import org.jetbrains.kotlin.compose.compiler.gradle.testUtils.buildProjectWithMPP +import org.jetbrains.kotlin.compose.compiler.gradle.testUtils.composeOptions +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.tasks.BaseKotlinCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile +import org.jetbrains.kotlin.gradle.utils.named +import org.junit.jupiter.api.Test +import kotlin.test.assertTrue + +class EnabledPlatformsConfigurationTest { + + @OptIn(ExperimentalWasmDsl::class) + @Test + fun allKotlinPlatformsAreUsedByDefault() { + val project = buildProjectWithMPP { + extensions.getByType().apply { + jvm() + js { nodejs() } + wasmJs() + wasmWasi() + linuxX64() + + applyDefaultHierarchyTemplate() + } + } + + project.evaluate() + + listOf( + "compileKotlinMetadata", + "compileCommonMainKotlinMetadata", + "compileKotlinJs", + "compileKotlinJvm", + "compileKotlinWasmJs", + "compileKotlinWasmWasi" + ).forEach { + assertTrue( + project.tasks.named(it).get().composeOptions().isNotEmpty(), + "'$it' task does not contain compose plugin options" + ) + } + + assertTrue( + project.tasks.named("compileKotlinLinuxX64").get().composeOptions().isNotEmpty(), + "'compileKotlinLinuxX64' task does not contain compose plugin options" + ) + } + + @OptIn(ExperimentalWasmDsl::class) + @Test + fun disableKotlinPlatforms() { + val project = buildProjectWithMPP { + extensions.getByType().apply { + jvm() + js { nodejs() } + wasmJs() + wasmWasi() + linuxX64() + + applyDefaultHierarchyTemplate() + } + + extensions.getByType() + .targetKotlinPlatforms + .set(setOf(KotlinPlatformType.jvm)) + } + + project.evaluate() + + assertTrue( + project.tasks.named("compileKotlinJvm").get().composeOptions().isNotEmpty(), + "'compileKotlinJvm' task does not contain compose plugin options" + ) + + listOf( + "compileKotlinMetadata", + "compileCommonMainKotlinMetadata", + "compileKotlinJs", + "compileKotlinWasmJs", + "compileKotlinWasmWasi" + ).forEach { + assertTrue( + project.tasks.named(it).get().composeOptions().isEmpty(), + "'$it' task contains compose plugin options" + ) + } + + assertTrue( + project.tasks.named("compileKotlinLinuxX64").get().composeOptions().isEmpty(), + "'compileKotlinLinuxX64' task contains compose plugin options" + ) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-compose-compiler/src/functionalTest/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ExtensionConfigurationTest.kt b/libraries/tools/kotlin-compose-compiler/src/functionalTest/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ExtensionConfigurationTest.kt index e16f87e0e7cfd..0a2331c4135c2 100644 --- a/libraries/tools/kotlin-compose-compiler/src/functionalTest/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ExtensionConfigurationTest.kt +++ b/libraries/tools/kotlin-compose-compiler/src/functionalTest/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/ExtensionConfigurationTest.kt @@ -11,6 +11,7 @@ import org.gradle.kotlin.dsl.named import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile import org.junit.jupiter.api.Test import org.jetbrains.kotlin.compose.compiler.gradle.testUtils.buildProjectWithJvm +import org.jetbrains.kotlin.compose.compiler.gradle.testUtils.composeOptions import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -109,15 +110,10 @@ class ExtensionConfigurationTest { project.evaluate() val jvmTask = project.tasks.named("compileKotlin").get() - val composeOptions = jvmTask.pluginOptions.get() - .flatMap { compilerPluginConfig -> - compilerPluginConfig.allOptions().filter { it.key == "androidx.compose.compiler.plugins.kotlin" }.values - } - .flatten() - .map { it.key to it.value } + val composeOptions = jvmTask.composeOptions() assertions(composeOptions, project) } - fun Project.simulateAgpPresence() = configurations.resolvable("kotlin-extension") + private fun Project.simulateAgpPresence() = configurations.resolvable("kotlin-extension") } \ No newline at end of file diff --git a/libraries/tools/kotlin-compose-compiler/src/functionalTest/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/testUtils/composeOptions.kt b/libraries/tools/kotlin-compose-compiler/src/functionalTest/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/testUtils/composeOptions.kt new file mode 100644 index 0000000000000..6874e852dc03d --- /dev/null +++ b/libraries/tools/kotlin-compose-compiler/src/functionalTest/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/testUtils/composeOptions.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2010-2024 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.compose.compiler.gradle.testUtils + +import org.jetbrains.kotlin.gradle.tasks.BaseKotlinCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile + +fun BaseKotlinCompile.composeOptions() = pluginOptions.get() + .flatMap { compilerPluginConfig -> + compilerPluginConfig.allOptions().filter { it.key == "androidx.compose.compiler.plugins.kotlin" }.values + } + .flatten() + .map { it.key to it.value } + +fun KotlinNativeCompile.composeOptions() = compilerPluginOptions + .allOptions() + .filter { it.key == "androidx.compose.compiler.plugins.kotlin" }.values + .flatten() + .map { it.key to it.value } diff --git a/libraries/tools/kotlin-compose-compiler/src/functionalTest/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/testUtils/functionalTestDsl.kt b/libraries/tools/kotlin-compose-compiler/src/functionalTest/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/testUtils/functionalTestDsl.kt index 936e02ee31cf8..4b99a8398c9d0 100644 --- a/libraries/tools/kotlin-compose-compiler/src/functionalTest/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/testUtils/functionalTestDsl.kt +++ b/libraries/tools/kotlin-compose-compiler/src/functionalTest/kotlin/org/jetbrains/kotlin/compose/compiler/gradle/testUtils/functionalTestDsl.kt @@ -17,6 +17,7 @@ import org.gradle.internal.service.scopes.ProjectScopeServices import org.gradle.testfixtures.ProjectBuilder import org.gradle.tooling.events.OperationCompletionListener import org.jetbrains.kotlin.compose.compiler.gradle.ComposeCompilerGradleSubplugin +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper import java.lang.reflect.Field import java.util.concurrent.atomic.AtomicReference @@ -45,6 +46,17 @@ fun buildProjectWithJvm( code() } +fun buildProjectWithMPP( + projectBuilder: ProjectBuilder.() -> Unit = { }, + preApplyCode: Project.() -> Unit = {}, + code: Project.() -> Unit = {} +) = buildProject(projectBuilder) { + preApplyCode() + project.applyMultiplatformPlugin() + project.applyKotlinComposePlugin() + code() +} + /** * In Gradle 6.7-rc-1 BuildEventsListenerRegistry service is not created in we need it in order * to instantiate AGP. This creates a fake one and injects it - http://b/168630734. @@ -96,4 +108,15 @@ fun Project.applyKotlinJvmPlugin() { fun Project.applyKotlinComposePlugin() { project.plugins.apply(ComposeCompilerGradleSubplugin::class.java) +} + +fun Project.applyMultiplatformPlugin(): KotlinMultiplatformExtension { + addBuildEventsListenerRegistryMock(this) + disableLegacyWarning(project) + plugins.apply("kotlin-multiplatform") + return extensions.getByName("kotlin") as KotlinMultiplatformExtension +} + +internal fun disableLegacyWarning(project: Project) { + project.extraProperties.set("kotlin.js.compiler.nowarn", "true") } \ No newline at end of file