diff --git a/CHANGELOG.md b/CHANGELOG.md index 28e204597c..7771141611 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - Configure all tasks that extend task classes instead of just those created by the plugin - Make JbrResolver prefer Gradle javaToolchains by `JetBrains s.r.o`, if available. Only otherwise start fetching and running a new one. +- Support for Kotlin Coroutines debugging ### Changed - Disabled caching for `BuildPluginTask` diff --git a/src/main/kotlin/org/jetbrains/intellij/IntelliJPlugin.kt b/src/main/kotlin/org/jetbrains/intellij/IntelliJPlugin.kt index 3a847a09f8..0e98ca4f04 100644 --- a/src/main/kotlin/org/jetbrains/intellij/IntelliJPlugin.kt +++ b/src/main/kotlin/org/jetbrains/intellij/IntelliJPlugin.kt @@ -129,6 +129,7 @@ import java.time.LocalDate import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.* +import kotlin.io.path.exists abstract class IntelliJPlugin : Plugin { @@ -907,6 +908,7 @@ abstract class IntelliJPlugin : Plugin { ) { val taskContext = logCategory() val prepareSandboxTaskProvider = project.tasks.named(prepareSandBoxTaskName) + val initializeIntelliJPluginTaskProvider = project.tasks.named(INITIALIZE_INTELLIJ_PLUGIN_TASK_NAME) val pluginIds = sourcePluginXmlFiles(project).mapNotNull { parsePluginXml(it, taskContext)?.id } ideDir.convention(ideaDependencyProvider.map { @@ -939,6 +941,9 @@ abstract class IntelliJPlugin : Plugin { ideDir = ideDir.orNull, ).toString() }) + coroutinesJavaAgentPath.convention(initializeIntelliJPluginTaskProvider.flatMap { + it.coroutinesJavaAgentPath + }) } private fun configureJarSearchableOptionsTask(project: Project) { @@ -1177,6 +1182,10 @@ abstract class IntelliJPlugin : Plugin { val instrumentedTestCodeOutputsProvider = project.provider { project.files(instrumentedTestCodeTaskProvider.map { it.outputDir.asFile }) } + val initializeIntellijPluginTaskProvider = project.tasks.named(INITIALIZE_INTELLIJ_PLUGIN_TASK_NAME) + val coroutinesJavaAgentPathProvider = initializeIntellijPluginTaskProvider.flatMap { + it.coroutinesJavaAgentPath + } val testTasks = project.tasks.withType() val pluginIds = sourcePluginXmlFiles(project).mapNotNull { parsePluginXml(it, context)?.id } @@ -1263,7 +1272,7 @@ abstract class IntelliJPlugin : Plugin { classpath = instrumentedCodeOutputsProvider.get() + instrumentedTestCodeOutputsProvider.get() + classpath testClassesDirs = instrumentedTestCodeOutputsProvider.get() + testClassesDirs - jvmArgumentProviders.add(IntelliJPlatformArgumentProvider(ideDirProvider.get(), this)) + jvmArgumentProviders.add(IntelliJPlatformArgumentProvider(ideDirProvider.get(), coroutinesJavaAgentPathProvider.get(), this)) doFirst { classpath += ideaDependencyLibrariesProvider.get() + @@ -1413,7 +1422,7 @@ abstract class IntelliJPlugin : Plugin { ) onlyIf { - // Workaround for Gradle 7.x to don't fail on "An input file was expected to be present but it doesn't exist." + // Workaround for Gradle 7.x to don't fail on "An input file was expected to be present, but it doesn't exist." inputArchiveFile.isSpecified } dependsOn(SIGN_PLUGIN_TASK_NAME) @@ -1544,6 +1553,7 @@ abstract class IntelliJPlugin : Plugin { } } + @Suppress("UnstableApiUsage") private fun configureProcessResources(project: Project) { info(context, "Configuring resources task") val patchPluginXmlTaskProvider = project.tasks.named(PATCH_PLUGIN_XML_TASK_NAME) @@ -1565,12 +1575,15 @@ abstract class IntelliJPlugin : Plugin { selfUpdateCheck.convention(project.provider { project.isBuildFeatureEnabled(SELF_UPDATE_CHECK) }) - lockFile.convention(project.provider { - temporaryDir.resolve(LocalDate.now().toString()) + selfUpdateLockPath.convention(project.provider { + temporaryDir.toPath().resolve(LocalDate.now().toString()) + }) + coroutinesJavaAgentPath.convention(project.provider { + temporaryDir.toPath().resolve("coroutines-javaagent.jar") }) onlyIf { - !lockFile.get().exists() + !selfUpdateLockPath.get().exists() || !coroutinesJavaAgentPath.get().exists() } } } diff --git a/src/main/kotlin/org/jetbrains/intellij/propertyProviders/IntelliJPlatformArgumentProvider.kt b/src/main/kotlin/org/jetbrains/intellij/propertyProviders/IntelliJPlatformArgumentProvider.kt index 4c602a9368..f1b32f97b0 100644 --- a/src/main/kotlin/org/jetbrains/intellij/propertyProviders/IntelliJPlatformArgumentProvider.kt +++ b/src/main/kotlin/org/jetbrains/intellij/propertyProviders/IntelliJPlatformArgumentProvider.kt @@ -16,6 +16,7 @@ import java.nio.file.Path class IntelliJPlatformArgumentProvider( @InputDirectory @PathSensitive(RELATIVE) val ideDirectory: Path, + @InputDirectory @PathSensitive(RELATIVE) val coroutinesJavaAgentPath: Path, private val options: JavaForkOptions, ) : CommandLineArgumentProvider { @@ -36,6 +37,8 @@ class IntelliJPlatformArgumentProvider( ?.removePrefix("../") ?.let { ideDirectory.resolve(it).readLines() } .orEmpty() + .filter { !it.contains("kotlinx.coroutines.debug=off") } + .let { it + "-javaagent:${coroutinesJavaAgentPath}" } private val additionalJvmArguments get() = productInfo diff --git a/src/main/kotlin/org/jetbrains/intellij/tasks/InitializeIntelliJPluginTask.kt b/src/main/kotlin/org/jetbrains/intellij/tasks/InitializeIntelliJPluginTask.kt index 08fff07759..04f72789e4 100644 --- a/src/main/kotlin/org/jetbrains/intellij/tasks/InitializeIntelliJPluginTask.kt +++ b/src/main/kotlin/org/jetbrains/intellij/tasks/InitializeIntelliJPluginTask.kt @@ -3,6 +3,7 @@ package org.jetbrains.intellij.tasks import com.jetbrains.plugin.structure.base.utils.create +import com.jetbrains.plugin.structure.base.utils.outputStream import org.gradle.api.DefaultTask import org.gradle.api.provider.Property import org.gradle.api.tasks.Internal @@ -14,7 +15,10 @@ import org.jetbrains.intellij.IntelliJPluginConstants.PLUGIN_GROUP_NAME import org.jetbrains.intellij.IntelliJPluginConstants.PLUGIN_ID import org.jetbrains.intellij.IntelliJPluginConstants.PLUGIN_NAME import org.jetbrains.intellij.utils.LatestVersionResolver -import java.io.File +import java.nio.file.Path +import java.util.jar.JarOutputStream +import java.util.jar.Manifest +import kotlin.io.path.exists /** * Initializes the Gradle IntelliJ Plugin and performs various checks, like if the plugin is up to date. @@ -29,7 +33,10 @@ abstract class InitializeIntelliJPluginTask : DefaultTask() { abstract val selfUpdateCheck: Property @get:Internal - abstract val lockFile: Property + abstract val selfUpdateLockPath: Property + + @get:Internal + abstract val coroutinesJavaAgentPath: Property init { group = PLUGIN_GROUP_NAME @@ -41,13 +48,14 @@ abstract class InitializeIntelliJPluginTask : DefaultTask() { @TaskAction fun initialize() { checkPluginVersion() + createCoroutinesJavaAgentFile() } /** - * Checks if the plugin is up to date. + * Checks if the plugin is up-to-date. */ private fun checkPluginVersion() { - if (!selfUpdateCheck.get() || offline.get()) { + if (!selfUpdateCheck.get() || selfUpdateLockPath.get().exists() || offline.get()) { return } @@ -60,9 +68,30 @@ abstract class InitializeIntelliJPluginTask : DefaultTask() { warn(context, "$PLUGIN_NAME is outdated: $version. Update `$PLUGIN_ID` to: $latestVersion") } - lockFile.get().toPath().create() + selfUpdateLockPath.get().create() } catch (e: Exception) { error(context, e.message.orEmpty(), e) } } + + /** + * Creates a Java Agent file for the Coroutines library required to enable coroutines debugging. + */ + private fun createCoroutinesJavaAgentFile() { + if (coroutinesJavaAgentPath.get().exists()) { + return + } + + val manifest = Manifest( + """ + Manifest-Version: 1.0 + Premain-Class: kotlinx.coroutines.debug.AgentPremain + Can-Retransform-Classes: true + Multi-Release: true + + """.trimIndent().byteInputStream() + ) + + JarOutputStream(coroutinesJavaAgentPath.get().outputStream(), manifest).close() + } } diff --git a/src/main/kotlin/org/jetbrains/intellij/tasks/RunIdeBase.kt b/src/main/kotlin/org/jetbrains/intellij/tasks/RunIdeBase.kt index 31b9107a83..bdccfb7860 100755 --- a/src/main/kotlin/org/jetbrains/intellij/tasks/RunIdeBase.kt +++ b/src/main/kotlin/org/jetbrains/intellij/tasks/RunIdeBase.kt @@ -148,6 +148,12 @@ abstract class RunIdeBase : JavaExec() { @get:Internal abstract val projectExecutable: Property + /** + * Represents the path to the coroutines Java agent file. + */ + @get:Internal + abstract val coroutinesJavaAgentPath: Property + private val ideDirPath by lazy { ideDir.get().toPath() } @@ -182,7 +188,7 @@ abstract class RunIdeBase : JavaExec() { @TaskAction override fun exec() { workingDir = projectWorkingDir.get() - jvmArgumentProviders.add(IntelliJPlatformArgumentProvider(ideDir.get().toPath(), this)) + jvmArgumentProviders.add(IntelliJPlatformArgumentProvider(ideDir.get().toPath(), coroutinesJavaAgentPath.get(), this)) configureSystemProperties() configureClasspath()