From b925569a6338f1a713bef5815a28f27528ec693c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Smolarek?= <34063647+Razz4780@users.noreply.github.com> Date: Mon, 21 Oct 2024 16:58:02 +0200 Subject: [PATCH] Add support for Bazel (#292) * Bumped platform version to 241, added execution listener for Bazel runs * Changelog entry * Remove unused import * CR suggestions * Update IDE version in test --- build.gradle.kts | 1 + changelog.d/207.added.md | 1 + gradle.properties | 2 +- .../metalbear/mirrord/MirrordExecManager.kt | 22 +-- modules/products/bazel/build.gradle.kts | 25 +++ .../products/bazel/BazelExecutionListener.kt | 154 ++++++++++++++++++ modules/products/goland/build.gradle.kts | 2 +- modules/products/idea/build.gradle.kts | 2 +- modules/products/pycharm/build.gradle.kts | 2 +- .../rider/RiderPatchCommandLineExtension.kt | 30 +--- modules/products/rubymine/build.gradle.kts | 2 +- modules/products/tomcat/build.gradle.kts | 2 +- .../tomcat/TomcatExecutionListener.kt | 8 +- settings.gradle.kts | 3 +- src/main/resources/META-INF/mirrord-bazel.xml | 7 + src/main/resources/META-INF/plugin.xml | 3 +- .../metalbear/mirrord/MirrordPluginTest.kt | 2 +- 17 files changed, 217 insertions(+), 51 deletions(-) create mode 100644 changelog.d/207.added.md create mode 100644 modules/products/bazel/build.gradle.kts create mode 100644 modules/products/bazel/src/main/kotlin/com/metalbear/mirrord/products/bazel/BazelExecutionListener.kt create mode 100644 src/main/resources/META-INF/mirrord-bazel.xml diff --git a/build.gradle.kts b/build.gradle.kts index 482681be..6247475c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -45,6 +45,7 @@ dependencies { implementation(project(":mirrord-products-nodejs")) implementation(project(":mirrord-products-rider")) implementation(project(":mirrord-products-tomcat")) + implementation(project(":mirrord-products-bazel")) testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion") testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion") testImplementation("com.intellij.remoterobot:ide-launcher:0.11.19.414") diff --git a/changelog.d/207.added.md b/changelog.d/207.added.md new file mode 100644 index 00000000..a702ee76 --- /dev/null +++ b/changelog.d/207.added.md @@ -0,0 +1 @@ +Added support for Bazel run configurations. \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 3346ad4c..4b08ca89 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ pluginName = mirrord # SemVer format -> https://semver.org pluginVersion = 3.62.1 -platformVersion = 2022.3.2 +platformVersion = 2024.1 # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 diff --git a/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt b/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt index a9f04b09..dae50b7f 100644 --- a/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt +++ b/modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordExecManager.kt @@ -8,7 +8,6 @@ import com.intellij.openapi.components.service import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.util.SystemInfo -import com.intellij.util.alsoIfNull /** * Functions to be called when one of our entry points to the program is called - when process is @@ -169,16 +168,17 @@ class MirrordExecManager(private val service: MirrordProjectService) { MirrordLogger.logger.debug("target not selected, showing dialog") chooseTarget(cli, wslDistribution, configPath, mirrordApi) - .takeUnless { it == MirrordExecDialog.targetlessTargetName } - .alsoIfNull { - MirrordLogger.logger.info("No target specified - running targetless") - service.notifier.notification( - "No target specified, mirrord running targetless.", - NotificationType.INFORMATION - ) - .withDontShowAgain(MirrordSettingsState.NotificationId.RUNNING_TARGETLESS) - .fire() - } + .takeUnless { it == MirrordExecDialog.targetlessTargetName } ?: run { + MirrordLogger.logger.info("No target specified - running targetless") + service.notifier.notification( + "No target specified, mirrord running targetless.", + NotificationType.INFORMATION + ) + .withDontShowAgain(MirrordSettingsState.NotificationId.RUNNING_TARGETLESS) + .fire() + + null + } } else { null } diff --git a/modules/products/bazel/build.gradle.kts b/modules/products/bazel/build.gradle.kts new file mode 100644 index 00000000..b48a2383 --- /dev/null +++ b/modules/products/bazel/build.gradle.kts @@ -0,0 +1,25 @@ +fun properties(key: String) = project.findProperty(key).toString() + +plugins { + // Java support + id("java") + // Kotlin support + id("org.jetbrains.kotlin.jvm") version "1.8.22" + // Gradle IntelliJ Plugin + id("org.jetbrains.intellij") version "1.+" +} + +tasks { + buildSearchableOptions { + enabled = false + } +} + +intellij { + version.set(properties("platformVersion")) + plugins.set(listOf("com.google.idea.bazel.ijwb:2024.09.24.0.2-api-version-241")) +} + +dependencies { + implementation(project(":mirrord-core")) +} diff --git a/modules/products/bazel/src/main/kotlin/com/metalbear/mirrord/products/bazel/BazelExecutionListener.kt b/modules/products/bazel/src/main/kotlin/com/metalbear/mirrord/products/bazel/BazelExecutionListener.kt new file mode 100644 index 00000000..ea64842e --- /dev/null +++ b/modules/products/bazel/src/main/kotlin/com/metalbear/mirrord/products/bazel/BazelExecutionListener.kt @@ -0,0 +1,154 @@ +package com.metalbear.mirrord.products.bazel + +import com.google.idea.blaze.base.run.BlazeCommandRunConfiguration +import com.google.idea.blaze.base.run.state.BlazeCommandRunConfigurationCommonState +import com.google.idea.blaze.base.scope.BlazeContext +import com.google.idea.blaze.base.settings.Blaze +import com.intellij.execution.ExecutionListener +import com.intellij.execution.process.ProcessHandler +import com.intellij.execution.runners.ExecutionEnvironment +import com.intellij.execution.target.createEnvironmentRequest +import com.intellij.execution.wsl.target.WslTargetEnvironmentRequest +import com.intellij.notification.NotificationType +import com.intellij.openapi.components.service +import com.intellij.openapi.util.SystemInfo +import com.metalbear.mirrord.MirrordLogger +import com.metalbear.mirrord.MirrordProjectService +import java.util.concurrent.ConcurrentHashMap + +data class SavedConfigData(val envVars: Map, val bazelPath: String?) + +class BazelExecutionListener : ExecutionListener { + /** + * Preserves original configuration for active Bazel runs (user environment variables and Bazel binary path) + */ + private val savedEnvs: ConcurrentHashMap = ConcurrentHashMap() + + /** + * Tries to unwrap Bazel-specific state from generic execution environment. + */ + private fun getBlazeConfigurationState(env: ExecutionEnvironment): BlazeCommandRunConfigurationCommonState? { + val runProfile = env.runProfile as? BlazeCommandRunConfiguration ?: return null + val state = runProfile.handler.state as? BlazeCommandRunConfigurationCommonState + return state + } + + override fun processStartScheduled(executorId: String, env: ExecutionEnvironment) { + val service = env.project.service() + + MirrordLogger.logger.debug("[${this.javaClass.name}] processStartScheduled: $executorId $env") + + val state = getBlazeConfigurationState(env) ?: run { + MirrordLogger.logger.debug("[${this.javaClass.name}] processStartScheduled: Bazel not detected") + super.processStartScheduled(executorId, env) + return + } + MirrordLogger.logger.debug("[${this.javaClass.name}] processStartScheduled: got config state $state") + + MirrordLogger.logger.debug("[${this.javaClass.name}] processStartScheduled: wsl check") + @Suppress("UnstableApiUsage") // `createEnvironmentRequest` + val wsl = when (val request = createEnvironmentRequest(env.runProfile, env.project)) { + is WslTargetEnvironmentRequest -> request.configuration.distribution!! + else -> null + } + + val originalEnv = state.userEnvVarsState.data.envs + MirrordLogger.logger.debug("[${this.javaClass.name}] processStartScheduled: found ${originalEnv.size} original env variables") + + val binaryToPatch = if (SystemInfo.isMac) { + state.blazeBinaryState.blazeBinary?.let { + MirrordLogger.logger.debug("[${this.javaClass.name}] processStartScheduled: found Bazel binary path in the config: $it") + it + } ?: run { + // Bazel binary path may be missing from the config. + // This is the logic that Bazel plugin uses to find global Bazel binary. + val global = Blaze.getBuildSystemProvider(env.project).buildSystem.getBuildInvoker(env.project, BlazeContext.create()).binaryPath + MirrordLogger.logger.debug("[${this.javaClass.name} processStartScheduled: found global Bazel binary path: $global") + global + } + } else { + null + } + + try { + service.execManager.wrapper("bazel", originalEnv).apply { + this.wsl = wsl + this.executable = binaryToPatch + }.start()?.let { executionInfo -> + MirrordLogger.logger.debug("[${this.javaClass.name}] processStartScheduled: adding ${executionInfo.environment.size} environment variables") + var envVars = originalEnv + executionInfo.environment + executionInfo.envToUnset?.let { envToUnset -> + envVars = envVars.filter { + !envToUnset.contains(it.key) + } + } + state.userEnvVarsState.setEnvVars(envVars) + + val originalBinary = state.blazeBinaryState.blazeBinary + if (SystemInfo.isMac) { + executionInfo.patchedPath?.let { + MirrordLogger.logger.debug("[${this.javaClass.name}] processStartScheduled: patchedPath is not null: $it, meaning original was SIP") + state.blazeBinaryState.blazeBinary = it + } ?: run { + MirrordLogger.logger.debug("[${this.javaClass.name}] processStartScheduled: isMac, but not patching SIP (no patched path returned by the CLI).") + } + } + + savedEnvs[executorId] = SavedConfigData(originalEnv, originalBinary) + } + } catch (e: Throwable) { + MirrordLogger.logger.debug("[${this.javaClass.name}] processStartScheduled: exception catched: ", e) + // Error notifications were already fired. + // We can't abort the execution here, so we let the app run without mirrord. + service.notifier.notifySimple( + "Cannot abort run due to platform limitations, running without mirrord", + NotificationType.WARNING + ) + } + + super.processStartScheduled(executorId, env) + } + + /** + * Restores original configuration after Bazel run has ended. + */ + private fun restoreConfig(executorId: String, configState: BlazeCommandRunConfigurationCommonState) { + MirrordLogger.logger.debug("[${this.javaClass.name}] restoreEnv: $executorId $configState") + + val saved = savedEnvs.remove(executorId) ?: run { + MirrordLogger.logger.debug("[${this.javaClass.name}] restoreConfig: no saved env found") + return + } + + MirrordLogger.logger.debug("[${this.javaClass.name}] restoreConfig: found ${saved.envVars.size} saved original variables") + configState.userEnvVarsState.setEnvVars(saved.envVars) + + if (SystemInfo.isMac) { + MirrordLogger.logger.debug("[${this.javaClass.name}] restoreConfig: found saved original Bazel path ${saved.bazelPath}") + configState.blazeBinaryState.blazeBinary = saved.bazelPath + } + } + + override fun processTerminating(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) { + MirrordLogger.logger.debug("[${this.javaClass.name}] processTerminating: $executorId $env $handler") + + val state = getBlazeConfigurationState(env) ?: run { + MirrordLogger.logger.debug("[${this.javaClass.name}] processTerminating: Bazel not detected") + return + } + + restoreConfig(executorId, state) + + super.processTerminating(executorId, env, handler) + } + + override fun processNotStarted(executorId: String, env: ExecutionEnvironment) { + MirrordLogger.logger.debug("[${this.javaClass.name}] processNotStarted (noop): $executorId $env") + super.processNotStarted(executorId, env) + } + + override fun processStarted(executorId: String, env: ExecutionEnvironment, handler: ProcessHandler) { + MirrordLogger.logger.debug("[${this.javaClass.name}] processStarted (noop): $executorId $env $handler") + super.processStarted(executorId, env, handler) + } +} diff --git a/modules/products/goland/build.gradle.kts b/modules/products/goland/build.gradle.kts index e1f3039f..770281b2 100644 --- a/modules/products/goland/build.gradle.kts +++ b/modules/products/goland/build.gradle.kts @@ -17,7 +17,7 @@ tasks { intellij { version.set(properties("platformVersion")) - plugins.set(listOf("org.jetbrains.plugins.go:223.7571.182")) + plugins.set(listOf("org.jetbrains.plugins.go:241.14494.240")) } dependencies { diff --git a/modules/products/idea/build.gradle.kts b/modules/products/idea/build.gradle.kts index 0ef575eb..fcd51425 100644 --- a/modules/products/idea/build.gradle.kts +++ b/modules/products/idea/build.gradle.kts @@ -18,7 +18,7 @@ tasks { intellij { version.set(properties("platformVersion")) - plugins.set(listOf("java", "gradle", "maven", "org.intellij.scala:2022.3.8")) + plugins.set(listOf("java", "gradle", "maven", "org.intellij.scala:2024.1.25")) } dependencies { diff --git a/modules/products/pycharm/build.gradle.kts b/modules/products/pycharm/build.gradle.kts index c48b246e..ec596624 100644 --- a/modules/products/pycharm/build.gradle.kts +++ b/modules/products/pycharm/build.gradle.kts @@ -17,7 +17,7 @@ tasks { intellij { version.set(properties("platformVersion")) - plugins.set(listOf("PythonCore:223.8617.56")) + plugins.set(listOf("PythonCore:241.14494.240")) } dependencies { diff --git a/modules/products/rider/src/main/kotlin/com/metalbear/mirrord/products/rider/RiderPatchCommandLineExtension.kt b/modules/products/rider/src/main/kotlin/com/metalbear/mirrord/products/rider/RiderPatchCommandLineExtension.kt index c66b4822..5d7941f0 100644 --- a/modules/products/rider/src/main/kotlin/com/metalbear/mirrord/products/rider/RiderPatchCommandLineExtension.kt +++ b/modules/products/rider/src/main/kotlin/com/metalbear/mirrord/products/rider/RiderPatchCommandLineExtension.kt @@ -1,7 +1,3 @@ -@file:Suppress("UnstableApiUsage") - -// ^^ `createEnvironmentRequest` used to get the env references unstable API - package com.metalbear.mirrord.products.rider import com.intellij.execution.RunManager @@ -25,6 +21,7 @@ class RiderPatchCommandLineExtension : PatchCommandLineExtension { val service = project.service() val wsl = RunManager.getInstance(project).selectedConfiguration?.configuration?.let { + @Suppress("UnstableApiUsage") // `createEnvironmentRequest` when (val request = createEnvironmentRequest(it, project)) { is WslTargetEnvironmentRequest -> request.configuration.distribution!! else -> null @@ -44,34 +41,13 @@ class RiderPatchCommandLineExtension : PatchCommandLineExtension { } } - override fun patchDebugCommandLine( - lifetime: Lifetime, - workerRunInfo: WorkerRunInfo, - project: Project - ): Promise { + override fun patchDebugCommandLine(lifetime: Lifetime, workerRunInfo: WorkerRunInfo, processInfo: ProcessInfo?, project: Project): Promise { patchCommandLine(workerRunInfo.commandLine, project) workerRunInfo.commandLine.withEnvironment("MIRRORD_DETECT_DEBUGGER_PORT", "resharper") return resolvedPromise(workerRunInfo) } - /** - * This method is the one that overrides in newer Rider versions. - */ - @Suppress("unused", "unused_parameter") - fun patchDebugCommandLine( - lifetime: Lifetime, - workerRunInfo: WorkerRunInfo, - processInfo: ProcessInfo?, - project: Project - ): Promise { - return patchDebugCommandLine(lifetime, workerRunInfo, project) - } - - override fun patchRunCommandLine( - commandLine: GeneralCommandLine, - dotNetRuntime: DotNetRuntime, - project: Project - ): ProcessListener? { + override fun patchRunCommandLine(commandLine: GeneralCommandLine, dotNetRuntime: DotNetRuntime, project: Project): ProcessListener? { patchCommandLine(commandLine, project) return null } diff --git a/modules/products/rubymine/build.gradle.kts b/modules/products/rubymine/build.gradle.kts index 6a2eff2f..e67d14d1 100644 --- a/modules/products/rubymine/build.gradle.kts +++ b/modules/products/rubymine/build.gradle.kts @@ -17,7 +17,7 @@ tasks { intellij { version.set(properties("platformVersion")) - plugins.set(listOf("org.jetbrains.plugins.ruby:223.8617.56")) + plugins.set(listOf("org.jetbrains.plugins.ruby:241.14494.240")) } dependencies { diff --git a/modules/products/tomcat/build.gradle.kts b/modules/products/tomcat/build.gradle.kts index 98306961..4907b3d6 100644 --- a/modules/products/tomcat/build.gradle.kts +++ b/modules/products/tomcat/build.gradle.kts @@ -18,7 +18,7 @@ tasks { intellij { version.set(properties("platformVersion")) type.set("IU") - plugins.set(listOf("Tomcat:223.7571.182", "com.intellij.javaee.app.servers.integration")) + plugins.set(listOf("Tomcat:241.14494.158", "com.intellij.javaee.app.servers.integration")) } dependencies { diff --git a/modules/products/tomcat/src/main/kotlin/com/metalbear/mirrord/products/tomcat/TomcatExecutionListener.kt b/modules/products/tomcat/src/main/kotlin/com/metalbear/mirrord/products/tomcat/TomcatExecutionListener.kt index 17fc1ce6..e6180cea 100644 --- a/modules/products/tomcat/src/main/kotlin/com/metalbear/mirrord/products/tomcat/TomcatExecutionListener.kt +++ b/modules/products/tomcat/src/main/kotlin/com/metalbear/mirrord/products/tomcat/TomcatExecutionListener.kt @@ -135,6 +135,8 @@ class TomcatExecutionListener : ExecutionListener { } override fun processStartScheduled(executorId: String, env: ExecutionEnvironment) { + val service = env.project.service() + MirrordLogger.logger.debug("[${this.javaClass.name}] processStartScheduled: $executorId $env") val config = getConfig(env) ?: run { @@ -147,8 +149,6 @@ class TomcatExecutionListener : ExecutionListener { var envVars = config.envVariables val envVarsMap = envVars.map { it.NAME to it.VALUE }.toMap() - val service = env.project.service() - MirrordLogger.logger.debug("[${this.javaClass.name}] processStartScheduled: wsl check") val wsl = when (val request = createEnvironmentRequest(env.runProfile, env.project)) { is WslTargetEnvironmentRequest -> request.configuration.distribution!! @@ -171,7 +171,7 @@ class TomcatExecutionListener : ExecutionListener { } try { - service.execManager.wrapper("idea", envVarsMap).apply { + service.execManager.wrapper("tomcat", envVarsMap).apply { this.wsl = wsl this.executable = scriptAndArgs?.command }.start()?.let { executionInfo -> @@ -260,7 +260,7 @@ class TomcatExecutionListener : ExecutionListener { } override fun processNotStarted(executorId: String, env: ExecutionEnvironment) { - MirrordLogger.logger.debug("[${this.javaClass.name}] processStarted (noop): $executorId $env") + MirrordLogger.logger.debug("[${this.javaClass.name}] processNotStarted (noop): $executorId $env") super.processNotStarted(executorId, env) } diff --git a/settings.gradle.kts b/settings.gradle.kts index b8f10ac8..700c7783 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -8,7 +8,8 @@ include( "modules/products/rubymine", "modules/products/nodejs", "modules/products/rider", - "modules/products/tomcat" + "modules/products/tomcat", + "modules/products/bazel" ) // Rename modules to mirrord-, I think this is required IntelliJ wise. diff --git a/src/main/resources/META-INF/mirrord-bazel.xml b/src/main/resources/META-INF/mirrord-bazel.xml new file mode 100644 index 00000000..20515c6f --- /dev/null +++ b/src/main/resources/META-INF/mirrord-bazel.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 20ac316e..82bec638 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -11,7 +11,7 @@ Read more here. ]]> - + com.intellij.modules.lang @@ -74,5 +74,6 @@ NodeJS com.intellij.modules.rider Tomcat + com.google.idea.bazel.ijwb com.intellij.modules.json diff --git a/src/test/kotlin/com/metalbear/mirrord/MirrordPluginTest.kt b/src/test/kotlin/com/metalbear/mirrord/MirrordPluginTest.kt index 432c4214..5f9c2694 100644 --- a/src/test/kotlin/com/metalbear/mirrord/MirrordPluginTest.kt +++ b/src/test/kotlin/com/metalbear/mirrord/MirrordPluginTest.kt @@ -59,7 +59,7 @@ internal class MirrordPluginTest { Ide.PYCHARM_COMMUNITY, tmpDir, Ide.BuildType.RELEASE, - "2022.3.2" + "2024.1" ) // IdeLauncher fails when the IDE bin directory does not contain exactly one `.vmoptions` file for 64 arch.