Skip to content

Commit

Permalink
Python via docker (#309)
Browse files Browse the repository at this point in the history
* ??

* Tiny

* Update

* Damn klint

* Changelog

* Update

* klint

* Updated CLI + revert list change

* Update when cli is fetched
  • Loading branch information
DmitryDodzin authored Dec 17, 2024
1 parent ba2e8d7 commit 03987c0
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 26 deletions.
1 change: 1 addition & 0 deletions changelog.d/294.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add support for using python docker interpreter.
87 changes: 87 additions & 0 deletions modules/core/src/main/kotlin/com/metalbear/mirrord/MirrordApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ data class MirrordExecution(
@SerializedName("uses_operator") val usesOperator: Boolean?
)

data class MirrordContainerExecution(
val runtime: String,
@SerializedName("extra_args") val extraArgs: MutableList<String>,
@SerializedName("uses_operator") val usesOperator: Boolean?
)

/**
* Wrapper around Gson for parsing messages from the mirrord binary.
*/
Expand Down Expand Up @@ -243,6 +249,66 @@ class MirrordApi(private val service: MirrordProjectService, private val project
}
}

private class MirrordContainerExtTask(cli: String, projectEnvVars: Map<String, String>?) : MirrordCliTask<MirrordContainerExecution>(cli, "container-ext", null, projectEnvVars) {
override fun compute(project: Project, process: Process, setText: (String) -> Unit): MirrordContainerExecution {
val parser = SafeParser()
val bufferedReader = process.inputStream.reader().buffered()

val warningHandler = MirrordWarningHandler(project.service<MirrordProjectService>())

setText("mirrord is starting...")
for (line in bufferedReader.lines()) {
val message = parser.parse(line, Message::class.java)
when {
message.name == "mirrord preparing to launch" && message.type == MessageType.FinishedTask -> {
val success = message.success
?: throw MirrordError("invalid message received from the mirrord binary")
if (success) {
val innerMessage = message.message
?: throw MirrordError("invalid message received from the mirrord binary")
val executionInfo = parser.parse(innerMessage as String, MirrordContainerExecution::class.java)
setText("mirrord is running")
return executionInfo
}
}

message.type == MessageType.Info -> {
val service = project.service<MirrordProjectService>()
message.message?.let { service.notifier.notifySimple(it as String, NotificationType.INFORMATION) }
}

message.type == MessageType.Warning -> {
message.message?.let { warningHandler.handle(it as String) }
}

message.type == MessageType.IdeMessage -> {
message.message?.run {
val ideMessage = Gson().fromJson(Gson().toJsonTree(this), IdeMessage::class.java)
val service = project.service<MirrordProjectService>()
ideMessage?.handleIdeMessage(service)
}
}

else -> {
var displayMessage = message.name
message.message?.let {
displayMessage += ": $it"
}
setText(displayMessage)
}
}
}

process.waitFor()
if (process.exitValue() != 0) {
val processStdError = process.errorStream.bufferedReader().readText()
throw MirrordError.fromStdErr(processStdError)
} else {
throw MirrordError("invalid output of the mirrord binary")
}
}
}

