Skip to content

Commit

Permalink
Add support for Bazel (#292)
Browse files Browse the repository at this point in the history
* Bumped platform version to 241, added execution listener for Bazel runs

* Changelog entry

* Remove unused import

* CR suggestions

* Update IDE version in test
  • Loading branch information
Razz4780 authored Oct 21, 2024
1 parent a0797ec commit b925569
Show file tree
Hide file tree
Showing 17 changed files with 217 additions and 51 deletions.
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
1 change: 1 addition & 0 deletions changelog.d/207.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added support for Bazel run configurations.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
25 changes: 25 additions & 0 deletions modules/products/bazel/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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"))
}
Original file line number Diff line number Diff line change
@@ -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<String, String>, val bazelPath: String?)

class BazelExecutionListener : ExecutionListener {
/**
* Preserves original configuration for active Bazel runs (user environment variables and Bazel binary path)
*/
private val savedEnvs: ConcurrentHashMap<String, SavedConfigData> = 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<MirrordProjectService>()

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)
}
}
2 changes: 1 addition & 1 deletion modules/products/goland/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion modules/products/idea/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion modules/products/pycharm/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -25,6 +21,7 @@ class RiderPatchCommandLineExtension : PatchCommandLineExtension {
val service = project.service<MirrordProjectService>()

val wsl = RunManager.getInstance(project).selectedConfiguration?.configuration?.let {
@Suppress("UnstableApiUsage") // `createEnvironmentRequest`
when (val request = createEnvironmentRequest(it, project)) {
is WslTargetEnvironmentRequest -> request.configuration.distribution!!
else -> null
Expand All @@ -44,34 +41,13 @@ class RiderPatchCommandLineExtension : PatchCommandLineExtension {
}
}

override fun patchDebugCommandLine(
lifetime: Lifetime,
workerRunInfo: WorkerRunInfo,
project: Project
): Promise<WorkerRunInfo> {
override fun patchDebugCommandLine(lifetime: Lifetime, workerRunInfo: WorkerRunInfo, processInfo: ProcessInfo?, project: Project): Promise<WorkerRunInfo> {
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<WorkerRunInfo> {
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
}
Expand Down
2 changes: 1 addition & 1 deletion modules/products/rubymine/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion modules/products/tomcat/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ class TomcatExecutionListener : ExecutionListener {
}

override fun processStartScheduled(executorId: String, env: ExecutionEnvironment) {
val service = env.project.service<MirrordProjectService>()

MirrordLogger.logger.debug("[${this.javaClass.name}] processStartScheduled: $executorId $env")

val config = getConfig(env) ?: run {
Expand All @@ -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<MirrordProjectService>()

MirrordLogger.logger.debug("[${this.javaClass.name}] processStartScheduled: wsl check")
val wsl = when (val request = createEnvironmentRequest(env.runProfile, env.project)) {
is WslTargetEnvironmentRequest -> request.configuration.distribution!!
Expand All @@ -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 ->
Expand Down Expand Up @@ -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)
}

Expand Down
3 changes: 2 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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-<module>, I think this is required IntelliJ wise.
Expand Down
7 changes: 7 additions & 0 deletions src/main/resources/META-INF/mirrord-bazel.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<idea-plugin>
<projectListeners>
<listener class="com.metalbear.mirrord.products.bazel.BazelExecutionListener"
topic="com.intellij.execution.ExecutionListener"/>
</projectListeners>
</idea-plugin>

Loading

0 comments on commit b925569

Please sign in to comment.