From 4c7ad7eeb5658fef99ac8b1394ef5667f038636e Mon Sep 17 00:00:00 2001 From: Sebastian Sellmair Date: Mon, 11 Dec 2023 12:14:17 +0100 Subject: [PATCH] [Gradle] Add IdeMultiplatformImportAction API This API can be used to schedule actions/setup code that is only relevant for IDE sync. ^KT-56440 Verification Pending --- .../api/kotlin-gradle-plugin.api | 6 ++ .../plugin/ide/IdeMultiplatformImport.kt | 17 +++- .../ide/IdeMultiplatformImportAction.kt | 56 +++++++++++++ .../plugin/ide/IdeMultiplatformImportImpl.kt | 4 + .../plugin/registerKotlinPluginExtensions.kt | 47 +---------- .../IdeMultiplatformImportActionTest.kt | 82 +++++++++++++++++++ 6 files changed, 168 insertions(+), 44 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportAction.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/IdeMultiplatformImportActionTest.kt 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 a3d7915b4ab..c13c1ca00aa 100644 --- a/libraries/tools/kotlin-gradle-plugin/api/kotlin-gradle-plugin.api +++ b/libraries/tools/kotlin-gradle-plugin/api/kotlin-gradle-plugin.api @@ -606,6 +606,7 @@ public abstract interface class org/jetbrains/kotlin/gradle/plugin/ide/IdeMultip public abstract fun registerDependencyResolver (Lorg/jetbrains/kotlin/gradle/plugin/ide/IdeDependencyResolver;Lorg/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport$SourceSetConstraint;Lorg/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport$DependencyResolutionPhase;Lorg/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport$Priority;)V public abstract fun registerDependencyTransformer (Lorg/jetbrains/kotlin/gradle/plugin/ide/IdeDependencyTransformer;Lorg/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport$SourceSetConstraint;Lorg/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport$DependencyTransformationPhase;)V public abstract fun registerExtrasSerializationExtension (Lorg/jetbrains/kotlin/gradle/idea/serialize/IdeaKotlinExtrasSerializationExtension;)V + public abstract fun registerImportAction (Lorg/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportAction;)V public abstract fun resolveDependencies (Ljava/lang/String;)Ljava/util/Set; public abstract fun resolveDependencies (Lorg/jetbrains/kotlin/gradle/plugin/KotlinSourceSet;)Ljava/util/Set; public abstract fun resolveDependenciesSerialized (Ljava/lang/String;)Ljava/util/List; @@ -678,6 +679,11 @@ public final class org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport public final fun isSinglePlatformType ()Lorg/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport$SourceSetConstraint; } +public abstract interface class org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportAction { + public static final field Companion Lorg/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportAction$Companion; + public abstract fun prepareIdeImport (Lorg/gradle/api/Project;)V +} + public final class org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportKt { public static final fun and (Lorg/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport$SourceSetConstraint;Lorg/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport$SourceSetConstraint;)Lorg/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport$SourceSetConstraint; public static final fun not (Lorg/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport$SourceSetConstraint;)Lorg/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport$SourceSetConstraint; diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport.kt index c483dad2b69..ee62ecbee64 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImport.kt @@ -15,10 +15,15 @@ import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinExtrasSerializationExtension import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinExtrasSerializationExtensionBuilder import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinDependency -import org.jetbrains.kotlin.gradle.plugin.* +import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType.* +import org.jetbrains.kotlin.gradle.plugin.KotlinProjectSetupAction import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.KotlinTarget +import org.jetbrains.kotlin.gradle.plugin.ide.IdeMultiplatformImport.Priority.Companion.high +import org.jetbrains.kotlin.gradle.plugin.ide.IdeMultiplatformImport.Priority.Companion.low +import org.jetbrains.kotlin.gradle.plugin.ide.IdeMultiplatformImport.Priority.Companion.normal +import org.jetbrains.kotlin.gradle.plugin.ide.IdeMultiplatformImport.Priority.Companion.veryHigh import org.jetbrains.kotlin.gradle.plugin.ide.IdeMultiplatformImport.SourceSetConstraint import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeCompilation import org.jetbrains.kotlin.gradle.plugin.sources.internal @@ -118,6 +123,16 @@ interface IdeMultiplatformImport { extension: IdeaKotlinExtrasSerializationExtension, ) + /** + * Registers a [IdeMultiplatformImportAction] which will be invoked only if an IDE/Gradle sync (import) is running. + * This action is guaranteed to run before the Kotlin Multiplatform Model is being built. + * There is no 'guarantee' whether this action will be running during configuration phase or as a first step + * of model building. This is considered an implementation detail. + * For further details please read [IdeMultiplatformImportAction] + */ + @ExternalKotlinTargetApi + fun registerImportAction(action: IdeMultiplatformImportAction) + /** * Any [IdeDependencyResolver] has to be registered for a given dependency resolution phase in which it participates * The resolution phases will be executed in the order of their enum's ordinal. diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportAction.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportAction.kt new file mode 100644 index 00000000000..ffd41c9df7a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportAction.kt @@ -0,0 +1,56 @@ +/* + * 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.ide + +import org.gradle.api.Project +import org.jetbrains.kotlin.gradle.ExternalKotlinTargetApi +import org.jetbrains.kotlin.gradle.internal.isInIdeaSync +import org.jetbrains.kotlin.gradle.plugin.KotlinGradlePluginExtensionPoint +import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle +import org.jetbrains.kotlin.gradle.plugin.KotlinProjectSetupCoroutine +import org.jetbrains.kotlin.gradle.plugin.await + +/** + * Registers a generic action that shall only run when a Kotlin Multiplatform project is being imported into the IDE. + * Note: For heavy workloads, please consider using the 'prepareKotlinIdeaImport' task infrastructure. + * (e.g. by implementing [IdeDependencyResolver.WithBuildDependencies]) instead. + * + * Note: The *exact* time of execution is not guaranteed and can be considered an implementation detail, however + * it is guaranteed that the action will be executed + * - only during Gradle import + * - before any Kotlin Multiplatform Model will be built + * + * + * Example of registering an action: + * + * ```kotlin + * kotlinExtension.createExternalKotlinTarget { + * configureIdeImport { + * registerImportAction { + * println("Running ide import") // Will be executed during Gradle sync + * } + * + * registerImportAction(MyIdeImportAction) // Will be executed during Gradle sync + * } + * } + * ``` + */ +@ExternalKotlinTargetApi +fun interface IdeMultiplatformImportAction { + fun prepareIdeImport(project: Project) + + companion object { + internal val extensionPoint = KotlinGradlePluginExtensionPoint() + } +} + +internal val IdeMultiplatformImportActionSetupAction = KotlinProjectSetupCoroutine { + if (!isInIdeaSync) return@KotlinProjectSetupCoroutine + KotlinPluginLifecycle.Stage.ReadyForExecution.await() + IdeMultiplatformImportAction.extensionPoint[this].forEach { action -> + action.prepareIdeImport(this) + } +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportImpl.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportImpl.kt index c7fc895e7af..fbd353364d0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportImpl.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/ide/IdeMultiplatformImportImpl.kt @@ -127,6 +127,10 @@ internal class IdeMultiplatformImportImpl( registeredExtrasSerializationExtensions.add(extension) } + override fun registerImportAction(action: IdeMultiplatformImportAction) { + IdeMultiplatformImportAction.extensionPoint.register(extension.project, action) + } + private fun createDependencyResolver(): IdeDependencyResolver { return IdeDependencyResolver( DependencyResolutionPhase.values().map { phase -> createDependencyResolver(phase) } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt index 79c15a01ba5..4d02b18636f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/registerKotlinPluginExtensions.kt @@ -7,63 +7,23 @@ package org.jetbrains.kotlin.gradle.plugin import org.gradle.api.Project import org.jetbrains.kotlin.gradle.artifacts.* -import org.jetbrains.kotlin.gradle.artifacts.KotlinJsKlibArtifact -import org.jetbrains.kotlin.gradle.artifacts.KotlinJvmJarArtifact -import org.jetbrains.kotlin.gradle.artifacts.KotlinNativeHostSpecificMetadataArtifact -import org.jetbrains.kotlin.gradle.artifacts.KotlinNativeKlibArtifact -import org.jetbrains.kotlin.gradle.artifacts.KotlinTargetArtifact import org.jetbrains.kotlin.gradle.dsl.* -import org.jetbrains.kotlin.gradle.dsl.AndroidMainSourceSetConventionsChecker -import org.jetbrains.kotlin.gradle.dsl.IosSourceSetConventionChecker -import org.jetbrains.kotlin.gradle.dsl.PlatformSourceSetConventionsChecker -import org.jetbrains.kotlin.gradle.dsl.multiplatformExtensionOrNull import org.jetbrains.kotlin.gradle.internal.CustomizeKotlinDependenciesSetupAction -import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnosticsSetupAction import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinGradleProjectChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.AndroidPluginWithoutAndroidTargetChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.AndroidSourceSetLayoutV1SourceSetsNotFoundChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.CommonMainOrTestWithDependsOnChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.DeprecatedKotlinNativeTargetsChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.DisabledCinteropCommonizationInHmppProjectChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.DisabledNativeTargetsChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.DuplicateSourceSetChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.ExperimentalTryNextUsageChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.IncorrectNativeDependenciesChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.InternalGradlePropertiesUsageChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.JsEnvironmentChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.KotlinSourceSetTreeDependsOnMismatchChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.KotlinTargetAlreadyDeclaredChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.MissingNativeStdlibChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.NoKotlinTargetsDeclaredChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.PreHmppDependenciesUsageChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.UnusedSourceSetsChecker -import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.WasmSourceSetsNotFoundChecker +import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnosticsSetupAction +import org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers.* +import org.jetbrains.kotlin.gradle.plugin.ide.IdeMultiplatformImportActionSetupAction import org.jetbrains.kotlin.gradle.plugin.ide.IdeMultiplatformImportSetupAction import org.jetbrains.kotlin.gradle.plugin.ide.IdeResolveDependenciesTaskSetupAction import org.jetbrains.kotlin.gradle.plugin.mpp.* -import org.jetbrains.kotlin.gradle.plugin.mpp.GlobalProjectStructureMetadataStorageSetupAction -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinMultiplatformTargetPresetAction -import org.jetbrains.kotlin.gradle.plugin.mpp.MultiplatformPublishingSetupAction -import org.jetbrains.kotlin.gradle.plugin.mpp.SyncLanguageSettingsWithKotlinExtensionSetupAction -import org.jetbrains.kotlin.gradle.plugin.mpp.UserDefinedAttributesSetupAction import org.jetbrains.kotlin.gradle.plugin.mpp.apple.AddBuildListenerForXCodeSetupAction import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.* -import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCompilationProcessorSideEffect -import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCompilationSideEffect -import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCreateResourcesTaskSideEffect -import org.jetbrains.kotlin.gradle.plugin.mpp.compilationImpl.KotlinCreateSourcesJarTaskSideEffect import org.jetbrains.kotlin.gradle.plugin.mpp.internal.DeprecatedMppGradlePropertiesMigrationSetupAction import org.jetbrains.kotlin.gradle.plugin.sources.KotlinMultiplatformSourceSetSetupAction import org.jetbrains.kotlin.gradle.plugin.sources.LanguageSettingsSetupAction import org.jetbrains.kotlin.gradle.plugin.statistics.MultiplatformBuildStatsReportSetupAction import org.jetbrains.kotlin.gradle.scripting.internal.ScriptingGradleSubpluginSetupAction import org.jetbrains.kotlin.gradle.targets.* -import org.jetbrains.kotlin.gradle.targets.ConfigureBuildSideEffect -import org.jetbrains.kotlin.gradle.targets.CreateArtifactsSideEffect -import org.jetbrains.kotlin.gradle.targets.CreateDefaultCompilationsSideEffect -import org.jetbrains.kotlin.gradle.targets.CreateTargetConfigurationsSideEffect -import org.jetbrains.kotlin.gradle.targets.KotlinTargetSideEffect -import org.jetbrains.kotlin.gradle.targets.NativeForwardImplementationToApiElementsSideEffect import org.jetbrains.kotlin.gradle.targets.js.npm.AddNpmDependencyExtensionProjectSetupAction import org.jetbrains.kotlin.gradle.targets.metadata.KotlinMetadataTargetSetupAction import org.jetbrains.kotlin.gradle.targets.native.ConfigureFrameworkExportSideEffect @@ -109,6 +69,7 @@ internal fun Project.registerKotlinPluginExtensions() { register(project, AddBuildListenerForXCodeSetupAction) register(project, CreateFatFrameworksSetupAction) register(project, KotlinRegisterCompilationArchiveTasksExtension) + register(project, IdeMultiplatformImportActionSetupAction) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/IdeMultiplatformImportActionTest.kt b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/IdeMultiplatformImportActionTest.kt new file mode 100644 index 00000000000..b7d7d413385 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/functionalTest/kotlin/org/jetbrains/kotlin/gradle/unitTests/IdeMultiplatformImportActionTest.kt @@ -0,0 +1,82 @@ +/* + * 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") + +package org.jetbrains.kotlin.gradle.unitTests + +import org.jetbrains.kotlin.gradle.dsl.multiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.ide.IdeMultiplatformImportAction +import org.jetbrains.kotlin.gradle.util.applyMultiplatformPlugin +import org.jetbrains.kotlin.gradle.util.buildProject +import org.jetbrains.kotlin.gradle.util.buildProjectWithMPP +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue +import kotlin.test.fail + +class IdeMultiplatformImportActionTest { + + @Test + fun `test - action is not launched when not in idea sync`() { + val project = buildProjectWithMPP() + /* Register arbitrary target */ + project.multiplatformExtension.jvm() + + IdeMultiplatformImportAction.extensionPoint.register(project) { + fail("This action is not expected to be executed") + } + + project.evaluate() + } + + @Test + fun `test - action is launched in idea sync`() = runInIdeSyncMode { + System.setProperty("idea.sync.active", "true") + + val project = buildProject { + applyMultiplatformPlugin() + } + + /* Register arbitrary target */ + project.multiplatformExtension.jvm() + + val executedAction1 = AtomicBoolean(false) + val executedAction2 = AtomicBoolean(false) + + IdeMultiplatformImportAction.extensionPoint.register(project) { + assertFalse(executedAction1.getAndSet(true)) + } + + IdeMultiplatformImportAction.extensionPoint.register(project) { + assertFalse(executedAction2.getAndSet(true)) + } + + project.evaluate() + assertTrue(executedAction1.get(), "Expected action 1 to be executed") + assertTrue(executedAction2.get(), "Expected action 2 to be executed") + } + + /** + * Will swap out the System property 'idea.sync.active' to emulate IDE sync. + * This method will enter the 'System properties monitor' to block any other thread from reading + * System properties while this [block] is executing + */ + private fun runInIdeSyncMode(block: () -> Unit) = synchronized(System.getProperties()) { + val isIdeaSyncActiveKey = "idea.sync.active" + val previousValue = System.getProperty(isIdeaSyncActiveKey) + try { + System.setProperty(isIdeaSyncActiveKey, "true") + block() + } finally { + if (previousValue != null) { + System.setProperty(isIdeaSyncActiveKey, previousValue) + } else { + System.clearProperty(isIdeaSyncActiveKey) + } + } + } +} \ No newline at end of file