diff --git a/platform/eel/src/com/intellij/platform/eel/path/EelPath.kt b/platform/eel/src/com/intellij/platform/eel/path/EelPath.kt
index 340720523290..8b430d005b7e 100644
--- a/platform/eel/src/com/intellij/platform/eel/path/EelPath.kt
+++ b/platform/eel/src/com/intellij/platform/eel/path/EelPath.kt
@@ -1,4 +1,4 @@
-// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
+// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.platform.eel.path
import com.intellij.platform.eel.EelDescriptor
@@ -145,6 +145,9 @@ sealed interface EelPath {
fun toDebugString(): String
+ /**
+ * @return path in the particular eel, i.e.: `/foo` or `c:\bar`
+ */
override fun toString(): String
@Deprecated("Use EelPlatform instead, will be removed soon")
diff --git a/platform/testFramework/junit5/eel/test/params/api/TestApplicationWithEel.kt b/platform/testFramework/junit5/eel/test/params/api/TestApplicationWithEel.kt
index 14f683e76f19..c661491fad1a 100644
--- a/platform/testFramework/junit5/eel/test/params/api/TestApplicationWithEel.kt
+++ b/platform/testFramework/junit5/eel/test/params/api/TestApplicationWithEel.kt
@@ -23,7 +23,10 @@ import org.junit.jupiter.api.extension.ExtendWith
* ```
* -Djava.nio.file.spi.DefaultFileSystemProvider=com.intellij.platform.core.nio.fs.MultiRoutingFileSystemProvider
* ```
- * Run a test, and failure will report an option name
+ * Run a test, and failure will report an option name.
+ *
+ * You either need to have providers (i.e. `intellij.platform.ijent.testFramework` in a classpath),
+ * or disable [atLeastOneRemoteEelRequired]
*/
@TestOnly
@Target(AnnotationTarget.CLASS)
diff --git a/python/openapi/intellij.python.community.iml b/python/openapi/intellij.python.community.iml
index 413ca8fe6625..73e1b56e3354 100644
--- a/python/openapi/intellij.python.community.iml
+++ b/python/openapi/intellij.python.community.iml
@@ -21,5 +21,6 @@
+
\ No newline at end of file
diff --git a/python/openapi/src/com/jetbrains/python/errorProcessing/ExecError.kt b/python/openapi/src/com/jetbrains/python/errorProcessing/ExecError.kt
index 4620908d167c..804b38aa7efc 100644
--- a/python/openapi/src/com/jetbrains/python/errorProcessing/ExecError.kt
+++ b/python/openapi/src/com/jetbrains/python/errorProcessing/ExecError.kt
@@ -3,6 +3,7 @@ package com.jetbrains.python.errorProcessing
import com.intellij.execution.process.ProcessOutput
import com.intellij.openapi.util.NlsContexts
+import com.intellij.platform.eel.path.EelPath
import com.jetbrains.python.PyCommunityBundle
import org.jetbrains.annotations.Nls
@@ -10,22 +11,19 @@ import org.jetbrains.annotations.Nls
* External process error.
*/
class ExecError(
+ val exe: EelPath,
/**
- * I.e ['python', '-v']
+ * I.e ['-v']
*/
- val command: Array,
+ val args: Array,
val errorReason: ExecErrorReason,
/**
* optional message to be displayed to the user: Why did we run this process. I.e "running pip to install package".
*/
val additionalMessageToUser: @NlsContexts.DialogTitle String? = null,
-) : PyError(getExecErrorMessage(command, additionalMessageToUser, errorReason)) {
- val exeAndArgs: Pair> = Pair(command[0], command.drop(1).toTypedArray())
-
- init {
- assert(command.isNotEmpty()) { "Command can't be empty" }
- }
+) : PyError(getExecErrorMessage(exe.toString(), args, additionalMessageToUser, errorReason)) {
+ val asCommand: String get() = (arrayOf(exe.toString()) + args).joinToString(" ")
}
@@ -51,12 +49,13 @@ sealed interface ExecErrorReason {
fun ProcessOutput.asExecutionFailed(): ExecErrorReason.UnexpectedProcessTermination =
ExecErrorReason.UnexpectedProcessTermination(exitCode, stdout, stderr)
-internal fun getExecErrorMessage(
- command: Array,
+private fun getExecErrorMessage(
+ exec: String,
+ args: Array,
additionalMessage: @NlsContexts.DialogTitle String?,
execErrorReason: ExecErrorReason,
): @Nls String {
- val commandLine = command.joinToString(" ")
+ val commandLine = exec + args.joinToString(" ")
return when (val r = execErrorReason) {
is ExecErrorReason.CantStart -> {
PyCommunityBundle.message("python.execution.cant.start.error",
diff --git a/python/openapi/src/com/jetbrains/python/packaging/PyExecutionException.kt b/python/openapi/src/com/jetbrains/python/packaging/PyExecutionException.kt
index 82159080bfac..5f2b0fb2aa33 100644
--- a/python/openapi/src/com/jetbrains/python/packaging/PyExecutionException.kt
+++ b/python/openapi/src/com/jetbrains/python/packaging/PyExecutionException.kt
@@ -4,9 +4,11 @@ package com.jetbrains.python.packaging
import com.intellij.execution.ExecutionException
import com.intellij.execution.process.ProcessOutput
import com.intellij.openapi.util.NlsContexts
+import com.intellij.platform.eel.provider.asEelPath
import com.jetbrains.python.errorProcessing.*
import org.jetbrains.annotations.ApiStatus
import java.io.IOException
+import kotlin.io.path.Path
/**
* Wraps [PyError] for cases where [ExecutionException] is used.
@@ -29,7 +31,7 @@ class PyExecutionException private constructor(
additionalMessageToUser: @NlsContexts.DialogMessage String?,
command: String,
args: List,
- ): PyExecutionException = PyExecutionException(ExecError(arrayOf(command) + args.toTypedArray(), ExecErrorReason.Timeout, additionalMessageToUser))
+ ): PyExecutionException = PyExecutionException(ExecError(Path(command).asEelPath(), args.toTypedArray(), ExecErrorReason.Timeout, additionalMessageToUser))
}
@@ -63,7 +65,7 @@ class PyExecutionException private constructor(
args: List,
fixes: List = listOf(),
) : this(
- pyError = ExecError(arrayOf(command) + args.toTypedArray(), ExecErrorReason.CantStart(null, startException.localizedMessage), additionalMessage),
+ pyError = ExecError(Path(command).asEelPath(), args.toTypedArray(), ExecErrorReason.CantStart(null, startException.localizedMessage), additionalMessage),
fixes = fixes,
ioException = startException)
@@ -82,7 +84,7 @@ class PyExecutionException private constructor(
output: ProcessOutput,
fixes: List = listOf(),
) : this(
- pyError = ExecError(arrayOf(command) + args.toTypedArray(), output.asExecutionFailed()),
+ pyError = ExecError(Path(command).asEelPath(), args.toTypedArray(), output.asExecutionFailed()),
fixes = fixes)
/**
diff --git a/python/python-exec-service/intellij.python.community.execService.iml b/python/python-exec-service/intellij.python.community.execService.iml
index 0f1476622f49..2f7636c2b09c 100644
--- a/python/python-exec-service/intellij.python.community.execService.iml
+++ b/python/python-exec-service/intellij.python.community.execService.iml
@@ -24,5 +24,7 @@
+
+
\ No newline at end of file
diff --git a/python/python-exec-service/resources/messages/PyExecBundle.properties b/python/python-exec-service/resources/messages/PyExecBundle.properties
index dbc29177d7a2..2d9963804759 100644
--- a/python/python-exec-service/resources/messages/PyExecBundle.properties
+++ b/python/python-exec-service/resources/messages/PyExecBundle.properties
@@ -1,6 +1,5 @@
py.exec.defaultName.process=Process
py.exec.defaultName.helper=Helper
-py.exec.defaultName.command=Command
py.exec.start.error={0} Failed to Start: {1} (Code {2})
py.exec.timeout.error={0} Timed out (Run More Than {1})
diff --git a/python/python-exec-service/src/com/intellij/python/community/execService/api.kt b/python/python-exec-service/src/com/intellij/python/community/execService/api.kt
index e4cd8c38f3cc..ddeeab5b9934 100644
--- a/python/python-exec-service/src/com/intellij/python/community/execService/api.kt
+++ b/python/python-exec-service/src/com/intellij/python/community/execService/api.kt
@@ -5,6 +5,7 @@ import com.intellij.execution.process.ProcessOutput
import com.intellij.openapi.util.NlsSafe
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.EelProcess
+import com.intellij.platform.eel.provider.asNioPath
import com.intellij.python.community.execService.impl.ExecServiceImpl
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.Result
@@ -89,18 +90,21 @@ sealed interface WhatToExec {
/**
* [binary] (can reside on local or remote Eel, [EelApi] is calculated out of it)
*/
- data class Binary(val binary: Path) : WhatToExec
+ data class Binary(val binary: Path) : WhatToExec {
+ companion object {
+ /**
+ * Resolves relative name to the full name or `null` if [relativeBinName] can't be found in the path.
+ */
+ suspend fun fromRelativeName(eel: EelApi, relativeBinName: String): Binary? =
+ eel.exec.findExeFilesInPath(relativeBinName).firstOrNull()?.let { Binary(it.asNioPath()) }
+ }
+ }
/**
* Execute [helper] on [python]. If [python] resides on remote Eel -- helper is copied there.
* Note, that only **one** helper file is copied, not all helpers.
*/
data class Helper(val python: PythonBinary, val helper: HelperName) : WhatToExec
-
- /**
- * Random command on [eel]. [EelApi] will look for it in the path
- */
- data class Command(val eel: EelApi, val command: String) : WhatToExec
}
/**
diff --git a/python/python-exec-service/src/com/intellij/python/community/execService/impl/ExecServiceImpl.kt b/python/python-exec-service/src/com/intellij/python/community/execService/impl/ExecServiceImpl.kt
index 72ddaadf9d26..b0cd55c38938 100644
--- a/python/python-exec-service/src/com/intellij/python/community/execService/impl/ExecServiceImpl.kt
+++ b/python/python-exec-service/src/com/intellij/python/community/execService/impl/ExecServiceImpl.kt
@@ -3,7 +3,11 @@ package com.intellij.python.community.execService.impl
import com.intellij.execution.process.ProcessOutput
import com.intellij.openapi.diagnostic.fileLogger
-import com.intellij.platform.eel.*
+import com.intellij.platform.eel.EelExecApi
+import com.intellij.platform.eel.EelProcess
+import com.intellij.platform.eel.execute
+import com.intellij.platform.eel.getOr
+import com.intellij.platform.eel.path.EelPath
import com.intellij.platform.eel.provider.asEelPath
import com.intellij.platform.eel.provider.getEelDescriptor
import com.intellij.platform.eel.provider.utils.*
@@ -75,8 +79,7 @@ internal object ExecServiceImpl : ExecService {
}
private data class EelExecutableProcess(
- val eel: EelApi,
- val exe: String,
+ val exe: EelPath,
val args: List,
val env: Map,
val workingDirectory: Path?,
@@ -84,8 +87,8 @@ private data class EelExecutableProcess(
)
private suspend fun WhatToExec.buildExecutableProcess(args: List, options: ExecOptions): EelExecutableProcess {
- val (eel, exe, args) = when (this) {
- is WhatToExec.Binary -> Triple(binary.getEelDescriptor().upgrade(), binary.asEelPath().toString(), args)
+ val (exe, args) = when (this) {
+ is WhatToExec.Binary -> Pair(binary, args)
is WhatToExec.Helper -> {
val eel = python.getEelDescriptor().upgrade()
val localHelper = PythonHelpersLocator.findPathInHelpers(helper)
@@ -94,24 +97,22 @@ private suspend fun WhatToExec.buildExecutableProcess(args: List, option
source = localHelper,
target = EelPathUtils.TransferTarget.Temporary(eel.descriptor)
).asEelPath().toString()
- Triple(eel, python.asEelPath().toString(), listOf(remoteHelper) + args)
+ Pair(python, listOf(remoteHelper) + args)
}
- is WhatToExec.Command -> Triple(eel, command, args)
}
val description = options.processDescription ?: when (this) {
is WhatToExec.Binary -> PyExecBundle.message("py.exec.defaultName.process")
is WhatToExec.Helper -> PyExecBundle.message("py.exec.defaultName.helper")
- is WhatToExec.Command -> PyExecBundle.message("py.exec.defaultName.command")
}
- return EelExecutableProcess(eel, exe, args, options.env, options.workingDirectory, description)
+ return EelExecutableProcess(exe.asEelPath(), args, options.env, options.workingDirectory, description)
}
@CheckReturnValue
private suspend fun EelExecutableProcess.run(): Result {
val workingDirectory = if (workingDirectory != null && !workingDirectory.isAbsolute) workingDirectory.toRealPath() else workingDirectory
- val executionResult = eel.exec.execute(exe)
+ val executionResult = exe.descriptor.upgrade().exec.execute(exe.toString())
.args(args)
.env(env)
.workingDirectory(workingDirectory?.asEelPath()).eelIt()
@@ -124,7 +125,8 @@ private suspend fun EelExecutableProcess.run(): Result {
private fun EelExecutableProcess.failAsCantStart(executeProcessError: EelExecApi.ExecuteProcessError): Result.Failure {
return ExecError(
- command = arrayOf(exe) + args.toTypedArray(),
+ exe = exe,
+ args = args.toTypedArray(),
additionalMessageToUser = PyExecBundle.message("py.exec.start.error", description, executeProcessError.message, executeProcessError.errno),
errorReason = ExecErrorReason.CantStart(executeProcessError.errno, executeProcessError.message)
).logAndFail()
@@ -134,7 +136,8 @@ private suspend fun EelExecutableProcess.killProcessAndFailAsTimeout(eelProcess:
eelProcess.kill()
return ExecError(
- command = arrayOf(exe) + args.toTypedArray(),
+ exe = exe,
+ args = args.toTypedArray(),
additionalMessageToUser = PyExecBundle.message("py.exec.timeout.error", description, timeout),
errorReason = ExecErrorReason.Timeout
).logAndFail()
@@ -146,7 +149,8 @@ private fun EelExecutableProcess.failAsExecutionFailed(processOutput: ProcessOut
}
return ExecError(
- command = arrayOf(exe) + args.toTypedArray(),
+ exe = exe,
+ args = args.toTypedArray(),
additionalMessageToUser = additionalMessage,
errorReason = processOutput.asExecutionFailed()
).logAndFail()
diff --git a/python/python-exec-service/tests/com/intellij/python/junit5Tests/unit/alsoWin/ExecServiceShowCaseTest.kt b/python/python-exec-service/tests/com/intellij/python/junit5Tests/unit/alsoWin/ExecServiceShowCaseTest.kt
index 083e833fdcb5..b9747492a888 100644
--- a/python/python-exec-service/tests/com/intellij/python/junit5Tests/unit/alsoWin/ExecServiceShowCaseTest.kt
+++ b/python/python-exec-service/tests/com/intellij/python/junit5Tests/unit/alsoWin/ExecServiceShowCaseTest.kt
@@ -2,39 +2,40 @@
package com.intellij.python.junit5Tests.unit.alsoWin
import com.intellij.platform.eel.EelPlatform
+import com.intellij.platform.eel.getShell
import com.intellij.platform.eel.provider.asNioPath
-import com.intellij.platform.eel.provider.localEel
-import com.intellij.platform.eel.where
+import com.intellij.platform.testFramework.junit5.eel.params.api.EelHolder
+import com.intellij.platform.testFramework.junit5.eel.params.api.EelSource
+import com.intellij.platform.testFramework.junit5.eel.params.api.TestApplicationWithEel
import com.intellij.python.community.execService.ExecService
import com.intellij.python.community.execService.WhatToExec
import com.intellij.testFramework.common.timeoutRunBlocking
-import com.intellij.testFramework.junit5.TestApplication
import com.jetbrains.python.Result
import com.jetbrains.python.getOrThrow
import org.hamcrest.CoreMatchers
import org.hamcrest.MatcherAssert.assertThat
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.fail
-import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
-import org.junit.jupiter.params.provider.ValueSource
/**
- * How to use [ExecService]
+ * How to use [ExecService].
+ * To exec this test against remote eels, you need `intellij.platform.ijent.testFramework` in classpath (exists on TC)
*/
-@TestApplication
+@TestApplicationWithEel(atLeastOneRemoteEelRequired = false)
class ExecServiceShowCaseTest {
- private val eel = localEel // TODO: Check other eels
+ @ParameterizedTest
+ @EelSource
+ fun testDataTransformer(eelHolder: EelHolder): Unit = timeoutRunBlocking {
+ val eel = eelHolder.eel
- @Test
- fun testDataTransformer(): Unit = timeoutRunBlocking {
data class Record(val name: String, val age: Int)
- val echoCommand = WhatToExec.Command(eel, "echo")
- val args = listOf("Alice,25\nBob,48\n")
+ val (shell, execArg) = eel.exec.getShell()
+ val args = listOf(execArg, "echo Alice,25 && echo Bob,48")
- val records = ExecService().execute(echoCommand, args) { output ->
+ val records = ExecService().execute(WhatToExec.Binary(shell.asNioPath()), args) { output ->
when {
output.exitCode == 123 -> {
Result.success(emptyList())
@@ -48,7 +49,7 @@ class ExecServiceShowCaseTest {
else -> {
val records = output.stdoutLines.map {
val (name, age) = it.split(',')
- Record(name, age.toInt())
+ Record(name, age.trim().toInt())
}
Result.success(records)
}
@@ -62,40 +63,40 @@ class ExecServiceShowCaseTest {
}
@ParameterizedTest
- @ValueSource(booleans = [true, false])
- fun testSunnyDay(useLocalPath: Boolean): Unit = timeoutRunBlocking {
+ @EelSource
+ fun testSunnyDay(eelHolder: EelHolder): Unit = timeoutRunBlocking {
+ val eel = eelHolder.eel
val execService = ExecService()
val expectedPhrase = "Usage"
- val (command, args) = when (eel.platform) {
- is EelPlatform.Windows -> Pair("ping.exe", arrayOf("/?"))
+ val (binaryName, args) = when (eel.platform) {
+ is EelPlatform.Windows -> {
+ Pair("ping.exe", arrayOf("/?"))
+ }
is EelPlatform.Posix -> {
Pair("sh", arrayOf("-c", "echo $expectedPhrase"))
}
}
- val whatToExec = if (useLocalPath) {
- val fullPath = (eel.exec.where(command) ?: error("no $command found on $eel")).asNioPath()
- WhatToExec.Binary(fullPath)
- }
- else {
- WhatToExec.Command(eel, command)
- }
+ val whatToExec = WhatToExec.Binary.fromRelativeName(eel, binaryName) ?: error("Can't find $binaryName")
val output = execService.execGetStdout(whatToExec, args.toList()).getOrThrow()
assertThat("Command doesn't have expected output", output, CoreMatchers.containsString(expectedPhrase))
}
- @Test
- fun testRainyDay(): Unit = timeoutRunBlocking {
+ @ParameterizedTest
+ @EelSource
+ fun testRainyDay(eelHolder: EelHolder): Unit = timeoutRunBlocking {
+ val eel = eelHolder.eel
+ val binary = eel.fs.user.home.asNioPath().resolve("Some_command_that_never_exists_on_any_machine${Math.random()}")
val arg = "foo"
- val command = WhatToExec.Command(eel, "Some_command_that_never_exists_on_any_machine${Math.random()}")
+ val command = WhatToExec.Binary(binary)
when (val output = ExecService().execGetStdout(command, listOf(arg))) {
is Result.Success -> fail("Execution of bad command should lead to an error")
is Result.Failure -> {
val err = output.error
- assertEquals(command.command, err.command[0], "Wrong command reported")
- assertEquals("foo", err.command[1], "Wrong args reported")
+ assertEquals(command.binary, err.exe.asNioPath(), "Wrong command reported")
+ assertEquals("foo", err.args[0], "Wrong args reported")
}
}
}
diff --git a/python/src/com/jetbrains/python/ProcessExecutionErrorDialog.kt b/python/src/com/jetbrains/python/ProcessExecutionErrorDialog.kt
index 2be2e4f867fa..4f7e6a53e16d 100644
--- a/python/src/com/jetbrains/python/ProcessExecutionErrorDialog.kt
+++ b/python/src/com/jetbrains/python/ProcessExecutionErrorDialog.kt
@@ -60,7 +60,7 @@ fun showProcessExecutionErrorDialog(
val errorMessageLabel = JBLabel(UIUtil.toHtml(errorMessageText), Messages.getErrorIcon(), SwingConstants.LEFT)
val commandOutputTextPane = JTextPane().apply {
- val command = execError.command.joinToString(" ")
+ val command = execError.asCommand
when (val err = execError.errorReason) {
is ExecErrorReason.CantStart -> {
appendProcessOutput(command, err.cantExecProcessError, execError.message, null)
diff --git a/python/src/com/jetbrains/python/packaging/PyExecutionExceptionExt.kt b/python/src/com/jetbrains/python/packaging/PyExecutionExceptionExt.kt
index 130613aedb99..3c28175b259f 100644
--- a/python/src/com/jetbrains/python/packaging/PyExecutionExceptionExt.kt
+++ b/python/src/com/jetbrains/python/packaging/PyExecutionExceptionExt.kt
@@ -2,10 +2,12 @@
package com.jetbrains.python.packaging
import com.intellij.execution.process.ProcessOutput
+import com.intellij.platform.eel.provider.asEelPath
import com.jetbrains.python.errorProcessing.ExecError
import com.jetbrains.python.errorProcessing.ExecErrorReason
import com.jetbrains.python.errorProcessing.MessageError
import java.io.IOException
+import kotlin.io.path.Path
/**
* A temporary hack for some outdated code (see usages), do not use in a new code. Stay away from [PyExecutionException]
@@ -18,8 +20,7 @@ internal fun PyExecutionException.copyWith(newCommand: String, newArgs: List {
- val command = arrayOf(newCommand) + newArgs.toTypedArray()
- PyExecutionException(ExecError(command, ExecErrorReason.Timeout, err.additionalMessageToUser))
+ PyExecutionException(ExecError(Path(newCommand).asEelPath(), newArgs.toTypedArray(), ExecErrorReason.Timeout, err.additionalMessageToUser))
}
is ExecErrorReason.UnexpectedProcessTermination -> {
val output = ProcessOutput(reason.stdout, reason.stderr, reason.exitCode, false, false)
diff --git a/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java b/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java
index a452b35df511..84984e16f3c4 100644
--- a/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java
+++ b/python/src/com/jetbrains/python/packaging/PyPackageManagerUI.java
@@ -190,7 +190,7 @@ public final class PyPackageManagerUI {
if (e instanceof PyExecutionException pyExecutionException) {
PyError pyError = pyExecutionException.getPyError();
if (pyError instanceof ExecError execError) {
- return Arrays.stream(execError.getExeAndArgs().getSecond());
+ return Arrays.stream(execError.getArgs());
}
}
return null;
diff --git a/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java b/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java
index cf70179d2e28..1e31fbf7577b 100644
--- a/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java
+++ b/python/src/com/jetbrains/python/packaging/ui/PyPackageManagementService.java
@@ -329,7 +329,7 @@ public class PyPackageManagementService extends PackageManagementServiceEx {
final String stderrCause = findErrorCause(execFailed.getStderr());
final String cause = stdoutCause != null ? stdoutCause : stderrCause;
final String message = cause != null ? cause : pyExecEx.getMessage();
- final String command = StringUtil.join(execError.getCommand(), " ");
+ final String command = execError.getAsCommand();
return new PyPackageInstallationErrorDescription(message, command,
stdout.isEmpty() ? execFailed.getStderr() : stdout + "\n" + execFailed.getStderr(),
findErrorSolution(pyExecEx, cause, sdk), packageName, sdk);
@@ -358,7 +358,8 @@ public class PyPackageManagementService extends PackageManagementServiceEx {
}
}
- if ("pip".equals(e.getCommand()[0]) && sdk != null) {
+ var fileName = e.getExe().getFileName();
+ if (fileName.startsWith("pip") && sdk != null) {
return PySdkBundle.message("python.sdk.try.to.run.command.from.system.terminal", sdk.getHomePath());
}
}
diff --git a/python/src/com/jetbrains/python/sdk/add/v2/hatch/HatchUIComponents.kt b/python/src/com/jetbrains/python/sdk/add/v2/hatch/HatchUIComponents.kt
index e8e3c286bd0c..a3c9928bb45b 100644
--- a/python/src/com/jetbrains/python/sdk/add/v2/hatch/HatchUIComponents.kt
+++ b/python/src/com/jetbrains/python/sdk/add/v2/hatch/HatchUIComponents.kt
@@ -58,7 +58,7 @@ internal sealed class HatchUIError(message: String) : MessageError(message) {
class HatchExecutionFailure(execError: ExecError) : HatchUIError(
message("sdk.create.custom.hatch.error.execution.failed",
- execError.command.joinToString(" ")
+ execError.asCommand
)
)
}