/**
* Interacts with the `mirrord verify-config [path]` cli command.
*
Expand Down Expand Up @@ -310,6 +376,27 @@ class MirrordApi(private val service: MirrordProjectService, private val project
return result
}

fun containerExec(cli: String, target: String?, configFile: String?, wslDistribution: WSLDistribution?): MirrordContainerExecution {
bumpRunCounter()

val task = MirrordContainerExtTask(cli, projectEnvVars).apply {
this.target = target
this.configFile = configFile
this.wslDistribution = wslDistribution
}

val result = task.run(service.project)
service.notifier.notifySimple("mirrord starting...", NotificationType.INFORMATION)

result.usesOperator?.let { usesOperator ->
if (usesOperator) {
MirrordSettingsState.instance.mirrordState.operatorUsed = true
}
}

return result
}

/**
* Increments the mirrord run counter.
* Can display some notifications (asking for feedback, discord invite, mirrord for Teams invite).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,12 @@ class MirrordExecManager(private val service: MirrordProjectService) {
return wslDistribution?.getWslPath(path) ?: path
}

/**
* Starts mirrord, shows dialog for selecting pod if target is not set and returns env to set.
*
* @param envVars Contains both system env vars, and (active) launch settings, see `Wrapper`.
* @return extra environment variables to set for the executed process and path to the patched executable.
* null if mirrord service is disabled
* @throws ProcessCanceledException if the user cancelled
*/
private fun start(
private fun prepareStart(
wslDistribution: WSLDistribution?,
executable: String?,
product: String,
projectEnvVars: Map<String, String>?
): MirrordExecution? {
projectEnvVars: Map<String, String>?,
mirrordApi: MirrordApi
): Pair<String?, String?>? {
MirrordLogger.logger.debug("MirrordExecManager.start")
val mirrordActiveValue = projectEnvVars?.get("MIRRORD_ACTIVE")
val explicitlyEnabled = mirrordActiveValue == "1"
Expand All @@ -122,8 +114,6 @@ class MirrordExecManager(private val service: MirrordProjectService) {
)
}

val mirrordApi = service.mirrordApi(projectEnvVars)

val mirrordConfigPath = projectEnvVars?.get(CONFIG_ENV_NAME)?.let {
if (it.contains("\$ProjectPath\$")) {
val projectFile = service.configApi.getProjectDir()
Expand Down Expand Up @@ -185,6 +175,27 @@ class MirrordExecManager(private val service: MirrordProjectService) {
null
}

return Pair(configPath, target)
}

/**
* Starts mirrord, shows dialog for selecting pod if target is not set and returns env to set.
*
* @param envVars Contains both system env vars, and (active) launch settings, see `Wrapper`.
* @return extra environment variables to set for the executed process and path to the patched executable.
* null if mirrord service is disabled
* @throws ProcessCanceledException if the user cancelled
*/
private fun start(
wslDistribution: WSLDistribution?,
executable: String?,
product: String,
projectEnvVars: Map<String, String>?
): MirrordExecution? {
val mirrordApi = service.mirrordApi(projectEnvVars)
val (configPath, target) = this.prepareStart(wslDistribution, product, projectEnvVars, mirrordApi) ?: return null
val cli = cliPath(wslDistribution, product)

val executionInfo = mirrordApi.exec(
cli,
target,
Expand All @@ -198,6 +209,29 @@ class MirrordExecManager(private val service: MirrordProjectService) {
return executionInfo
}

private fun containerStart(
wslDistribution: WSLDistribution?,
product: String,
projectEnvVars: Map<String, String>?
): MirrordContainerExecution? {
val mirrordApi = service.mirrordApi(projectEnvVars)
val (configPath, target) = this.prepareStart(wslDistribution, product, projectEnvVars, mirrordApi) ?: return null
val cli = cliPath(wslDistribution, product)

val executionInfo = mirrordApi.containerExec(
cli,
target,
configPath,
wslDistribution
)
MirrordLogger.logger.debug("MirrordExecManager.start: executionInfo: $executionInfo")

executionInfo.extraArgs.add("-e")
executionInfo.extraArgs.add("MIRRORD_IGNORE_DEBUGGER_PORTS=\"35000-65535\"")

return executionInfo
}

/**
* Wrapper around `MirrordExecManager` that is called by each IDE, or language variant.
*
Expand All @@ -222,6 +256,22 @@ class MirrordExecManager(private val service: MirrordProjectService) {
throw e
}
}

fun containerStart(): MirrordContainerExecution? {
return try {
manager.containerStart(wsl, product, extraEnvVars)
} catch (e: MirrordError) {
e.showHelp(manager.service.project)
throw e
} catch (e: ProcessCanceledException) {
manager.service.notifier.notifySimple("mirrord was cancelled", NotificationType.WARNING)
throw e
} catch (e: Throwable) {
val mirrordError = MirrordError(e.toString(), e)
mirrordError.showHelp(manager.service.project)
throw e
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.metalbear.mirrord.products.pycharm

import com.intellij.execution.target.TargetEnvironmentRequest
import com.intellij.execution.wsl.target.WslTargetEnvironmentRequest
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
Expand All @@ -11,6 +12,38 @@ import com.jetbrains.python.run.target.PythonCommandLineTargetEnvironmentProvide
import com.metalbear.mirrord.MirrordProjectService

class PythonCommandLineProvider : PythonCommandLineTargetEnvironmentProvider {
// Wrapper for docker variant of TargetEnvironmentRequest because the variant is dynamically loaded from another
// plugin so we need to perform our operations via reflection api
private class DockerRuntimeConfig(val inner: TargetEnvironmentRequest) {
var runCliOptions: String?
get() {
val runCliOptionsField = inner.javaClass.getDeclaredField("myRunCliOptions")
return if (runCliOptionsField.trySetAccessible()) {
runCliOptionsField.get(inner) as String
} else {
null
}
}
set(value) {
inner
.javaClass
.getMethod("setRunCliOptions", Class.forName("java.lang.String"))
.invoke(inner, value)
}
}

private fun extendContainerTargetEnvironment(project: Project, runParams: PythonRunParams, docker: DockerRuntimeConfig) {
val service = project.service<MirrordProjectService>()

service.execManager.wrapper("pycharm", runParams.getEnvs()).containerStart()?.let { executionInfo ->
docker.runCliOptions?.let {
executionInfo.extraArgs.add(it)
}

docker.runCliOptions = executionInfo.extraArgs.joinToString(" ")
}
}

override fun extendTargetEnvironment(
project: Project,
helpersAwareTargetRequest: HelpersAwareTargetEnvironmentRequest,
Expand All @@ -20,26 +53,38 @@ class PythonCommandLineProvider : PythonCommandLineTargetEnvironmentProvider {
val service = project.service<MirrordProjectService>()

if (runParams is AbstractPythonRunConfiguration<*>) {
val wsl = helpersAwareTargetRequest.targetEnvironmentRequest.let {
if (it is WslTargetEnvironmentRequest) {
it.configuration.distribution
val docker = helpersAwareTargetRequest.targetEnvironmentRequest.let {
if (it.javaClass.name.startsWith("com.intellij.docker")) {
DockerRuntimeConfig(it)
} else {
null
}
}

service.execManager.wrapper("pycharm", runParams.getEnvs()).apply {
this.wsl = wsl
}.start()?.let { executionInfo ->
for (entry in executionInfo.environment.entries.iterator()) {
pythonExecution.addEnvironmentVariable(entry.key, entry.value)
if (docker != null) {
extendContainerTargetEnvironment(project, runParams, docker)
} else {
val wsl = helpersAwareTargetRequest.targetEnvironmentRequest.let {
if (it is WslTargetEnvironmentRequest) {
it.configuration.distribution
} else {
null
}
}

for (key in executionInfo.envToUnset.orEmpty()) {
pythonExecution.envs.remove(key)
}
service.execManager.wrapper("pycharm", runParams.getEnvs()).apply {
this.wsl = wsl
}.start()?.let { executionInfo ->
for (entry in executionInfo.environment.entries.iterator()) {
pythonExecution.addEnvironmentVariable(entry.key, entry.value)
}

pythonExecution.addEnvironmentVariable("MIRRORD_DETECT_DEBUGGER_PORT", "pydevd")
for (key in executionInfo.envToUnset.orEmpty()) {
pythonExecution.envs.remove(key)
}

pythonExecution.addEnvironmentVariable("MIRRORD_DETECT_DEBUGGER_PORT", "pydevd")
}
}
}
}
Expand Down

0 comments on commit 03987c0

Please sign in to comment.