From 543b409f9011db007b337b79cce1e50f2dc8e6f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Smolarek?= <34063647+Razz4780@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:01:08 +0200 Subject: [PATCH] Conditional Go Delve patch (#288) * Checking DLV version in Goland config extension * Changelog entry * Style + docs * Lint * Throwing exception when bundled delve is not found. --- changelog.d/284.changed.md | 1 + modules/products/goland/build.gradle.kts | 1 + .../goland/GolandRunConfigurationExtension.kt | 110 ++++++++++++++---- 3 files changed, 89 insertions(+), 23 deletions(-) create mode 100644 changelog.d/284.changed.md diff --git a/changelog.d/284.changed.md b/changelog.d/284.changed.md new file mode 100644 index 00000000..9580706d --- /dev/null +++ b/changelog.d/284.changed.md @@ -0,0 +1 @@ +Go Delve is now patched only if its version is older than the one bundled with our plugin. \ No newline at end of file diff --git a/modules/products/goland/build.gradle.kts b/modules/products/goland/build.gradle.kts index 6398fccf..e1f3039f 100644 --- a/modules/products/goland/build.gradle.kts +++ b/modules/products/goland/build.gradle.kts @@ -22,4 +22,5 @@ intellij { dependencies { implementation(project(":mirrord-core")) + implementation("com.github.zafarkhaja:java-semver:0.9.0") } diff --git a/modules/products/goland/src/main/kotlin/com/metalbear/mirrord/products/goland/GolandRunConfigurationExtension.kt b/modules/products/goland/src/main/kotlin/com/metalbear/mirrord/products/goland/GolandRunConfigurationExtension.kt index 37fb0a42..7a4fe1c2 100644 --- a/modules/products/goland/src/main/kotlin/com/metalbear/mirrord/products/goland/GolandRunConfigurationExtension.kt +++ b/modules/products/goland/src/main/kotlin/com/metalbear/mirrord/products/goland/GolandRunConfigurationExtension.kt @@ -1,5 +1,6 @@ package com.metalbear.mirrord.products.goland +import com.github.zafarkhaja.semver.Version import com.goide.execution.GoRunConfigurationBase import com.goide.execution.GoRunningState import com.goide.execution.extension.GoRunConfigurationExtension @@ -10,6 +11,8 @@ import com.intellij.execution.target.TargetedCommandLineBuilder import com.intellij.execution.wsl.target.WslTargetEnvironmentRequest import com.intellij.openapi.components.service import com.intellij.openapi.util.SystemInfo +import com.metalbear.mirrord.MirrordError +import com.metalbear.mirrord.MirrordLogger import com.metalbear.mirrord.MirrordPathManager import com.metalbear.mirrord.MirrordProjectService import java.nio.file.Paths @@ -65,6 +68,10 @@ class GolandRunConfigurationExtension : GoRunConfigurationExtension() { super.patchCommandLine(configuration, runnerSettings, cmdLine, runnerId, state, commandLineType) } + /** + * Patch delve used in the commandline, if necessary + * (debugging on Mac and commandline uses delve version older than ours). + */ override fun patchExecutor( configuration: GoRunConfigurationBase<*>, runnerSettings: RunnerSettings?, @@ -75,35 +82,92 @@ class GolandRunConfigurationExtension : GoRunConfigurationExtension() { ) { val service = configuration.getProject().service() - if (commandLineType == GoRunningState.CommandLineType.RUN && - service.enabled && - SystemInfo.isMac && - state.isDebug - ) { - val delvePath = getCustomDelvePath() - // convert the delve file to an executable - val delveExecutable = Paths.get(delvePath).toFile() - if (delveExecutable.exists()) { - if (!delveExecutable.canExecute()) { - delveExecutable.setExecutable(true) - } - // parameters returns a copy, and we can't modify it so need to reset it. - val patchedParameters = executor.parameters - for (i in 0 until patchedParameters.size) { - // no way to reset the whole commandline, so we just remove each entry. - executor.withoutParameter(0) - val value = patchedParameters[i] - if (value.toPresentableString().endsWith("/dlv", true)) { - patchedParameters[i] = PathParameter(delveExecutable.toString()) - } - } - executor.withParameters(*patchedParameters.toTypedArray()) + if (commandLineType != GoRunningState.CommandLineType.RUN || !service.enabled || !SystemInfo.isMac || !state.isDebug) { + super.patchExecutor(configuration, runnerSettings, executor, runnerId, state, commandLineType) + return + } + + val ourDelvePath = getCustomDelvePath() + val delveExecutable = Paths.get(ourDelvePath).toFile() + if (!delveExecutable.exists()) { + val error = MirrordError( + "Failed to find delve bundled with mirrord plugin ($ourDelvePath", + "This is a bug, please contact us." + ) + error.showHelp(configuration.getProject()) + throw error + } + + if (!delveExecutable.canExecute()) { + delveExecutable.setExecutable(true) + } + + val ourDelveVersion = try { + getDelveVersion(ourDelvePath) + } catch (e: Throwable) { + MirrordLogger.logger.error("Failed to get version delve bundled with mirrord plugin ($ourDelvePath)", e) + super.patchExecutor(configuration, runnerSettings, executor, runnerId, state, commandLineType) + return + } + + // parameters returns a copy, and we can't modify it so need to reset it. + val patchedParameters = executor.parameters + for (i in 0 until patchedParameters.size) { + // no way to reset the whole commandline, so we just remove each entry. + executor.withoutParameter(0) + + val foundDelvePath = patchedParameters[i].toPresentableString() + + if (!foundDelvePath.endsWith("/dlv", true)) { + continue + } + + val delveVersion = try { + getDelveVersion(foundDelvePath) + } catch (e: Throwable) { + MirrordLogger + .logger + .error("Failed to get version of delve found in executor parameters ($foundDelvePath)", e) + continue + } + + if (delveVersion.lessThan(ourDelveVersion)) { + patchedParameters[i] = PathParameter(delveExecutable.toString()) } } + + executor.withParameters(*patchedParameters.toTypedArray()) + super.patchExecutor(configuration, runnerSettings, executor, runnerId, state, commandLineType) } + /** + * Return path to custom delve bundled with mirrord plugin. + */ private fun getCustomDelvePath(): String { return MirrordPathManager.getBinary("dlv", false)!! } + + /** + * Get version of the given delve. + */ + private fun getDelveVersion(path: String): Version { + val process = ProcessBuilder(path, "version") + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.DISCARD) + .start() + + val exitCode = process.waitFor() + if (exitCode != 0) { + throw Exception("dlv version command exited with code $exitCode") + } + + val versionLine = process + .inputStream + .bufferedReader() + .readLines() + .find { it.startsWith("Version: ") } ?: throw Exception("no output line starts with 'Version: '") + + return Version.valueOf(versionLine.removePrefix("Version: ")) + } }