mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 13:02:30 +07:00
PY-77226: Introduce InterpreterService.
This service is aimed to substitute various SDK-management tools in PyCharm. Begin with `InterpreterService()` function. GitOrigin-RevId: 368d56c4a78812fe81de941e5e5ce61a56d385e6
This commit is contained in:
committed by
intellij-monorepo-bot
parent
87108b9813
commit
a0231c334a
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@@ -1059,6 +1059,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/python/installer/intellij.python.community.impl.installer.iml" filepath="$PROJECT_DIR$/python/installer/intellij.python.community.impl.installer.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/poetry/intellij.python.community.impl.poetry.iml" filepath="$PROJECT_DIR$/python/poetry/intellij.python.community.impl.poetry.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/python-venv/intellij.python.community.impl.venv.iml" filepath="$PROJECT_DIR$/python/python-venv/intellij.python.community.impl.venv.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/interpreters/intellij.python.community.interpreters.iml" filepath="$PROJECT_DIR$/python/interpreters/intellij.python.community.interpreters.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/junit5Tests-framework/intellij.python.community.junit5Tests.framework.iml" filepath="$PROJECT_DIR$/python/junit5Tests-framework/intellij.python.community.junit5Tests.framework.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/junit5Tests-framework/conda/intellij.python.community.junit5Tests.framework.conda.iml" filepath="$PROJECT_DIR$/python/junit5Tests-framework/conda/intellij.python.community.junit5Tests.framework.conda.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/pluginCore/intellij.python.community.plugin.iml" filepath="$PROJECT_DIR$/python/pluginCore/intellij.python.community.plugin.iml" />
|
||||
|
||||
@@ -291,6 +291,7 @@ jvm_library(
|
||||
"@lib//:kaml",
|
||||
"//python/impl.helperLocator:community-helpersLocator",
|
||||
"//python/python-exec-service/execService.python",
|
||||
"//python/interpreters",
|
||||
],
|
||||
exports = [
|
||||
"//python/openapi:community",
|
||||
|
||||
@@ -188,5 +188,6 @@
|
||||
<orderEntry type="library" name="kaml" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.helpersLocator" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.execService.python" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.interpreters" />
|
||||
</component>
|
||||
</module>
|
||||
81
python/interpreters/BUILD.bazel
Normal file
81
python/interpreters/BUILD.bazel
Normal file
@@ -0,0 +1,81 @@
|
||||
### auto-generated section `build intellij.python.community.interpreters` start
|
||||
load("@community//build:tests-options.bzl", "jps_test")
|
||||
load("@rules_jvm//:jvm.bzl", "jvm_library", "jvm_resources", "jvm_test")
|
||||
|
||||
jvm_resources(
|
||||
name = "interpreters_resources",
|
||||
files = glob(["resources/**/*"]),
|
||||
strip_prefix = "resources"
|
||||
)
|
||||
|
||||
jvm_library(
|
||||
name = "interpreters",
|
||||
module_name = "intellij.python.community.interpreters",
|
||||
visibility = ["//visibility:public"],
|
||||
srcs = glob(["src/**/*.kt", "src/**/*.java"], allow_empty = True),
|
||||
deps = [
|
||||
"@lib//:kotlin-stdlib",
|
||||
"//python/services/shared",
|
||||
"@lib//:jetbrains-annotations",
|
||||
"//platform/extensions",
|
||||
"//python/python-sdk:sdk",
|
||||
"//python/python-psi-impl:psi-impl",
|
||||
"//platform/projectModel-api:projectModel",
|
||||
"//platform/util",
|
||||
"//platform/diagnostic",
|
||||
"//python/python-exec-service/execService.python",
|
||||
"//python/python-exec-service:community-execService",
|
||||
"//python/python-parser:parser",
|
||||
"//python/openapi:community",
|
||||
"//platform/core-api:core",
|
||||
"//platform/eel-provider",
|
||||
],
|
||||
runtime_deps = [":interpreters_resources"]
|
||||
)
|
||||
|
||||
jvm_library(
|
||||
name = "interpreters_test_lib",
|
||||
visibility = ["//visibility:public"],
|
||||
srcs = glob(["tests/**/*.kt", "tests/**/*.java"], allow_empty = True),
|
||||
associates = [":interpreters"],
|
||||
deps = [
|
||||
"@lib//:kotlin-stdlib",
|
||||
"//python/services/shared",
|
||||
"//python/services/shared:shared_test_lib",
|
||||
"@lib//:jetbrains-annotations",
|
||||
"//platform/extensions",
|
||||
"//python/python-sdk:sdk",
|
||||
"//python/python-sdk:sdk_test_lib",
|
||||
"//python/python-psi-impl:psi-impl",
|
||||
"//platform/projectModel-api:projectModel",
|
||||
"//platform/util",
|
||||
"//platform/diagnostic",
|
||||
"//python/python-exec-service/execService.python",
|
||||
"//python/python-exec-service/execService.python:execService.python_test_lib",
|
||||
"//python/python-exec-service:community-execService",
|
||||
"//python/python-exec-service:community-execService_test_lib",
|
||||
"//python/python-parser:parser",
|
||||
"//python/openapi:community",
|
||||
"//python/openapi:community_test_lib",
|
||||
"//platform/core-api:core",
|
||||
"//platform/eel-provider",
|
||||
"//python/junit5Tests-framework:community-junit5Tests-framework_test_lib",
|
||||
"@lib//:junit5",
|
||||
"@lib//:junit5Params",
|
||||
"//platform/testFramework/junit5",
|
||||
"//platform/testFramework/junit5:junit5_test_lib",
|
||||
"//platform/lang-core",
|
||||
"//python/pluginCore:community-plugin",
|
||||
"//python/python-venv:community-impl-venv",
|
||||
"//python/python-venv:community-impl-venv_test_lib",
|
||||
"//platform/execution",
|
||||
"//python/impl.helperLocator:community-helpersLocator",
|
||||
],
|
||||
runtime_deps = [":interpreters_resources"]
|
||||
)
|
||||
|
||||
jps_test(
|
||||
name = "interpreters_test",
|
||||
runtime_deps = [":interpreters_test_lib"]
|
||||
)
|
||||
### auto-generated section `build intellij.python.community.interpreters` end
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="com.intellij.python.community.interpreters" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<orderEntry type="library" name="kotlin-stdlib" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.services.shared" />
|
||||
<orderEntry type="library" name="jetbrains-annotations" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.extensions" />
|
||||
<orderEntry type="module" module-name="intellij.python.sdk" />
|
||||
<orderEntry type="module" module-name="intellij.python.psi.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.projectModel" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util" />
|
||||
<orderEntry type="module" module-name="intellij.platform.diagnostic" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.execService.python" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.execService" />
|
||||
<orderEntry type="module" module-name="intellij.python.parser" />
|
||||
<orderEntry type="module" module-name="intellij.python.community" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.eel.provider" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.junit5Tests.framework" scope="TEST" />
|
||||
<orderEntry type="library" scope="TEST" name="JUnit5" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="JUnit5Params" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.lang.core" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.plugin" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.impl.venv" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.execution" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.helpersLocator" scope="TEST" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,20 @@
|
||||
<idea-plugin>
|
||||
<dependencies>
|
||||
<module name="intellij.python.community.services.shared"/>
|
||||
<module name="intellij.python.sdk"/>
|
||||
<module name="intellij.python.psi.impl"/>
|
||||
<module name="intellij.python.community.execService.python"/>
|
||||
<module name="intellij.python.community.execService"/>
|
||||
<module name="intellij.python.parser"/>
|
||||
<module name="intellij.python.community"/>
|
||||
</dependencies>
|
||||
|
||||
<extensionPoints>
|
||||
<extensionPoint qualifiedName="Pythonid.interpreterProvider"
|
||||
dynamic="true"
|
||||
interface="com.intellij.python.community.interpreters.spi.InterpreterProvider"/>
|
||||
</extensionPoints>
|
||||
<extensions defaultExtensionNs="Pythonid">
|
||||
<interpreterProvider implementation="com.intellij.python.community.interpreters.impl.VanillaInterpreterProvider"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,3 @@
|
||||
py.interpreter.broken=Interpreter Home path {0} is invalid: {1}
|
||||
py.interpreter.no.version=Interpreter failed to report its version {0}
|
||||
py.unknown.type=Unknown interpreter type {0}. Plugin missing?
|
||||
62
python/interpreters/src/advancedApi/advancedApi.kt
Normal file
62
python/interpreters/src/advancedApi/advancedApi.kt
Normal file
@@ -0,0 +1,62 @@
|
||||
// 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.interpreters.advancedApi
|
||||
|
||||
import com.intellij.python.community.execService.*
|
||||
import com.intellij.python.community.execService.python.HelperName
|
||||
import com.intellij.python.community.execService.python.advancedApi.executeHelperAdvanced
|
||||
import com.intellij.python.community.execService.python.advancedApi.executePythonAdvanced
|
||||
import com.intellij.python.community.execService.python.advancedApi.validatePythonAndGetVersion
|
||||
import com.intellij.python.community.interpreters.ValidInterpreter
|
||||
import com.jetbrains.python.errorProcessing.PyExecResult
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
// This in advanced API, most probably you need "api.kt"
|
||||
|
||||
/**
|
||||
* Execute [python]
|
||||
*/
|
||||
suspend fun <T> ExecService.executePythonAdvanced(
|
||||
python: ValidInterpreter,
|
||||
argsBuilder: suspend ArgsBuilder.() -> Unit = {},
|
||||
options: ExecOptions = ExecOptions(),
|
||||
processInteractiveHandler: ProcessInteractiveHandler<T>,
|
||||
): PyResult<T> =
|
||||
executePythonAdvanced(python.asExecutablePython, argsBuilder, options, processInteractiveHandler)
|
||||
|
||||
|
||||
/**
|
||||
* Execute [helper] on [python]. For remote eels, [helper] is copied (but only one file!).
|
||||
*/
|
||||
suspend fun <T> ExecService.executeHelperAdvanced(
|
||||
python: ValidInterpreter,
|
||||
helper: HelperName,
|
||||
args: List<String> = emptyList(),
|
||||
options: ExecOptions = ExecOptions(),
|
||||
procListener: PyProcessListener? = null,
|
||||
processOutputTransformer: ProcessOutputTransformer<T>,
|
||||
): PyResult<T> = executeHelperAdvanced(python.asExecutablePython, helper, args, options, procListener, processOutputTransformer)
|
||||
|
||||
/**
|
||||
* Ensures that this python is executable and returns its version. Error if python is broken.
|
||||
*
|
||||
* Some pythons might be broken: they may be executable, even return a version, but still fail to execute it.
|
||||
* As we need workable pythons, we validate it by executing
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
suspend fun ValidInterpreter.validatePythonAndGetVersion(): PyResult<LanguageLevel> =
|
||||
ExecService().validatePythonAndGetVersion(asExecutablePython)
|
||||
|
||||
/**
|
||||
* Execute [helper] on [python]. For remote eels, [helper] is copied (but only one file!).
|
||||
* Returns `stdout`
|
||||
*/
|
||||
suspend fun ExecService.executeHelper(
|
||||
python: ValidInterpreter,
|
||||
helper: HelperName,
|
||||
args: List<String> = emptyList(),
|
||||
options: ExecOptions = ExecOptions(),
|
||||
procListener: PyProcessListener? = null,
|
||||
): PyResult<String> =
|
||||
executeHelperAdvanced(python.asExecutablePython, helper, args, options, procListener, ZeroCodeStdoutTransformer)
|
||||
5
python/interpreters/src/advancedApi/package-info.java
Normal file
5
python/interpreters/src/advancedApi/package-info.java
Normal file
@@ -0,0 +1,5 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@ApiStatus.Internal
|
||||
package com.intellij.python.community.interpreters.advancedApi;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
94
python/interpreters/src/api.kt
Normal file
94
python/interpreters/src/api.kt
Normal file
@@ -0,0 +1,94 @@
|
||||
// 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.interpreters
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.python.community.execService.ExecOptions
|
||||
import com.intellij.python.community.execService.ExecService
|
||||
import com.intellij.python.community.execService.PyProcessListener
|
||||
import com.intellij.python.community.execService.ZeroCodeStdoutTransformer
|
||||
import com.intellij.python.community.execService.impl.transformerToHandler
|
||||
import com.intellij.python.community.execService.python.HelperName
|
||||
import com.intellij.python.community.execService.python.advancedApi.executeHelperAdvanced
|
||||
import com.intellij.python.community.execService.python.advancedApi.executePythonAdvanced
|
||||
import com.intellij.python.community.interpreters.impl.InterpreterFields
|
||||
import com.intellij.python.community.interpreters.impl.InterpreterServiceImpl
|
||||
import com.intellij.python.community.services.shared.PythonWithLanguageLevel
|
||||
import com.intellij.python.community.services.shared.PythonWithUi
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import org.jetbrains.annotations.Nls
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
/**
|
||||
* Python interpreter can be either valid or invalid (broken at the moment it was loaded)
|
||||
*/
|
||||
sealed interface Interpreter : InterpreterFields
|
||||
|
||||
/**
|
||||
* Interpreter was usable at the moment it was loaded.
|
||||
* It has [ui] and [getReadableName] and can be used to execute code against [ExecService] (see extension functions)
|
||||
*/
|
||||
interface ValidInterpreter : PythonWithLanguageLevel, PythonWithUi, Interpreter
|
||||
|
||||
/**
|
||||
* At the moment of loading this interpreter was invalid due to [invalidMessage].
|
||||
*/
|
||||
interface InvalidInterpreter : Interpreter {
|
||||
val invalidMessage: @Nls String
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain it by means of eponymous function.
|
||||
*/
|
||||
interface InterpreterService {
|
||||
/**
|
||||
* Which interpreters might be used for this directory?
|
||||
*/
|
||||
suspend fun getInterpreters(projectDir: Path): List<Interpreter>
|
||||
|
||||
/**
|
||||
* Is there an [Interpreter] associated with this [module]?
|
||||
*/
|
||||
suspend fun getForModule(module: Module): Interpreter?
|
||||
}
|
||||
|
||||
/***
|
||||
* ```kotlin
|
||||
* InterpreterService().getInterpreters(path)
|
||||
* ```
|
||||
*/
|
||||
fun InterpreterService(): InterpreterService = InterpreterServiceImpl
|
||||
|
||||
|
||||
suspend fun InterpreterService.getValidInterpreters(projectDir: Path): List<ValidInterpreter> =
|
||||
getInterpreters(projectDir).mapNotNull {
|
||||
when (it) {
|
||||
is InvalidInterpreter -> null
|
||||
is ValidInterpreter -> it
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute [helper] on [python]. For remote eels, [helper] is copied (but only one file!).
|
||||
* Returns `stdout`
|
||||
*/
|
||||
suspend fun ExecService.executeHelper(
|
||||
python: ValidInterpreter,
|
||||
helper: HelperName,
|
||||
args: List<String> = emptyList(),
|
||||
options: ExecOptions = ExecOptions(),
|
||||
procListener: PyProcessListener? = null,
|
||||
): PyResult<String> =
|
||||
executeHelperAdvanced(python.asExecutablePython, helper, args, options, procListener, ZeroCodeStdoutTransformer)
|
||||
|
||||
/**
|
||||
* Execute command on [python].
|
||||
* Returns `stdout`
|
||||
*/
|
||||
suspend fun ExecService.executeGetStdout(
|
||||
python: ValidInterpreter,
|
||||
args: List<String> = emptyList(),
|
||||
options: ExecOptions = ExecOptions(),
|
||||
procListener: PyProcessListener? = null,
|
||||
): PyResult<String> =
|
||||
executePythonAdvanced(python.asExecutablePython, { addArgs(*args.toTypedArray()) }, options, transformerToHandler(procListener, ZeroCodeStdoutTransformer))
|
||||
60
python/interpreters/src/impl/InterpreterImpl.kt
Normal file
60
python/interpreters/src/impl/InterpreterImpl.kt
Normal file
@@ -0,0 +1,60 @@
|
||||
// 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.interpreters.impl
|
||||
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.python.community.execService.python.advancedApi.ExecutablePython
|
||||
import com.intellij.python.community.interpreters.Interpreter
|
||||
import com.intellij.python.community.interpreters.InvalidInterpreter
|
||||
import com.intellij.python.community.interpreters.ValidInterpreter
|
||||
import com.intellij.python.community.services.shared.PythonWithName
|
||||
import com.intellij.python.community.services.shared.UICustomization
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.sdk.PythonSdkAdditionalData
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.Nls
|
||||
import java.util.*
|
||||
|
||||
internal class ValidInterpreterImpl(
|
||||
override val languageLevel: LanguageLevel,
|
||||
override val asExecutablePython: ExecutablePython,
|
||||
private val mixin: SdkMixin,
|
||||
override val ui: UICustomization?,
|
||||
) : ValidInterpreter, InterpreterFields by mixin {
|
||||
override fun toString(): String {
|
||||
return "ValidInterpreterImpl(languageLevel=$languageLevel, asExecutablePython=$asExecutablePython)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal class InvalidInterpreterImpl(
|
||||
private val mixin: SdkMixin,
|
||||
override val invalidMessage: @Nls String,
|
||||
) : InvalidInterpreter, InterpreterFields by mixin {
|
||||
override fun toString(): String {
|
||||
return "InvalidInterpreterImpl(invalidMessage='$invalidMessage')"
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.NonExtendable
|
||||
interface InterpreterFields : PythonWithName {
|
||||
/**
|
||||
* Currently, interpreters are based on SDK. But this is an implementation detail to be changed soon.
|
||||
* Do not use this field unless absolutely necessary.
|
||||
*/
|
||||
val sdk: Sdk
|
||||
val id: UUID
|
||||
}
|
||||
|
||||
internal class SdkMixin(override val sdk: Sdk, data: PythonSdkAdditionalData) : InterpreterFields {
|
||||
override val id: UUID = data.uuid
|
||||
|
||||
override suspend fun getReadableName(): @Nls String = sdk.name
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Interpreter) return false
|
||||
|
||||
return id == other.id
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = id.hashCode()
|
||||
}
|
||||
110
python/interpreters/src/impl/InterpreterServiceImpl.kt
Normal file
110
python/interpreters/src/impl/InterpreterServiceImpl.kt
Normal file
@@ -0,0 +1,110 @@
|
||||
// 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.interpreters.impl
|
||||
|
||||
import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.python.community.execService.ExecService
|
||||
import com.intellij.python.community.execService.python.advancedApi.ExecutablePython
|
||||
import com.intellij.python.community.execService.python.advancedApi.validatePythonAndGetVersion
|
||||
import com.intellij.python.community.interpreters.Interpreter
|
||||
import com.intellij.python.community.interpreters.InterpreterService
|
||||
import com.intellij.python.community.interpreters.impl.PyInterpreterBundle.message
|
||||
import com.intellij.python.community.interpreters.spi.InterpreterProvider
|
||||
import com.intellij.python.community.services.shared.UICustomization
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.MessageError
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.sdk.PythonSdkAdditionalData
|
||||
import com.jetbrains.python.sdk.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.flavors.PyFlavorData
|
||||
import com.jetbrains.python.sdk.getOrCreateAdditionalData
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
|
||||
internal object InterpreterServiceImpl : InterpreterService {
|
||||
private val logger = fileLogger()
|
||||
override suspend fun getInterpreters(projectDir: Path): List<Interpreter> {
|
||||
|
||||
val sdkAndDatas = PythonSdkUtil.getAllSdks()
|
||||
.map { Pair(it.getOrCreateAdditionalData(), it) }
|
||||
.filter { (data, _) -> sdkApplicableToThePath(data, projectDir) }
|
||||
|
||||
|
||||
val result = mutableListOf<Interpreter>()
|
||||
for ((additionalData, sdk) in sdkAndDatas) {
|
||||
val interpreter = findInterpreter(additionalData, sdk)
|
||||
result.add(interpreter)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override suspend fun getForModule(module: Module): Interpreter? {
|
||||
val sdk = ModuleRootManager.getInstance(module).sdk ?: return null
|
||||
if (sdk.sdkAdditionalData !is PythonSdkAdditionalData) {
|
||||
return null
|
||||
}
|
||||
return findInterpreter(sdk.getOrCreateAdditionalData(), sdk)
|
||||
}
|
||||
|
||||
private suspend fun findInterpreter(
|
||||
additionalData: PythonSdkAdditionalData,
|
||||
sdk: Sdk,
|
||||
): Interpreter {
|
||||
val flavorData = additionalData.flavorAndData.data
|
||||
val provider = InterpreterProvider.providerForData(flavorData)
|
||||
val interpreter = if (provider == null) {
|
||||
InvalidInterpreterImpl(SdkMixin(sdk, additionalData), message("py.unknown.type", flavorData.javaClass))
|
||||
}
|
||||
else {
|
||||
createInterpreter(provider, sdk, additionalData, flavorData)
|
||||
}
|
||||
return interpreter
|
||||
}
|
||||
|
||||
|
||||
private fun sdkApplicableToThePath(data: PythonSdkAdditionalData, projectDir: Path): Boolean {
|
||||
val associatedPathStr = data.associatedModulePath ?: return true
|
||||
val associatedPath = try {
|
||||
Path(associatedPathStr)
|
||||
}
|
||||
catch (e: InvalidPathException) {
|
||||
logger.warn("Skipping broken sdk associated with $associatedPathStr", e)
|
||||
return false
|
||||
}
|
||||
return projectDir.startsWith(associatedPath)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T : PyFlavorData> createInterpreter(provider: InterpreterProvider<T>, sdk: Sdk, additionalData: PythonSdkAdditionalData, flavorData: T): Interpreter {
|
||||
val homePath = try {
|
||||
Path(sdk.homePath!!)
|
||||
}
|
||||
catch (e: InvalidPathException) {
|
||||
return InvalidInterpreterImpl(SdkMixin(sdk, additionalData), message("py.interpreter.broken", sdk.homePath!!, e.localizedMessage))
|
||||
}
|
||||
when (val r = provider.createExecutablePython(homePath, flavorData)) {
|
||||
is Result.Failure -> {
|
||||
return InvalidInterpreterImpl(SdkMixin(sdk, additionalData), r.error.message)
|
||||
}
|
||||
is Result.Success -> {
|
||||
val executablePython = r.result
|
||||
val languageLevel = sdk.versionString?.let { LanguageLevel.fromPythonVersionSafe(it) }
|
||||
?: ExecService().validatePythonAndGetVersion(executablePython).getOr {
|
||||
return InvalidInterpreterImpl(SdkMixin(sdk, additionalData), message("py.interpreter.no.version", it.error.message))
|
||||
}
|
||||
return ValidInterpreterImpl(languageLevel, executablePython, SdkMixin(sdk, additionalData), provider.ui)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class VanillaInterpreterProvider : InterpreterProvider<PyFlavorData.Empty> {
|
||||
override val ui: UICustomization? = null
|
||||
override val flavorDataClass: Class<PyFlavorData.Empty> = PyFlavorData.Empty::class.java
|
||||
|
||||
override suspend fun createExecutablePython(sdkHomePath: Path, flavorData: PyFlavorData.Empty): Result<ExecutablePython, MessageError> {
|
||||
return Result.success(ExecutablePython.vanillaExecutablePython(sdkHomePath))
|
||||
}
|
||||
}
|
||||
29
python/interpreters/src/impl/PyInterpreterBundle.kt
Normal file
29
python/interpreters/src/impl/PyInterpreterBundle.kt
Normal file
@@ -0,0 +1,29 @@
|
||||
// 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.interpreters.impl
|
||||
|
||||
import com.intellij.DynamicBundle
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import org.jetbrains.annotations.PropertyKey
|
||||
import java.util.function.Supplier
|
||||
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
|
||||
internal object PyInterpreterBundle {
|
||||
private const val BUNDLE_FQN: @NonNls String = "messages.PyInterpreterBundle"
|
||||
private val BUNDLE = DynamicBundle(PyInterpreterBundle::class.java, BUNDLE_FQN)
|
||||
|
||||
fun message(
|
||||
key: @PropertyKey(resourceBundle = BUNDLE_FQN) String,
|
||||
vararg params: Any,
|
||||
): @Nls String {
|
||||
return BUNDLE.getMessage(key, *params)
|
||||
}
|
||||
|
||||
fun messagePointer(
|
||||
key: @PropertyKey(resourceBundle = BUNDLE_FQN) String,
|
||||
vararg params: Any,
|
||||
): Supplier<String> {
|
||||
return BUNDLE.getLazyMessage(key, *params)
|
||||
}
|
||||
}
|
||||
5
python/interpreters/src/impl/package-info.java
Normal file
5
python/interpreters/src/impl/package-info.java
Normal file
@@ -0,0 +1,5 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@ApiStatus.Internal
|
||||
package com.intellij.python.community.interpreters.impl;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
9
python/interpreters/src/package-info.java
Normal file
9
python/interpreters/src/package-info.java
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
/**
|
||||
* API for python interpreters to be used instead of SDK, see `api.kt`
|
||||
* Entry point is {@link com.intellij.python.community.interpreters.InterpreterService}
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
package com.intellij.python.community.interpreters;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
32
python/interpreters/src/spi/InterpreterProvider.kt
Normal file
32
python/interpreters/src/spi/InterpreterProvider.kt
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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.interpreters.spi
|
||||
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.python.community.execService.python.advancedApi.ExecutablePython
|
||||
import com.intellij.python.community.interpreters.Interpreter
|
||||
import com.intellij.python.community.services.shared.UICustomization
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.MessageError
|
||||
import com.jetbrains.python.sdk.flavors.PyFlavorData
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Bridge between [Sdk] and [Interpreter].
|
||||
* Each [Sdk] has [PyFlavorData].
|
||||
*
|
||||
* [InterpreterProvider] must be found by [PyFlavorData] to convert [Interpreter]
|
||||
*/
|
||||
interface InterpreterProvider<T : PyFlavorData> {
|
||||
companion object {
|
||||
private val EP: ExtensionPointName<InterpreterProvider<*>> = ExtensionPointName<InterpreterProvider<*>>("Pythonid.interpreterProvider")
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : PyFlavorData> providerForData(data: T): InterpreterProvider<T>? =
|
||||
EP.extensionList.firstOrNull { it.flavorDataClass.isInstance(data) } as InterpreterProvider<T>?
|
||||
}
|
||||
|
||||
val ui: UICustomization?
|
||||
val flavorDataClass: Class<T>
|
||||
suspend fun createExecutablePython(sdkHomePath: Path, flavorData: T): Result<ExecutablePython, MessageError>
|
||||
}
|
||||
8
python/interpreters/src/spi/package-info.java
Normal file
8
python/interpreters/src/spi/package-info.java
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
/**
|
||||
* To be used by PyCharm exec team only
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
package com.intellij.python.community.interpreters.spi;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -0,0 +1,96 @@
|
||||
// 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.tests.interpreters
|
||||
|
||||
import com.intellij.openapi.application.edtWriteAction
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.roots.ModuleRootModificationUtil
|
||||
import com.intellij.python.community.execService.ExecService
|
||||
import com.intellij.python.community.helpersLocator.PythonHelpersLocator
|
||||
import com.intellij.python.community.impl.venv.tests.pyVenvFixture
|
||||
import com.intellij.python.community.interpreters.*
|
||||
import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase
|
||||
import com.intellij.python.junit5Tests.framework.env.pySdkFixture
|
||||
import com.intellij.testFramework.junit5.fixture.TestFixture
|
||||
import com.intellij.testFramework.junit5.fixture.moduleFixture
|
||||
import com.intellij.testFramework.junit5.fixture.projectFixture
|
||||
import com.intellij.testFramework.junit5.fixture.tempPathFixture
|
||||
import com.jetbrains.python.sdk.getOrCreateAdditionalData
|
||||
import com.jetbrains.python.sdk.setAssociationToPath
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.fail
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import kotlin.io.path.deleteIfExists
|
||||
import kotlin.io.path.pathString
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
@PyEnvTestCase
|
||||
class InterpreterServiceShowCaseTest {
|
||||
private val sdkFixture = pySdkFixture()
|
||||
private val validSdkFixture = sdkFixture.pyVenvFixture(tempPathFixture(prefix = "valid"), addToSdkTable = true)
|
||||
private val validSdkFixture2 = sdkFixture.pyVenvFixture(tempPathFixture(prefix = "valid2"), addToSdkTable = true)
|
||||
|
||||
private val invalidSdkFixture = sdkFixture.pyVenvFixture(tempPathFixture(prefix = "invalid"), addToSdkTable = true)
|
||||
private val sdkFixtureAnotherPath = sdkFixture.pyVenvFixture(tempPathFixture(prefix = "anotherpath"), addToSdkTable = true)
|
||||
|
||||
private val moduleFixture = projectFixture().moduleFixture()
|
||||
|
||||
|
||||
@Test
|
||||
fun testListInterpreters(@TempDir dir: Path): Unit = runBlocking {
|
||||
val interpreterService = InterpreterService()
|
||||
|
||||
edtWriteAction {
|
||||
val m = invalidSdkFixture.get().sdkModificator
|
||||
m.homePath += "junk"
|
||||
m.commitChanges()
|
||||
}
|
||||
|
||||
val validSdk = validSdkFixture.get()
|
||||
validSdk.setAssociationToPath(null)
|
||||
validSdkFixture2.get().setAssociationToPath(dir.pathString)
|
||||
sdkFixtureAnotherPath.get().setAssociationToPath(dir.resolveSibling("asdasd").pathString)
|
||||
|
||||
val module = moduleFixture.get()
|
||||
val expectedInterpreters = setOf(sdkFixture, validSdkFixture, validSdkFixture2,
|
||||
invalidSdkFixture).associateBy { it.id() }.toMutableMap()
|
||||
val interpreters = interpreterService.getInterpreters(dir)
|
||||
for (i in interpreters) {
|
||||
val sdk = expectedInterpreters.remove(i.id)?.get()
|
||||
Assertions.assertTrue(sdk != null, "Unexpected interpreter: $i")
|
||||
Assertions.assertEquals(sdk, i.sdk, "Wrong sdk")
|
||||
|
||||
ModuleRootModificationUtil.setModuleSdk(module, sdk!!)
|
||||
Assertions.assertEquals(sdk.getOrCreateAdditionalData().uuid, interpreterService.getForModule(module)!!.id, "No module set")
|
||||
|
||||
when (i) {
|
||||
is InvalidInterpreter -> {
|
||||
Assertions.assertEquals(invalidSdkFixture.id(), i.id, "Unexpected invalid interpreter $i")
|
||||
}
|
||||
is ValidInterpreter -> {
|
||||
val version = ExecService().executeGetStdout(i, listOf("--version")).orThrow().trim()
|
||||
Assertions.assertTrue(version.isNotBlank(), "No version returned")
|
||||
|
||||
val helperName = "file.py"
|
||||
val helper = PythonHelpersLocator.getCommunityHelpersRoot().resolve(helperName)
|
||||
try {
|
||||
helper.writeText("print(1)")
|
||||
val helperOutput = ExecService().executeHelper(i, helperName).orThrow().trim()
|
||||
Assertions.assertEquals("1", helperOutput, "Wrong helper output")
|
||||
}
|
||||
finally {
|
||||
helper.deleteIfExists()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (expectedInterpreters.isNotEmpty()) {
|
||||
fail("Missing interpreters: ${expectedInterpreters.values}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestFixture<Sdk>.id(): UUID = get().getOrCreateAdditionalData().uuid
|
||||
@@ -16,7 +16,21 @@ import org.jetbrains.annotations.ApiStatus.Internal
|
||||
* Sdk with environment info. Use it as a regular sdk, but env fixtures (venv, conda) might use [env]
|
||||
*/
|
||||
@Internal
|
||||
class SdkFixture<ENV : Any> internal constructor(private val sdk: Sdk, val env: ENV) : Sdk by sdk
|
||||
class SdkFixture<ENV : Any> internal constructor(private val sdk: Sdk, val env: ENV) : Sdk by sdk {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
|
||||
return when (other) {
|
||||
is SdkFixture<*> -> other.sdk == sdk
|
||||
is Sdk -> other == sdk
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return sdk.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
- name: intellij.python.community.services.shared
|
||||
- name: intellij.python.community.services.internal.impl
|
||||
- name: intellij.python.community.services.systemPython
|
||||
- name: intellij.python.community.interpreters
|
||||
- name: intellij.python.community.plugin.minor
|
||||
- name: intellij.python.community.plugin.minorRider
|
||||
- name: intellij.python.community.communityOnly
|
||||
|
||||
@@ -52,7 +52,7 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
|
||||
<module name="intellij.python.community.services.shared" loading="embedded"/>
|
||||
<module name="intellij.python.community.services.internal.impl" loading="embedded"/>
|
||||
<module name="intellij.python.community.services.systemPython" loading="embedded"/>
|
||||
|
||||
<module name="intellij.python.community.interpreters" loading="embedded"/>
|
||||
<!--Mini-IDes support community python only-->
|
||||
<module name="intellij.python.community.plugin.minor"/> <!-- Python for Mini-IDEs-->
|
||||
<module name="intellij.python.community.plugin.minorRider"/> <!-- Python special support for Rider -->
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<module name="intellij.python.community.helpersLocator"/>
|
||||
<module name="intellij.python.community.execService"/>
|
||||
<module name="intellij.python.community.execService.python"/>
|
||||
<module name="intellij.python.community.interpreters"/>
|
||||
</dependencies>
|
||||
|
||||
<resource-bundle>messages.PyBundle</resource-bundle>
|
||||
|
||||
Reference in New Issue
Block a user