mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-05 01:50:56 +07:00
[python]: PY-85585 : Do not display vens as system pythons in "Add new interpreter" window.
Instead of old `addManuallyAddedInterpreter` we now have two functions: one that requires system python and one that doesn't. Both functions register system python if provided, but the latter one accepts any python (venv included). Various "selectors" use these functions. We also make sure no non-system python is set to `baseInterpreters`: base are always system! As a bonus, we show "system" or "virtual env" title near interpreter. It now checks that python is system (see `ensureSystemPython`). Non-system pythons are never reported, and `registerSystemPython` also returns an error for non-system pythons We need `execGetBoolFromStdout` for the further changes Merge-request: IJ-MR-182415 Merged-by: Ilya Kazakevich <ilya.kazakevich@jetbrains.com> (cherry picked from commit 2950f5f0cd2745c12987a92e40774d366568f312) GitOrigin-RevId: f30e9a6cd7b5d103454d66f26a0c2282d7c587fc
This commit is contained in:
committed by
intellij-monorepo-bot
parent
ed0fda649a
commit
3388536d2c
@@ -7,7 +7,7 @@ import org.jetbrains.annotations.Nls
|
||||
/**
|
||||
* Some "business" error: just a message to be displayed to a user
|
||||
*/
|
||||
open class MessageError(message: @NlsSafe String) : PyError(message)
|
||||
open class MessageError(message: @Nls String) : PyError(message)
|
||||
|
||||
suspend fun ErrorSink.emit(message: @Nls String) {
|
||||
emit(MessageError(message))
|
||||
|
||||
@@ -589,6 +589,9 @@ sdk.create.custom.python=Python
|
||||
|
||||
sdk.create.check.environments=Checking Existing Environments
|
||||
|
||||
|
||||
sdk.rendering.detected.grey.text.system=system
|
||||
sdk.rendering.detected.grey.text.venv=virtual environment
|
||||
sdk.rendering.detected.grey.text=system
|
||||
sdk.rendering.installable.grey.text=download and install
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
python.get.version.error={0} returned error: {1}
|
||||
python.get.version.wrong.version={0} has a wrong version: {1}
|
||||
python.cannot.exec=Python {0} could not be executed
|
||||
python.check.threading.fail=Can not check for python threading model
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// 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.python.community.execService.python
|
||||
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.python.community.execService.*
|
||||
import com.intellij.python.community.execService.python.advancedApi.ExecutablePython
|
||||
import com.intellij.python.community.execService.python.advancedApi.executeHelperAdvanced
|
||||
import com.intellij.python.community.execService.python.advancedApi.validatePythonAndGetInfo
|
||||
import com.intellij.python.community.execService.python.impl.execGetStdoutBoolImpl
|
||||
import com.intellij.python.community.execService.python.impl.execGetStdoutImpl
|
||||
import com.intellij.python.community.helpersLocator.PythonHelpersLocator
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.PythonInfo
|
||||
@@ -42,6 +45,24 @@ suspend fun PythonBinaryOnEelOrTarget.validatePythonAndGetInfo(): PyResult<Pytho
|
||||
suspend fun ExecService.validatePythonAndGetInfo(python: PythonBinary): PyResult<PythonInfo> = validatePythonAndGetInfo(python.asBinToExec())
|
||||
suspend fun PythonBinary.validatePythonAndGetInfo(): PyResult<PythonInfo> = asBinToExec().validatePythonAndGetInfo()
|
||||
|
||||
/**
|
||||
* Execute [pythonCode] on [ExecutablePython] and (if exitcode is 0) return stdout
|
||||
*/
|
||||
suspend fun ExecutablePython.execGetStdout(pythonCode: @NlsSafe String, execService: ExecService = ExecService()): PyResult<String> = execService.execGetStdoutImpl(this, pythonCode, ZeroCodeStdoutTransformer)
|
||||
suspend fun PythonBinaryOnEelOrTarget.execGetStdout(pythonCode: @NlsSafe String, execService: ExecService = ExecService()): PyResult<String> = execService.execGetStdoutImpl(ExecutablePython.vanillaExecutablePython(this), pythonCode, ZeroCodeStdoutTransformer)
|
||||
|
||||
/**
|
||||
* Execute [pythonCode] on [ExecutablePython] and (if exitcode is 0) return stdout converted to [Boolean]. Useful for things like:
|
||||
* ```kotlin
|
||||
* when (val r = executeGetBoolFromStdout("print(some_system_check()")) {
|
||||
* is Success<*> -> {/*r is true of false*/}
|
||||
* is Failure<*> -> {/*r is an error here*/}
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
suspend fun ExecutablePython.execGetBoolFromStdout(pythonCode: @NlsSafe String, execService: ExecService = ExecService()): PyResult<Boolean> = execService.execGetStdoutBoolImpl(this, pythonCode)
|
||||
suspend fun PythonBinaryOnEelOrTarget.execGetBoolFromStdout(pythonCode: @NlsSafe String, execService: ExecService = ExecService()): PyResult<Boolean> = execService.execGetStdoutBoolImpl(ExecutablePython.vanillaExecutablePython(this), pythonCode)
|
||||
|
||||
|
||||
/**
|
||||
* Adds helper by copying it to the remote system (if needed)
|
||||
|
||||
@@ -29,18 +29,7 @@ private const val GIL_CHECK_CMD = "from __future__ import print_function; import
|
||||
@ApiStatus.Internal
|
||||
internal suspend fun ExecService.validatePythonAndGetInfoImpl(python: ExecutablePython): PyResult<PythonInfo> = withContext(Dispatchers.IO) {
|
||||
val options = ExecOptions(timeout = 1.minutes)
|
||||
val gilCheckOutput = executePythonAdvanced(
|
||||
python,
|
||||
Args("-c", GIL_CHECK_CMD),
|
||||
processInteractiveHandler = transformerToHandler(null, ZeroCodeStdoutTransformer),
|
||||
options = options
|
||||
).getOr(message("python.cannot.exec", python.userReadableName)) { return@withContext it }.trim()
|
||||
|
||||
val freeThreaded = when (gilCheckOutput) {
|
||||
"True" -> false
|
||||
"False" -> true
|
||||
else -> return@withContext PyResult.localizedError(message("python.get.version.error", python.userReadableName, gilCheckOutput))
|
||||
}
|
||||
val freeThreaded = !execGetStdoutBoolImpl(python, GIL_CHECK_CMD).getOr(message("python.check.threading.fail")) { return@withContext it }
|
||||
|
||||
val versionOutput: EelProcessExecutionResult = executePythonAdvanced(python, options = options, args = Args(PYTHON_VERSION_ARG), processInteractiveHandler = transformerToHandler<EelProcessExecutionResult>(null) { r ->
|
||||
if (r.exitCode == 0) Result.success(r) else Result.failure(message("python.get.version.error", python.userReadableName, r.exitCode))
|
||||
@@ -55,6 +44,21 @@ internal suspend fun ExecService.validatePythonAndGetInfoImpl(python: Executable
|
||||
return@withContext Result.success(PythonInfo(languageLevel, freeThreaded))
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
internal suspend fun ExecService.execGetStdoutBoolImpl(python: ExecutablePython, command: @NlsSafe String): PyResult<Boolean> = execGetStdoutImpl(python, command, ZeroCodeStdoutTransformerBool)
|
||||
|
||||
@ApiStatus.Internal
|
||||
internal suspend fun <T : Any> ExecService.execGetStdoutImpl(python: ExecutablePython, command: @NlsSafe String, transformer: ZeroCodeStdoutTransformerTyped<T>): PyResult<T> = withContext(Dispatchers.IO) {
|
||||
val options = ExecOptions(timeout = 1.minutes)
|
||||
val result = executePythonAdvanced(
|
||||
python,
|
||||
Args("-c", command),
|
||||
processInteractiveHandler = transformerToHandler(null, transformer),
|
||||
options = options
|
||||
).getOr(message("python.cannot.exec", python.userReadableName)) { return@withContext it }
|
||||
return@withContext Result.success(result)
|
||||
}
|
||||
|
||||
private val ExecutablePython.userReadableName: @NlsSafe String
|
||||
get() =
|
||||
(listOf(when (binary) {
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
// 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.python.junit5Tests.env
|
||||
|
||||
import com.intellij.python.community.execService.BinOnEel
|
||||
import com.intellij.python.community.execService.python.execGetBoolFromStdout
|
||||
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
|
||||
import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase
|
||||
import com.intellij.python.junit5Tests.framework.env.PythonBinaryPath
|
||||
import com.intellij.python.junit5Tests.randomBinary
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.getOrThrow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -30,4 +33,22 @@ class PythonBinaryValidationTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBool(@PythonBinaryPath python: PythonBinary): Unit = runBlocking {
|
||||
val pyOnEel = BinOnEel(python)
|
||||
|
||||
for (jvmBool in arrayOf(true, false)) {
|
||||
val pyBool = jvmBool.toString().replaceFirstChar { it.uppercase() }
|
||||
val parseResult = pyOnEel.execGetBoolFromStdout("print($pyBool)").getOrThrow()
|
||||
Assertions.assertEquals(jvmBool, parseResult, "Error parsing $pyBool")
|
||||
}
|
||||
|
||||
for (buggyCode in arrayOf("pycharm", "print(42)")) {
|
||||
when (val r = pyOnEel.execGetBoolFromStdout(buggyCode)) {
|
||||
is Result.Failure -> Unit
|
||||
is Result.Success -> Assertions.fail("Unexpected success $r")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,4 +5,6 @@ py.exec.timeout.error={0} Timed out (Run More Than {1})
|
||||
py.exec.exitCode.error={0} Exited with {1}
|
||||
py.exec.fileNotFound=File {0} not found on {1}
|
||||
|
||||
py.exec.error.not.zero=Exit code is not zero
|
||||
py.exec.error.unexpected.output=Unexpected output {0}
|
||||
py.exec.target.name.default="Local"
|
||||
@@ -15,7 +15,7 @@ import com.intellij.platform.eel.provider.utils.stdoutString
|
||||
import com.intellij.platform.util.progress.reportRawProgress
|
||||
import com.intellij.python.community.execService.impl.Arg
|
||||
import com.intellij.python.community.execService.impl.ExecServiceImpl
|
||||
import com.intellij.python.community.execService.impl.PyExecBundle
|
||||
import com.intellij.python.community.execService.impl.PyExecBundle.message
|
||||
import com.intellij.python.community.execService.impl.transformerToHandler
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.PythonBinary
|
||||
@@ -104,7 +104,7 @@ suspend fun ExecService.execGetStdout(
|
||||
procListener: PyProcessListener? = null,
|
||||
): PyResult<String> {
|
||||
val binary = eelApi.exec.findExeFilesInPath(binaryName).firstOrNull()?.asNioPath()
|
||||
?: return PyResult.localizedError(PyExecBundle.message("py.exec.fileNotFound", binaryName, eelApi.descriptor.machine.name))
|
||||
?: return PyResult.localizedError(message("py.exec.fileNotFound", binaryName, eelApi.descriptor.machine.name))
|
||||
return execGetStdout(BinOnEel(binary), args, options, procListener)
|
||||
}
|
||||
|
||||
@@ -169,9 +169,40 @@ suspend fun <T> ExecService.execute(
|
||||
*/
|
||||
typealias ProcessOutputTransformer<T> = (EelProcessExecutionResult) -> Result<T, @NlsSafe String?>
|
||||
|
||||
object ZeroCodeStdoutTransformer : ProcessOutputTransformer<String> {
|
||||
override fun invoke(processOutput: EelProcessExecutionResult): Result<String, String?> =
|
||||
if (processOutput.exitCode == 0) Result.success(processOutput.stdoutString.trim()) else Result.failure(null)
|
||||
/**
|
||||
* Return `stdout` if error code is `0`
|
||||
*/
|
||||
val ZeroCodeStdoutTransformer: ZeroCodeStdoutTransformerTyped<String> = ZeroCodeStdoutTransformerTyped { it }
|
||||
|
||||
/**
|
||||
* Parses `bool` out of `stdout` is error code is `0`
|
||||
*/
|
||||
val ZeroCodeStdoutTransformerBool: ZeroCodeStdoutTransformerTyped<Boolean> = ZeroCodeStdoutTransformerTyped {
|
||||
when (it) {
|
||||
"True" -> true
|
||||
"False" -> false
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* See also [ZeroCodeStdoutTransformer], [ZeroCodeStdoutTransformerBool]
|
||||
*/
|
||||
class ZeroCodeStdoutTransformerTyped<T : Any>(val strParser: (String) -> T?) : ProcessOutputTransformer<T> {
|
||||
override fun invoke(processOutput: EelProcessExecutionResult): Result<T, String?> {
|
||||
if (processOutput.exitCode != 0) {
|
||||
return Result.failure(message("py.exec.error.not.zero"))
|
||||
}
|
||||
val output = processOutput.stdoutString.trim()
|
||||
val result = strParser(output)
|
||||
return if (result == null) {
|
||||
Result.failure(message("py.exec.error.unexpected.output", output))
|
||||
}
|
||||
else {
|
||||
Result.success(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -35,6 +35,7 @@ jvm_library(
|
||||
"//python/installer",
|
||||
"//python/python-venv:community-impl-venv",
|
||||
"//python/python-psi-impl:psi-impl",
|
||||
"//python/python-exec-service/execService.python",
|
||||
],
|
||||
exports = ["//python/services/shared"]
|
||||
)
|
||||
@@ -75,6 +76,8 @@ jvm_library(
|
||||
"//platform/testFramework",
|
||||
"//platform/testFramework:testFramework_test_lib",
|
||||
"//python/python-psi-impl:psi-impl",
|
||||
"//python/python-exec-service/execService.python",
|
||||
"//python/python-exec-service/execService.python:execService.python_test_lib",
|
||||
],
|
||||
exports = [
|
||||
"//python/services/shared",
|
||||
|
||||
@@ -33,5 +33,6 @@
|
||||
<orderEntry type="module" module-name="intellij.python.community.testFramework.testEnv" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.python.psi.impl" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.execService.python" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1 +1,2 @@
|
||||
py.system.python.service.python.is.broken=Python {0} is broken
|
||||
py.system.python.service.python.is.broken=Python {0} is broken
|
||||
py.system.python.service.python.is.not.system={0} is not a system python
|
||||
|
||||
@@ -9,10 +9,16 @@ import com.intellij.python.community.impl.venv.createVenv
|
||||
import com.intellij.python.community.services.shared.PythonInfoWithUiComparator
|
||||
import com.intellij.python.community.services.shared.PythonWithUi
|
||||
import com.intellij.python.community.services.shared.VanillaPythonWithPythonInfo
|
||||
import com.intellij.python.community.services.systemPython.impl.PySystemPythonBundle
|
||||
import com.intellij.python.community.services.systemPython.impl.asSysPythonRegisterError
|
||||
import com.intellij.python.community.services.systemPython.impl.ensureSystemPython
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.MessageError
|
||||
import com.jetbrains.python.errorProcessing.PyError
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.mapError
|
||||
import com.jetbrains.python.venvReader.Directory
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
@@ -32,9 +38,9 @@ interface SystemPythonService {
|
||||
|
||||
/**
|
||||
* When user provides a path to the python binary, use this method to the [SystemPython].
|
||||
* @return either [SystemPython] or an error if python is broken.
|
||||
* @return either [SystemPython] or an error if python is broken or not a system python.
|
||||
*/
|
||||
suspend fun registerSystemPython(pythonPath: PythonBinary): PyResult<SystemPython>
|
||||
suspend fun registerSystemPython(pythonPath: PythonBinary): Result<SystemPython, SysPythonRegisterError>
|
||||
|
||||
/**
|
||||
* @return tool to install python on OS If [eelApi] supports python installation
|
||||
@@ -42,6 +48,35 @@ interface SystemPythonService {
|
||||
fun getInstaller(eelApi: EelApi = localEel): PythonInstallerService?
|
||||
}
|
||||
|
||||
/**
|
||||
* System python has an error.
|
||||
* It is either [NotASystemPython] (think: virtual env) or [PythonIsBroken] (and completely unusable)
|
||||
*/
|
||||
sealed interface SysPythonRegisterError {
|
||||
val asPyError: PyError
|
||||
|
||||
/**
|
||||
* Virtual env, not a system python
|
||||
*/
|
||||
class NotASystemPython private constructor(val notSystemPython: VanillaPythonWithPythonInfo, override val asPyError: PyError) : SysPythonRegisterError {
|
||||
companion object : suspend (VanillaPythonWithPythonInfo) -> NotASystemPython {
|
||||
override suspend fun invoke(notSystemPython: VanillaPythonWithPythonInfo): NotASystemPython = NotASystemPython(
|
||||
notSystemPython = notSystemPython,
|
||||
asPyError = MessageError(PySystemPythonBundle.message("py.system.python.service.python.is.not.system", notSystemPython.getReadableName()))
|
||||
)
|
||||
}
|
||||
|
||||
override fun toString(): String = "NotASystemPython(notSystemPython=$notSystemPython, asPyError=$asPyError)"
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Python failed during execution
|
||||
*/
|
||||
data class PythonIsBroken(override val asPyError: PyError) : SysPythonRegisterError
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an instance of this service
|
||||
*/
|
||||
@@ -56,10 +91,19 @@ fun SystemPythonService(): SystemPythonService = ApplicationManager.getApplicati
|
||||
*
|
||||
* Instances could be obtained with [SystemPythonService]
|
||||
*/
|
||||
class SystemPython internal constructor(private val delegate: VanillaPythonWithPythonInfo, override val ui: PyToolUIInfo?) : VanillaPythonWithPythonInfo by delegate, PythonWithUi, Comparable<SystemPython> {
|
||||
class SystemPython private constructor(private val delegate: VanillaPythonWithPythonInfo, override val ui: PyToolUIInfo?) : VanillaPythonWithPythonInfo by delegate, PythonWithUi, Comparable<SystemPython> {
|
||||
|
||||
private companion object {
|
||||
internal companion object {
|
||||
val comparator = PythonInfoWithUiComparator<SystemPython>()
|
||||
internal suspend fun create(delegate: VanillaPythonWithPythonInfo, ui: PyToolUIInfo?): Result<SystemPython, SysPythonRegisterError> {
|
||||
val isSystemPython = ensureSystemPython(delegate).mapError { it.asSysPythonRegisterError() }.getOr { return it }
|
||||
return if (isSystemPython) {
|
||||
Result.success(SystemPython(delegate, ui))
|
||||
}
|
||||
else {
|
||||
Result.failure(SysPythonRegisterError.NotASystemPython(delegate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// 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.python.community.services.systemPython.impl
|
||||
|
||||
import com.intellij.python.community.execService.python.execGetBoolFromStdout
|
||||
import com.intellij.python.community.services.shared.VanillaPythonWithPythonInfo
|
||||
import com.intellij.python.community.services.systemPython.SysPythonRegisterError
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.PyError
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import org.intellij.lang.annotations.Language
|
||||
|
||||
internal fun PyError.asSysPythonRegisterError(): SysPythonRegisterError.PythonIsBroken = SysPythonRegisterError.PythonIsBroken(this)
|
||||
|
||||
@Language("Python")
|
||||
internal const val ENSURE_SYSTEM_PYTHON_CMD = "import sys; print(sys.prefix == sys.base_prefix)"
|
||||
|
||||
internal suspend fun ensureSystemPython(python: VanillaPythonWithPythonInfo): PyResult<Boolean> {
|
||||
if (python.pythonInfo.languageLevel.isPython2) {
|
||||
// there is no obvious check for venv in py2.7. Nobody uses it, anyway. Let it be.
|
||||
return Result.success(true)
|
||||
}
|
||||
val systemPython = python.asExecutablePython.execGetBoolFromStdout(ENSURE_SYSTEM_PYTHON_CMD).getOr { return it }
|
||||
return Result.success(systemPython)
|
||||
}
|
||||
@@ -15,13 +15,9 @@ import com.intellij.python.community.services.internal.impl.VanillaPythonWithPyt
|
||||
import com.intellij.python.community.services.systemPython.SystemPythonServiceImpl.MyServiceState
|
||||
import com.intellij.python.community.services.systemPython.impl.Cache
|
||||
import com.intellij.python.community.services.systemPython.impl.PySystemPythonBundle
|
||||
import com.jetbrains.python.NON_INTERACTIVE_ROOT_TRACE_CONTEXT
|
||||
import com.jetbrains.python.PyToolUIInfo
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.intellij.python.community.services.systemPython.impl.asSysPythonRegisterError
|
||||
import com.jetbrains.python.*
|
||||
import com.jetbrains.python.errorProcessing.getOr
|
||||
import com.jetbrains.python.getOrNull
|
||||
import com.jetbrains.python.sdk.installer.installBinary
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
@@ -65,10 +61,10 @@ internal class SystemPythonServiceImpl(scope: CoroutineScope) : SystemPythonServ
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun registerSystemPython(pythonPath: PythonBinary): PyResult<SystemPython> {
|
||||
override suspend fun registerSystemPython(pythonPath: PythonBinary): Result<SystemPython, SysPythonRegisterError> {
|
||||
val pythonWithLangLevel = VanillaPythonWithPythonInfoImpl.createByPythonBinary(pythonPath)
|
||||
.getOr(PySystemPythonBundle.message("py.system.python.service.python.is.broken", pythonPath)) { return it }
|
||||
val systemPython = SystemPython(pythonWithLangLevel, null)
|
||||
.getOr(PySystemPythonBundle.message("py.system.python.service.python.is.broken", pythonPath)) { return Result.failure(it.error.asSysPythonRegisterError()) }
|
||||
val systemPython = SystemPython.create(pythonWithLangLevel, null).getOr { return it }
|
||||
state.userProvidedPythons.add(pythonPath.pathString)
|
||||
cache()?.get(pythonPath.getEelDescriptor())?.add(systemPython)
|
||||
return Result.success(systemPython)
|
||||
@@ -126,10 +122,14 @@ internal class SystemPythonServiceImpl(scope: CoroutineScope) : SystemPythonServ
|
||||
|
||||
val result = VanillaPythonWithPythonInfoImpl.createByPythonBinaries(pythons.toSet())
|
||||
.mapNotNull { (python, r) ->
|
||||
when (r) {
|
||||
is Result.Success -> SystemPython(r.result, pythonsUi[r.result.pythonBinary])
|
||||
val sysPython = r.mapSuccessError(
|
||||
onSuccess = { r -> SystemPython.create(r, pythonsUi[r.pythonBinary]) },
|
||||
onErr = { it.asSysPythonRegisterError() }
|
||||
)
|
||||
when (sysPython) {
|
||||
is Result.Success -> sysPython.result
|
||||
is Result.Failure -> {
|
||||
fileLogger().warn("Skipping $python : ${r.error}")
|
||||
fileLogger().warn("Skipping $python : ${sysPython.error.asPyError}")
|
||||
badPythons.add(python)
|
||||
null
|
||||
}
|
||||
|
||||
@@ -68,28 +68,6 @@ class SystemPythonServiceShowCaseTest {
|
||||
SystemPythonService().registerSystemPython(randomBinary).assertFail()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCustomPythonSunnyDay(@PythonBinaryPath python: Path, @TempDir venvPath: Path): Unit = timeoutRunBlocking(10.minutes) {
|
||||
createVenv(python, venvPath).getOrThrow()
|
||||
val python = VirtualEnvReader.Instance.findPythonInPythonRoot(venvPath) ?: error("no python in $venvPath")
|
||||
val newPython = SystemPythonService().registerSystemPython(python).orThrow()
|
||||
var allPythons = SystemPythonService().findSystemPythons()
|
||||
assertThat("No newly registered python returned", allPythons, hasItem(newPython))
|
||||
if (SystemInfo.isWindows) {
|
||||
deleteCheckLocking(python)
|
||||
}
|
||||
else {
|
||||
python.deleteExisting()
|
||||
}
|
||||
|
||||
allPythons = SystemPythonService().findSystemPythons(forceRefresh = true)
|
||||
assertThat("Broken python returned", allPythons, not(hasItem(newPython)))
|
||||
|
||||
if (SystemInfo.isWindows) {
|
||||
deleteCheckLocking(venvPath)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRefresh(@TestDisposable disposable: Disposable): Unit = timeoutRunBlocking(10.minutes) {
|
||||
val provider = CountingTestProvider(Result.failure(MessageError("...")))
|
||||
|
||||
@@ -47,7 +47,7 @@ internal abstract class CustomExistingEnvironmentSelector<P : PathHolder>(
|
||||
title = message("sdk.create.custom.existing.env.title"),
|
||||
selectedSdkProperty = selectedEnv,
|
||||
validationRequestor = validationRequestor,
|
||||
onPathSelected = model::addManuallyAddedInterpreter,
|
||||
onPathSelected = model::addManuallyAddedPythonNotNecessarilySystem,
|
||||
) {
|
||||
visibleIf(toolState.isValidationSuccessful)
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ internal abstract class CustomNewEnvironmentCreator<P : PathHolder>(
|
||||
title = message("sdk.create.custom.base.python"),
|
||||
selectedSdkProperty = model.state.baseInterpreter,
|
||||
validationRequestor = validationRequestor,
|
||||
onPathSelected = model::addManuallyAddedInterpreter,
|
||||
onPathSelected = model::addManuallyAddedSystemPython,
|
||||
)
|
||||
|
||||
executablePath = validatablePathField(
|
||||
@@ -96,7 +96,6 @@ internal abstract class CustomNewEnvironmentCreator<P : PathHolder>(
|
||||
module.baseDir?.refresh(true, false)
|
||||
}
|
||||
|
||||
model.addInterpreter(newSdk)
|
||||
|
||||
return Result.success(newSdk)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import com.intellij.python.community.execService.*
|
||||
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
|
||||
import com.intellij.python.community.services.internal.impl.VanillaPythonWithPythonInfoImpl
|
||||
import com.intellij.python.community.services.shared.VanillaPythonWithPythonInfo
|
||||
import com.intellij.python.community.services.systemPython.SysPythonRegisterError
|
||||
import com.intellij.python.community.services.systemPython.SystemPython
|
||||
import com.intellij.python.community.services.systemPython.SystemPythonService
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
@@ -24,6 +25,7 @@ import com.jetbrains.python.errorProcessing.MessageError
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.getOrLogException
|
||||
import com.jetbrains.python.isCondaVirtualEnv
|
||||
import com.jetbrains.python.orLogException
|
||||
import com.jetbrains.python.pathValidation.PlatformAndRoot.Companion.getPlatformAndRoot
|
||||
import com.jetbrains.python.pathValidation.ValidationRequest
|
||||
import com.jetbrains.python.pathValidation.validateEmptyDir
|
||||
@@ -62,7 +64,10 @@ sealed interface FileSystem<P : PathHolder> {
|
||||
fun parsePath(raw: String): PyResult<P>
|
||||
fun validateExecutable(path: P): PyResult<Unit>
|
||||
|
||||
suspend fun getSystemPythonFromSelection(pathToPython: P): PyResult<DetectedSelectableInterpreter<P>>
|
||||
/**
|
||||
* [pathToPython] has to be system (not venv) if set [requireSystemPython]
|
||||
*/
|
||||
suspend fun getSystemPythonFromSelection(pathToPython: P, requireSystemPython: Boolean): PyResult<DetectedSelectableInterpreter<P>>
|
||||
|
||||
suspend fun validateVenv(homePath: P): PyResult<Unit>
|
||||
suspend fun suggestVenv(projectPath: Path): PyResult<P>
|
||||
@@ -106,7 +111,7 @@ sealed interface FileSystem<P : PathHolder> {
|
||||
!homePath.path.isAbsolute -> PyResult.localizedError(message("python.sdk.new.error.no.absolute"))
|
||||
homePath.path.exists() -> {
|
||||
val pythonBinaryPath = homePath.path.resolvePythonBinary()?.let { PathHolder.Eel(it) }
|
||||
val existingPython = pythonBinaryPath?.let { getSystemPythonFromSelection(it) }?.successOrNull
|
||||
val existingPython = pythonBinaryPath?.let { getSystemPythonFromSelection(it, requireSystemPython = false) }?.successOrNull
|
||||
if (existingPython == null) {
|
||||
PyResult.localizedError(message("sdk.create.custom.venv.folder.not.empty"))
|
||||
}
|
||||
@@ -126,12 +131,32 @@ sealed interface FileSystem<P : PathHolder> {
|
||||
parsePath(suggestedVirtualEnvPath)
|
||||
}
|
||||
|
||||
override suspend fun getSystemPythonFromSelection(pathToPython: PathHolder.Eel): PyResult<DetectedSelectableInterpreter<PathHolder.Eel>> {
|
||||
val systemPython = SystemPythonService().registerSystemPython(pathToPython.path).getOr { return it }
|
||||
override suspend fun getSystemPythonFromSelection(pathToPython: PathHolder.Eel, requireSystemPython: Boolean): PyResult<DetectedSelectableInterpreter<PathHolder.Eel>> {
|
||||
val sysPythonValidationInfo = SystemPythonService().registerSystemPython(pathToPython.path)
|
||||
val (vanillaPython, isSystem) = when (sysPythonValidationInfo) {
|
||||
is Result.Failure -> {
|
||||
if (requireSystemPython) {
|
||||
// Not a system python, error
|
||||
return Result.failure(sysPythonValidationInfo.error.asPyError)
|
||||
}
|
||||
else {
|
||||
when (val r = sysPythonValidationInfo.error) {
|
||||
// Not a system python, but we are ok with it
|
||||
is SysPythonRegisterError.NotASystemPython -> Pair(r.notSystemPython, false)
|
||||
// Not a python at all
|
||||
is SysPythonRegisterError.PythonIsBroken -> {
|
||||
return Result.failure(r.asPyError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Perfectly valid system python
|
||||
is Result.Success -> Pair(sysPythonValidationInfo.result, true)
|
||||
}
|
||||
val interpreter = DetectedSelectableInterpreter(
|
||||
homePath = PathHolder.Eel(systemPython.pythonBinary),
|
||||
pythonInfo = systemPython.pythonInfo,
|
||||
isBase = true
|
||||
homePath = PathHolder.Eel(vanillaPython.pythonBinary),
|
||||
pythonInfo = vanillaPython.pythonInfo,
|
||||
isBase = isSystem
|
||||
)
|
||||
|
||||
return PyResult.success(interpreter)
|
||||
@@ -215,7 +240,7 @@ sealed interface FileSystem<P : PathHolder> {
|
||||
override suspend fun validateVenv(homePath: PathHolder.Target): PyResult<Unit> = withContext(Dispatchers.IO) {
|
||||
val pythonBinaryPath = resolvePythonBinary(homePath)
|
||||
|
||||
val existingPython = getSystemPythonFromSelection(pythonBinaryPath).successOrNull
|
||||
val existingPython = getSystemPythonFromSelection(pythonBinaryPath, requireSystemPython = false).successOrNull
|
||||
val validationResult = if (existingPython == null) {
|
||||
val validationInfo = validateEmptyDir(
|
||||
ValidationRequest(
|
||||
@@ -275,14 +300,14 @@ sealed interface FileSystem<P : PathHolder> {
|
||||
return BinOnTarget(path.pathString, targetEnvironmentConfiguration)
|
||||
}
|
||||
|
||||
override suspend fun getSystemPythonFromSelection(pathToPython: PathHolder.Target): PyResult<DetectedSelectableInterpreter<PathHolder.Target>> {
|
||||
override suspend fun getSystemPythonFromSelection(pathToPython: PathHolder.Target, requireSystemPython: Boolean): PyResult<DetectedSelectableInterpreter<PathHolder.Target>> {
|
||||
return registerSystemPython(pathToPython)
|
||||
}
|
||||
|
||||
override suspend fun detectSelectableVenv(): List<DetectedSelectableInterpreter<PathHolder.Target>> {
|
||||
val fullPathOnTarget = pythonLanguageRuntimeConfiguration.pythonInterpreterPath
|
||||
val pathHolder = PathHolder.Target(fullPathOnTarget)
|
||||
val systemPython = getSystemPythonFromSelection(pathHolder).getOr { return emptyList() }
|
||||
val systemPython = getSystemPythonFromSelection(pathHolder, requireSystemPython = false).getOr { return emptyList() }
|
||||
return listOf(systemPython)
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ internal class PythonSdkPanelBuilderAndSdkCreator(
|
||||
title = message("sdk.create.python.version"),
|
||||
selectedSdkProperty = model.state.baseInterpreter,
|
||||
validationRequestor = validationRequestor,
|
||||
onPathSelected = model::addManuallyAddedInterpreter
|
||||
onPathSelected = model::addManuallyAddedSystemPython
|
||||
) {
|
||||
visibleIf(_projectVenv)
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ internal fun <P : PathHolder> Panel.buildHatchFormFields(
|
||||
title = message("sdk.create.custom.base.python"),
|
||||
selectedSdkProperty = model.state.baseInterpreter,
|
||||
validationRequestor = validationRequestor,
|
||||
onPathSelected = model::addManuallyAddedInterpreter,
|
||||
onPathSelected = model::addManuallyAddedSystemPython,
|
||||
)
|
||||
}
|
||||
}.visibleIf(model.hatchViewModel.hatchExecutable.transform { it?.validationResult?.successOrNull != null })
|
||||
|
||||
@@ -23,7 +23,6 @@ import com.jetbrains.python.errorProcessing.emit
|
||||
import com.jetbrains.python.getOrNull
|
||||
import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows
|
||||
import com.jetbrains.python.sdk.PySdkToInstall
|
||||
import com.jetbrains.python.sdk.PySdkUtil
|
||||
import com.jetbrains.python.sdk.add.v2.conda.CondaViewModel
|
||||
import com.jetbrains.python.sdk.add.v2.hatch.HatchViewModel
|
||||
import com.jetbrains.python.sdk.add.v2.pipenv.PipenvViewModel
|
||||
@@ -31,7 +30,6 @@ import com.jetbrains.python.sdk.add.v2.poetry.PoetryViewModel
|
||||
import com.jetbrains.python.sdk.add.v2.uv.UvViewModel
|
||||
import com.jetbrains.python.sdk.add.v2.venv.VenvViewModel
|
||||
import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.isSystemWide
|
||||
import com.jetbrains.python.target.ui.TargetPanelExtension
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
@@ -65,7 +63,7 @@ abstract class PythonAddInterpreterModel<P : PathHolder>(
|
||||
internal val knownInterpreters: MutableStateFlow<List<PythonSelectableInterpreter<P>>?> = MutableStateFlow(null)
|
||||
private val _detectedInterpreters: MutableStateFlow<List<DetectedSelectableInterpreter<P>>?> = MutableStateFlow(null)
|
||||
val detectedInterpreters: StateFlow<List<DetectedSelectableInterpreter<P>>?> = _detectedInterpreters
|
||||
val manuallyAddedInterpreters: MutableStateFlow<List<PythonSelectableInterpreter<P>>> = MutableStateFlow(emptyList())
|
||||
val manuallyAddedInterpreters: MutableStateFlow<List<ManuallyAddedSelectableInterpreter<P>>> = MutableStateFlow(emptyList())
|
||||
private var installable: List<InstallableSelectableInterpreter<P>> = emptyList()
|
||||
lateinit var allInterpreters: StateFlow<List<PythonSelectableInterpreter<P>>?>
|
||||
lateinit var baseInterpreters: StateFlow<List<PythonSelectableInterpreter<P>>?>
|
||||
@@ -113,11 +111,11 @@ abstract class PythonAddInterpreterModel<P : PathHolder>(
|
||||
all?.distinctBy { int -> int.homePath }?.sorted()
|
||||
}.stateIn(scope, started = SharingStarted.Eagerly, initialValue = null)
|
||||
|
||||
this.baseInterpreters = combine(
|
||||
detectedInterpreters,
|
||||
manuallyAddedInterpreters
|
||||
this.baseInterpreters = combine( // base pythons are always system only
|
||||
detectedInterpreters.map { it?.sysPythonsOnly() },
|
||||
manuallyAddedInterpreters.sysPythonsOnly()
|
||||
) { detected, manual ->
|
||||
val base = detected?.filter { it.isBase } ?: return@combine null
|
||||
val base = detected ?: return@combine null
|
||||
val existingLanguageLevels = base.map { it.pythonInfo.languageLevel }.toSet()
|
||||
val nonExistingInstallable = installable.filter { it.pythonInfo.languageLevel !in existingLanguageLevels }
|
||||
manual + base.sorted() + nonExistingInstallable
|
||||
@@ -125,28 +123,22 @@ abstract class PythonAddInterpreterModel<P : PathHolder>(
|
||||
}
|
||||
|
||||
|
||||
internal fun addManuallyAddedInterpreter(interpreter: PythonSelectableInterpreter<P>) {
|
||||
internal fun addManuallyAddedInterpreter(interpreter: ManuallyAddedSelectableInterpreter<P>) {
|
||||
manuallyAddedInterpreters.value += interpreter
|
||||
state.selectedInterpreter.set(interpreter)
|
||||
}
|
||||
|
||||
internal suspend fun addManuallyAddedInterpreter(homePath: P): PyResult<ManuallyAddedSelectableInterpreter<P>> {
|
||||
val python = homePath.let { fileSystem.getSystemPythonFromSelection(it) }.getOr { return it }
|
||||
internal suspend fun addManuallyAddedPythonNotNecessarilySystem(homePath: P) = addManuallyAddedInterpreter(homePath, requireSystemPython = false)
|
||||
internal suspend fun addManuallyAddedSystemPython(homePath: P) = addManuallyAddedInterpreter(homePath, requireSystemPython = true)
|
||||
private suspend fun addManuallyAddedInterpreter(homePath: P, requireSystemPython: Boolean): PyResult<ManuallyAddedSelectableInterpreter<P>> {
|
||||
val python = homePath.let { fileSystem.getSystemPythonFromSelection(it, requireSystemPython) }.getOr { return it }
|
||||
|
||||
val interpreter = ManuallyAddedSelectableInterpreter(homePath, python.pythonInfo).also {
|
||||
val interpreter = ManuallyAddedSelectableInterpreter(homePath, python.pythonInfo, isBase = python.isBase).also {
|
||||
this@PythonAddInterpreterModel.addManuallyAddedInterpreter(it)
|
||||
}
|
||||
return PyResult.success(interpreter)
|
||||
}
|
||||
|
||||
open fun addInterpreter(sdk: Sdk) {
|
||||
val interpreter = ExistingSelectableInterpreter(
|
||||
fileSystem.wrapSdk(sdk),
|
||||
PythonInfo(PySdkUtil.getLanguageLevelForSdk(sdk)),
|
||||
sdk.isSystemWide
|
||||
)
|
||||
this@PythonAddInterpreterModel.addManuallyAddedInterpreter(interpreter)
|
||||
}
|
||||
|
||||
@RequiresEdt
|
||||
internal fun addInstalledInterpreter(homePath: P, pythonInfo: PythonInfo): DetectedSelectableInterpreter<P> {
|
||||
@@ -176,6 +168,12 @@ class PythonLocalAddInterpreterModel<P : PathHolder>(projectPathFlows: ProjectPa
|
||||
}
|
||||
}
|
||||
|
||||
internal interface MaybeSystemPython {
|
||||
/**
|
||||
* System python can be used as a base python for other envs
|
||||
*/
|
||||
val isBase: Boolean
|
||||
}
|
||||
|
||||
sealed class PythonSelectableInterpreter<P : PathHolder> : Comparable<PythonSelectableInterpreter<*>>, UiHolder, PythonInfoHolder {
|
||||
companion object {
|
||||
@@ -204,14 +202,14 @@ class ExistingSelectableInterpreter<P : PathHolder>(
|
||||
}
|
||||
|
||||
/**
|
||||
* [isBase] is a system interpreter, see [isBasePython]
|
||||
* [isBase] is a system interpreter (aka system python)
|
||||
*/
|
||||
class DetectedSelectableInterpreter<P : PathHolder>(
|
||||
override val homePath: P,
|
||||
override val pythonInfo: PythonInfo,
|
||||
val isBase: Boolean,
|
||||
override val isBase: Boolean,
|
||||
override val ui: PyToolUIInfo? = null,
|
||||
) : PythonSelectableInterpreter<P>() {
|
||||
) : PythonSelectableInterpreter<P>(), MaybeSystemPython {
|
||||
override fun toString(): String {
|
||||
return "DetectedSelectableInterpreter(homePath='$homePath', pythonInfo=$pythonInfo, isBase=$isBase, uiCustomization=$ui)"
|
||||
}
|
||||
@@ -220,7 +218,8 @@ class DetectedSelectableInterpreter<P : PathHolder>(
|
||||
class ManuallyAddedSelectableInterpreter<P : PathHolder>(
|
||||
override val homePath: P,
|
||||
override val pythonInfo: PythonInfo,
|
||||
) : PythonSelectableInterpreter<P>() {
|
||||
override val isBase: Boolean,
|
||||
) : PythonSelectableInterpreter<P>(), MaybeSystemPython {
|
||||
override fun toString(): String {
|
||||
return "ManuallyAddedSelectableInterpreter(homePath='$homePath', pythonInfo=$pythonInfo)"
|
||||
}
|
||||
@@ -280,3 +279,6 @@ internal suspend fun PythonAddInterpreterModel<*>.getBasePath(module: Module?):
|
||||
|
||||
pyProjectTomlBased ?: module?.basePath?.let { Path.of(it) } ?: projectPathFlows.projectPathWithDefault.first()
|
||||
}
|
||||
|
||||
private fun <T : MaybeSystemPython> Flow<Iterable<T>>.sysPythonsOnly(): Flow<List<T>> = map { it.sysPythonsOnly() }
|
||||
private fun <T : MaybeSystemPython> Iterable<T>.sysPythonsOnly(): List<T> = filter { it.isBase }
|
||||
|
||||
@@ -187,7 +187,12 @@ internal fun <P : PathHolder> SimpleColoredComponent.customizeForPythonInterpret
|
||||
when (interpreter) {
|
||||
is DetectedSelectableInterpreter, is ManuallyAddedSelectableInterpreter -> {
|
||||
icon = IconLoader.getTransparentIcon(interpreter.ui?.icon ?: PythonParserIcons.PythonFile)
|
||||
val title = interpreter.ui?.toolName ?: message("sdk.rendering.detected.grey.text")
|
||||
val title = interpreter.ui?.toolName ?:
|
||||
if (interpreter.isBase) {
|
||||
message("sdk.rendering.detected.grey.text.system")
|
||||
}else {
|
||||
message("sdk.rendering.detected.grey.text.venv")
|
||||
}
|
||||
append(String.format("Python %-4s", interpreter.pythonInfo.languageLevel))
|
||||
append(" (" + replaceHomePathToTilde(interpreter.homePath.toString()) + ") $title", SimpleTextAttributes.GRAYED_SMALL_ATTRIBUTES)
|
||||
}
|
||||
|
||||
@@ -53,10 +53,7 @@ class EnvironmentCreatorVenv<P : PathHolder>(model: PythonMutableTargetAddInterp
|
||||
val venvAlreadyExistsError = venvAlreadyExistsError.get()
|
||||
|
||||
venvAlreadyExistsError?.let { error ->
|
||||
val interpreter = error.detectedSelectableInterpreter.also {
|
||||
model.addManuallyAddedInterpreter(it)
|
||||
}
|
||||
|
||||
val interpreter = error.detectedSelectableInterpreter
|
||||
model.state.selectedInterpreter.set(interpreter)
|
||||
model.navigator.navigateTo(
|
||||
newMethod = PythonInterpreterSelectionMethod.SELECT_EXISTING,
|
||||
@@ -71,7 +68,7 @@ class EnvironmentCreatorVenv<P : PathHolder>(model: PythonMutableTargetAddInterp
|
||||
title = message("sdk.create.custom.base.python"),
|
||||
selectedSdkProperty = model.state.baseInterpreter,
|
||||
validationRequestor = validationRequestor,
|
||||
onPathSelected = model::addManuallyAddedInterpreter,
|
||||
onPathSelected = model::addManuallyAddedSystemPython,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ class PythonExistingEnvironmentSelector<P : PathHolder>(model: PythonAddInterpre
|
||||
title = message("sdk.create.custom.python.path"),
|
||||
selectedSdkProperty = model.state.selectedInterpreter,
|
||||
validationRequestor = validationRequestor,
|
||||
onPathSelected = model::addManuallyAddedInterpreter,
|
||||
onPathSelected = model::addManuallyAddedPythonNotNecessarilySystem,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,10 +40,10 @@ private suspend fun <P : PathHolder> PythonAddInterpreterModel<P>.createSdkFromB
|
||||
val inheritSitePackages = venvViewModel.inheritSitePackages.get()
|
||||
createVenv(basePython, pathToVenvHome.toString(), inheritSitePackages).getOr(message("project.error.cant.venv")) { return it }
|
||||
|
||||
val pythonBinaryPath = fileSystem.resolvePythonBinary(pathToVenvHome)
|
||||
?: return PyResult.localizedError(message("commandLine.directoryCantBeAccessed", pathToVenvHome))
|
||||
val venvPythonBinaryPath = fileSystem.resolvePythonBinary(pathToVenvHome)
|
||||
?: return PyResult.localizedError(message("commandLine.directoryCantBeAccessed", pathToVenvHome))
|
||||
|
||||
val detectedSelectableInterpreter = fileSystem.getSystemPythonFromSelection(pythonBinaryPath).getOr { return it }
|
||||
val detectedSelectableInterpreter = fileSystem.getSystemPythonFromSelection(venvPythonBinaryPath, requireSystemPython = false).getOr { return it }
|
||||
|
||||
val sdkResult = detectedSelectableInterpreter.setupSdk(
|
||||
moduleOrProject = moduleOrProject,
|
||||
|
||||
Reference in New Issue
Block a user