From 17afc5ef19cd896362b6ebbf330987fc4a99db3b Mon Sep 17 00:00:00 2001 From: Alexey Katsman Date: Wed, 11 Feb 2026 09:47:26 +0100 Subject: [PATCH] PY-87517 Check python binary name for targets When adding new interpreter, we could either specify venv root or python binary path. Since we cannot check actual files for targets, we check path regex to determine whether it's a venv root or a binary. GitOrigin-RevId: d46ef025d9681b4d697d5d9572d0302c9599fdfc --- python/openapi/BUILD.bazel | 2 ++ python/openapi/intellij.python.community.iml | 1 + .../python/venvReader/VirtualEnvReader.kt | 24 +++++++++++++++++++ .../python/sdk/AddInterpreterActions.kt | 10 ++++++-- .../jetbrains/python/sdk/add/v2/FileSystem.kt | 9 +++---- 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/python/openapi/BUILD.bazel b/python/openapi/BUILD.bazel index dd22a41f02c2..f888bdeb6a90 100644 --- a/python/openapi/BUILD.bazel +++ b/python/openapi/BUILD.bazel @@ -32,6 +32,7 @@ jvm_library( "//platform/projectModel-api:projectModel", "//platform/eel", "//platform/eel-provider", + "//platform/execution", ], exports = [ "//python/python-psi-api:psi", @@ -59,6 +60,7 @@ jvm_library( "@lib//:junit5", "//platform/eel", "//platform/eel-provider", + "//platform/execution", ], exports = [ "//python/python-psi-api:psi", diff --git a/python/openapi/intellij.python.community.iml b/python/openapi/intellij.python.community.iml index 28609645d97a..0cc7e0a1c39c 100644 --- a/python/openapi/intellij.python.community.iml +++ b/python/openapi/intellij.python.community.iml @@ -23,5 +23,6 @@ + \ No newline at end of file diff --git a/python/openapi/src/com/jetbrains/python/venvReader/VirtualEnvReader.kt b/python/openapi/src/com/jetbrains/python/venvReader/VirtualEnvReader.kt index 48c42a05db2c..973d68140f91 100644 --- a/python/openapi/src/com/jetbrains/python/venvReader/VirtualEnvReader.kt +++ b/python/openapi/src/com/jetbrains/python/venvReader/VirtualEnvReader.kt @@ -1,6 +1,8 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.python.venvReader +import com.intellij.execution.Platform +import com.intellij.execution.target.FullPathOnTarget import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.progress.runBlockingMaybeCancellable import com.intellij.openapi.util.io.toCanonicalPath @@ -160,6 +162,23 @@ class VirtualEnvReader private constructor( return findInterpreter(pathOrDir) } + /** + * [binaryOrDir] is either a venv root or a python binary + */ + fun findPythonInPythonRootForTarget(binaryOrDir: FullPathOnTarget, platform: Platform): FullPathOnTarget { + val pythonPattern = getPythonBinaryPattern(platform) + val separator = platform.fileSeparator + val binaryOrDirWithoutSeparatorSuffix = binaryOrDir.removeSuffix(separator.toString()) + if (pythonPattern.matches(binaryOrDirWithoutSeparatorSuffix.substringAfterLast(separator))) { + return binaryOrDir + } + + return when (platform) { + Platform.WINDOWS -> "${binaryOrDirWithoutSeparatorSuffix}${separator}Scripts${separator}python.exe" + Platform.UNIX -> "${binaryOrDirWithoutSeparatorSuffix}${separator}bin${separator}python" + } + } + fun getVenvRootPath(path: Path): Path? { val bin = path.parent @@ -255,6 +274,11 @@ class VirtualEnvReader private constructor( EelOsFamily.Windows -> WIN_PYTHON_PATTERN } } + + private fun getPythonBinaryPattern(platform: Platform): Regex = when (platform) { + Platform.UNIX -> POSIX_PYTHON_PATTERN + Platform.WINDOWS -> WIN_PYTHON_PATTERN + } } } diff --git a/python/src/com/jetbrains/python/sdk/AddInterpreterActions.kt b/python/src/com/jetbrains/python/sdk/AddInterpreterActions.kt index f15bf79e9ae7..3333847950c3 100644 --- a/python/src/com/jetbrains/python/sdk/AddInterpreterActions.kt +++ b/python/src/com/jetbrains/python/sdk/AddInterpreterActions.kt @@ -45,7 +45,9 @@ import com.jetbrains.python.util.ShowingMessageErrorSync import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async +import kotlinx.coroutines.launch import org.jetbrains.annotations.ApiStatus import org.jetbrains.annotations.Nls import java.util.function.Consumer @@ -149,13 +151,14 @@ internal class AddInterpreterOnTargetAction( if (dialogWrapper.exitCode != OK_EXIT_CODE) return val sdk = (dialogWrapper.currentStepObject as? TargetCustomToolWizardStep)?.customTool as? Sdk ?: return - PythonNewInterpreterAddedCollector.logPythonNewInterpreterAdded(sdk, isPreviouslyConfigured = true) + service().coroutineScope.launch(Dispatchers.Default) { + PythonNewInterpreterAddedCollector.logPythonNewInterpreterAdded(sdk, isPreviouslyConfigured = true) + } onSdkCreated(sdk) } } @ApiStatus.Internal - fun switchToSdk(module: Module, sdk: Sdk, currentSdk: Sdk?) { val project = module.project (sdk.sdkType as PythonSdkType).setupSdkPaths(sdk) @@ -171,6 +174,9 @@ fun switchToSdk(module: Module, sdk: Sdk, currentSdk: Sdk?) { module.excludeInnerVirtualEnv(sdk) } +@Service +private class LogCollectorService(val coroutineScope: CoroutineScope) + @Service(Service.Level.PROJECT) @ApiStatus.Internal private class ToolDetectionService(project: Project, val coroutineScope: CoroutineScope) { diff --git a/python/src/com/jetbrains/python/sdk/add/v2/FileSystem.kt b/python/src/com/jetbrains/python/sdk/add/v2/FileSystem.kt index 20ae9064ea06..f6cd888bd93f 100644 --- a/python/src/com/jetbrains/python/sdk/add/v2/FileSystem.kt +++ b/python/src/com/jetbrains/python/sdk/add/v2/FileSystem.kt @@ -336,12 +336,9 @@ sealed interface FileSystem

{ } override suspend fun resolvePythonBinary(pythonHome: PathHolder.Target): PathHolder.Target { - val pythonHomeString = pythonHome.pathString.takeIf { !it.contains("/bin/python") } - ?: return pythonHome - - val pythonBinaryString = pythonHomeString.let { "${it.removeSuffix("/")}/bin/python" } - - return PathHolder.Target(pythonBinaryString) + val pythonHomeString = pythonHome.pathString + val platform = targetEnvironmentConfiguration.getPlatformAndRoot().platform + return PathHolder.Target(VirtualEnvReader().findPythonInPythonRootForTarget(pythonHomeString, platform)) } override suspend fun which(cmd: String): PathHolder.Target? {