[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
This commit is contained in:
Sebastian Sellmair
2023-12-11 12:14:17 +01:00
committed by Space Team
parent dada4a60bd
commit 4c7ad7eeb5
6 changed files with 168 additions and 44 deletions
@@ -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;
@@ -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.
@@ -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<MyExternalTarget> {
* 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<IdeMultiplatformImportAction>()
}
}
internal val IdeMultiplatformImportActionSetupAction = KotlinProjectSetupCoroutine {
if (!isInIdeaSync) return@KotlinProjectSetupCoroutine
KotlinPluginLifecycle.Stage.ReadyForExecution.await()
IdeMultiplatformImportAction.extensionPoint[this].forEach { action ->
action.prepareIdeImport(this)
}
}
@@ -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) }
@@ -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)
}
}
@@ -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)
}
}
}
}