mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-04 17:20:55 +07:00
Python: extract python-specific extensions from exec service to simplify API and make it extendable for intepreters.
Use `ExecService` `api.kt` to exec any binary and extensions from `execService.python/api.kt` for python-specific things (i.e helpers) GitOrigin-RevId: bb217798a9d1ee886c4b12220ec1f66a5ef08336
This commit is contained in:
committed by
intellij-monorepo-bot
parent
fd2ad62299
commit
2e14347844
1
.idea/modules.xml
generated
1
.idea/modules.xml
generated
@@ -1045,6 +1045,7 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/python/intellij.python.community.communityOnly/intellij.python.community.communityOnly.iml" filepath="$PROJECT_DIR$/python/intellij.python.community.communityOnly/intellij.python.community.communityOnly.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/python-core-impl/intellij.python.community.core.impl.iml" filepath="$PROJECT_DIR$/python/python-core-impl/intellij.python.community.core.impl.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/python-exec-service/intellij.python.community.execService.iml" filepath="$PROJECT_DIR$/python/python-exec-service/intellij.python.community.execService.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/python-exec-service/execService.python/intellij.python.community.execService.python.iml" filepath="$PROJECT_DIR$/python/python-exec-service/execService.python/intellij.python.community.execService.python.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/impl.helperLocator/intellij.python.community.helpersLocator.iml" filepath="$PROJECT_DIR$/python/impl.helperLocator/intellij.python.community.helpersLocator.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/intellij.python.community.impl.iml" filepath="$PROJECT_DIR$/python/intellij.python.community.impl.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/python/huggingFace/intellij.python.community.impl.huggingFace.iml" filepath="$PROJECT_DIR$/python/huggingFace/intellij.python.community.impl.huggingFace.iml" />
|
||||
|
||||
@@ -4,19 +4,12 @@
|
||||
package com.intellij.platform.eel.provider.utils
|
||||
|
||||
import com.intellij.openapi.util.IntellijInternalApi
|
||||
import com.intellij.platform.eel.*
|
||||
import com.intellij.platform.eel.provider.getEelDescriptor
|
||||
import com.intellij.platform.eel.EelProcess
|
||||
import com.intellij.util.io.computeDetached
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.pathString
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
/**
|
||||
* To simplify [EelProcessExecutionResult] delegation
|
||||
@@ -67,24 +60,3 @@ suspend fun EelProcess.awaitProcessResult(): EelProcessExecutionResult {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given [this] is a binary, executes it with [args] and returns either [EelExecApi.ExecuteProcessError] (couldn't execute) or
|
||||
* [ProcessOutput] as a result of the execution.
|
||||
* If [timeout] elapsed then return value is an error with `null`.
|
||||
* ```kotlin
|
||||
* withTimeout(10.seconds) {python.exec("-v")}.getOr{return it}
|
||||
* ```
|
||||
*/
|
||||
@ThrowsChecked(ExecuteProcessException::class)
|
||||
@ApiStatus.Internal
|
||||
@ApiStatus.Experimental
|
||||
suspend fun Path.exec(vararg args: String, timeout: Duration = Int.MAX_VALUE.days): EelProcessExecutionResult {
|
||||
val process = getEelDescriptor().toEelApi().exec.spawnProcess(pathString, *args).eelIt()
|
||||
return withTimeoutOrNull(timeout) {
|
||||
process.awaitProcessResult()
|
||||
} ?: run {
|
||||
process.kill()
|
||||
throw ExecuteProcessException(-1, "Timeout exceeded: $timeout")
|
||||
}
|
||||
}
|
||||
@@ -282,6 +282,7 @@ jvm_library(
|
||||
"@lib//:http-client",
|
||||
"@lib//:commons-lang3",
|
||||
"//python/impl.helperLocator:community-helpersLocator",
|
||||
"//python/python-exec-service/execService.python",
|
||||
],
|
||||
exports = [
|
||||
"//python/openapi:community",
|
||||
|
||||
@@ -180,5 +180,6 @@
|
||||
<orderEntry type="library" name="http-client" level="project" />
|
||||
<orderEntry type="library" name="commons-lang3" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.helpersLocator" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.execService.python" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,5 +1,7 @@
|
||||
// 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
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
@@ -11,3 +13,11 @@ typealias PythonBinary = Path
|
||||
* python home directory, virtual environment or a base one.
|
||||
*/
|
||||
typealias PythonHomePath = Path
|
||||
|
||||
/**
|
||||
* `
|
||||
* python --version
|
||||
` *
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
const val PYTHON_VERSION_ARG: String = "--version"
|
||||
@@ -48,6 +48,7 @@
|
||||
- name: intellij.python.community
|
||||
- name: intellij.python.community.impl
|
||||
- name: intellij.python.community.execService
|
||||
- name: intellij.python.community.execService.python
|
||||
- name: intellij.python.community.impl.installer
|
||||
- name: intellij.python.pydev
|
||||
- name: intellij.python.community.impl.venv
|
||||
|
||||
@@ -45,6 +45,7 @@ The Python plug-in provides smart editing for Python scripts. The feature set of
|
||||
<module name="intellij.python.community" loading="embedded"/>
|
||||
<module name="intellij.python.community.impl" loading="embedded"/>
|
||||
<module name="intellij.python.community.execService" loading="embedded"/>
|
||||
<module name="intellij.python.community.execService.python" loading="embedded"/>
|
||||
<module name="intellij.python.community.impl.installer" loading="embedded"/>
|
||||
<module name="intellij.python.pydev" loading="embedded"/>
|
||||
<module name="intellij.python.community.impl.venv" loading="embedded"/>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<module name="intellij.python.sdk"/>
|
||||
<module name="intellij.python.community"/>
|
||||
<module name="intellij.python.community.helpersLocator"/>
|
||||
<module name="intellij.python.community.execService"/>
|
||||
<module name="intellij.python.community.execService.python"/>
|
||||
</dependencies>
|
||||
|
||||
<resource-bundle>messages.PyBundle</resource-bundle>
|
||||
|
||||
@@ -23,7 +23,6 @@ jvm_library(
|
||||
"//platform/core-api:core",
|
||||
"//platform/eel",
|
||||
"//platform/util/progress",
|
||||
"//python/impl.helperLocator:community-helpersLocator",
|
||||
],
|
||||
runtime_deps = [":community-execService_resources"]
|
||||
)
|
||||
@@ -54,7 +53,6 @@ jvm_library(
|
||||
"@lib//:junit5Pioneer",
|
||||
"//platform/testFramework/common",
|
||||
"//platform/util/progress",
|
||||
"//python/impl.helperLocator:community-helpersLocator",
|
||||
],
|
||||
runtime_deps = [":community-execService_resources"]
|
||||
)
|
||||
|
||||
2
python/python-exec-service/README.txt
Normal file
2
python/python-exec-service/README.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Execution services to run code locally or remotely. Python-agnostic
|
||||
Start with `api.kt`, do not touch "advanced" functions.
|
||||
66
python/python-exec-service/execService.python/BUILD.bazel
Normal file
66
python/python-exec-service/execService.python/BUILD.bazel
Normal file
@@ -0,0 +1,66 @@
|
||||
### auto-generated section `build intellij.python.community.execService.python` start
|
||||
load("@rules_jvm//:jvm.bzl", "jvm_library", "jvm_resources", "jvm_test")
|
||||
|
||||
jvm_resources(
|
||||
name = "execService.python_resources",
|
||||
files = glob(["resources/**/*"]),
|
||||
strip_prefix = "resources"
|
||||
)
|
||||
|
||||
jvm_library(
|
||||
name = "execService.python",
|
||||
module_name = "intellij.python.community.execService.python",
|
||||
visibility = ["//visibility:public"],
|
||||
srcs = glob(["src/**/*.kt", "src/**/*.java"], allow_empty = True),
|
||||
deps = [
|
||||
"@lib//:kotlin-stdlib",
|
||||
"//python/python-exec-service:community-execService",
|
||||
"//python/openapi:community",
|
||||
"@lib//:jetbrains-annotations",
|
||||
"@lib//:kotlinx-coroutines-core",
|
||||
"//platform/eel",
|
||||
"//platform/eel-provider",
|
||||
"//python/impl.helperLocator:community-helpersLocator",
|
||||
"//platform/util",
|
||||
"//platform/core-api:core",
|
||||
"//python/python-sdk:sdk",
|
||||
],
|
||||
runtime_deps = [":execService.python_resources"]
|
||||
)
|
||||
|
||||
jvm_library(
|
||||
name = "execService.python_test_lib",
|
||||
visibility = ["//visibility:public"],
|
||||
srcs = glob(["tests/**/*.kt", "tests/**/*.java"], allow_empty = True),
|
||||
associates = [":execService.python"],
|
||||
deps = [
|
||||
"@lib//:kotlin-stdlib",
|
||||
"//python/python-exec-service:community-execService",
|
||||
"//python/python-exec-service:community-execService_test_lib",
|
||||
"//python/openapi:community",
|
||||
"//python/openapi:community_test_lib",
|
||||
"@lib//:jetbrains-annotations",
|
||||
"@lib//:kotlinx-coroutines-core",
|
||||
"//platform/eel",
|
||||
"//platform/eel-provider",
|
||||
"//python/impl.helperLocator:community-helpersLocator",
|
||||
"@lib//:junit5",
|
||||
"//platform/testFramework/junit5",
|
||||
"//platform/testFramework/junit5:junit5_test_lib",
|
||||
"//python/junit5Tests-framework:community-junit5Tests-framework_test_lib",
|
||||
"//platform/testFramework/junit5/eel",
|
||||
"//platform/testFramework/junit5/eel:eel_test_lib",
|
||||
"@lib//:junit5Params",
|
||||
"//platform/util",
|
||||
"//platform/core-api:core",
|
||||
"//python/python-sdk:sdk",
|
||||
"//python/python-sdk:sdk_test_lib",
|
||||
],
|
||||
runtime_deps = [":execService.python_resources"]
|
||||
)
|
||||
|
||||
jvm_test(
|
||||
name = "execService.python_test",
|
||||
runtime_deps = [":execService.python_test_lib"]
|
||||
)
|
||||
### auto-generated section `build intellij.python.community.execService.python` end
|
||||
2
python/python-exec-service/execService.python/README.txt
Normal file
2
python/python-exec-service/execService.python/README.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
Execution service extensions for python-specific things like helpers.
|
||||
Start with `api.kt`, do not touch "advanced" functions.
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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.execService.python" />
|
||||
<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.execService" />
|
||||
<orderEntry type="module" module-name="intellij.python.community" />
|
||||
<orderEntry type="library" name="jetbrains-annotations" level="project" />
|
||||
<orderEntry type="library" name="kotlinx-coroutines-core" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.eel" />
|
||||
<orderEntry type="module" module-name="intellij.platform.eel.provider" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.helpersLocator" />
|
||||
<orderEntry type="library" scope="TEST" name="JUnit5" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.junit5Tests.framework" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5.eel" scope="TEST" />
|
||||
<orderEntry type="library" scope="TEST" name="JUnit5Params" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core" />
|
||||
<orderEntry type="module" module-name="intellij.python.sdk" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,6 @@
|
||||
<idea-plugin>
|
||||
<dependencies>
|
||||
<module name="intellij.python.community.execService"/>
|
||||
<module name="intellij.python.community.helpersLocator"/>
|
||||
</dependencies>
|
||||
</idea-plugin>
|
||||
@@ -0,0 +1,5 @@
|
||||
py.exec.defaultName.process=Process {0}
|
||||
|
||||
python.get.version.error={0} returned error: {1}
|
||||
python.get.version.too.long={0} took too long
|
||||
python.get.version.wrong.version={0} has a wrong version: {1}
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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
|
||||
package com.intellij.python.community.execService.python
|
||||
|
||||
/**
|
||||
* Name of the helper file in helpers module
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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.advancedApi
|
||||
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Something that can execute python code (vanilla cpython, conda).
|
||||
* `[binary] [args]` <python-args-go-here> (i.e `-m foo.py`).
|
||||
* For [VanillaExecutablePython] it is `python` without arguments, but for conda it might be `conda run` etc
|
||||
*/
|
||||
interface ExecutablePython {
|
||||
val binary: Path
|
||||
val args: List<String>
|
||||
|
||||
companion object {
|
||||
class VanillaExecutablePython(override val binary: PythonBinary) : ExecutablePython {
|
||||
override val args: List<String> = emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// 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.advancedApi
|
||||
|
||||
import com.intellij.python.community.execService.*
|
||||
import com.intellij.python.community.execService.impl.transformerToHandler
|
||||
import com.intellij.python.community.execService.python.HelperName
|
||||
import com.intellij.python.community.execService.python.impl.validatePythonAndGetVersionImpl
|
||||
import com.intellij.python.community.helpersLocator.PythonHelpersLocator
|
||||
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: ExecutablePython,
|
||||
argsBuilder: suspend ArgsBuilder.() -> Unit = {},
|
||||
options: ExecOptions = ExecOptions(),
|
||||
processInteractiveHandler: ProcessInteractiveHandler<T>,
|
||||
): PyExecResult<T> =
|
||||
executeAdvanced(python.binary, {
|
||||
addArgs(*python.args.toTypedArray())
|
||||
argsBuilder()
|
||||
}, options, processInteractiveHandler)
|
||||
|
||||
|
||||
/**
|
||||
* Execute [helper] on [python]. For remote eels, [helper] is copied (but only one file!).
|
||||
*/
|
||||
suspend fun <T> ExecService.executeHelperAdvanced(
|
||||
python: ExecutablePython,
|
||||
helper: HelperName,
|
||||
args: List<String> = emptyList(),
|
||||
options: ExecOptions = ExecOptions(),
|
||||
procListener: PyProcessListener? = null,
|
||||
processOutputTransformer: ProcessOutputTransformer<T>,
|
||||
): PyExecResult<T> = executePythonAdvanced(python, {
|
||||
addLocalFile(PythonHelpersLocator.findPathInHelpers(helper))
|
||||
addArgs(*args.toTypedArray())
|
||||
|
||||
}, options, transformerToHandler(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 ExecService.validatePythonAndGetVersion(python: ExecutablePython): PyResult<LanguageLevel> =
|
||||
validatePythonAndGetVersionImpl(python)
|
||||
@@ -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.execService.python.advancedApi;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
41
python/python-exec-service/execService.python/src/api.kt
Normal file
41
python/python-exec-service/execService.python/src/api.kt
Normal file
@@ -0,0 +1,41 @@
|
||||
// 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.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.python.advancedApi.ExecutablePython.Companion.VanillaExecutablePython
|
||||
import com.intellij.python.community.execService.python.advancedApi.executeHelperAdvanced
|
||||
import com.intellij.python.community.execService.python.advancedApi.validatePythonAndGetVersion
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.errorProcessing.PyExecResult
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
/**
|
||||
* Execute [helper] on [python]. For remote eels, [helper] is copied (but only one file!).
|
||||
* Returns `stdout`
|
||||
*/
|
||||
suspend fun ExecService.executeHelper(
|
||||
python: PythonBinary,
|
||||
helper: HelperName,
|
||||
args: List<String> = emptyList(),
|
||||
options: ExecOptions = ExecOptions(),
|
||||
procListener: PyProcessListener? = null,
|
||||
): PyExecResult<String> =
|
||||
executeHelperAdvanced(VanillaExecutablePython(python), helper, args, options, procListener, ZeroCodeStdoutTransformer)
|
||||
|
||||
|
||||
/**
|
||||
* 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 ExecService.validatePythonAndGetVersion(python: PythonBinary): PyResult<LanguageLevel> =
|
||||
validatePythonAndGetVersion(VanillaExecutablePython(python))
|
||||
|
||||
suspend fun PythonBinary.validatePythonAndGetVersion(): PyResult<LanguageLevel> = ExecService().validatePythonAndGetVersion(this)
|
||||
@@ -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.execService.python.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 PyExecPythonBundle {
|
||||
private const val BUNDLE_FQN: @NonNls String = "messages.PyExecPythonBundle"
|
||||
private val BUNDLE = DynamicBundle(PyExecPythonBundle::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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// 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.impl
|
||||
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.platform.eel.provider.utils.EelProcessExecutionResult
|
||||
import com.intellij.platform.eel.provider.utils.stderrString
|
||||
import com.intellij.platform.eel.provider.utils.stdoutString
|
||||
import com.intellij.python.community.execService.ExecOptions
|
||||
import com.intellij.python.community.execService.ExecService
|
||||
import com.intellij.python.community.execService.ZeroCodeStdoutTransformer
|
||||
import com.intellij.python.community.execService.impl.transformerToHandler
|
||||
import com.intellij.python.community.execService.python.advancedApi.ExecutablePython
|
||||
import com.intellij.python.community.execService.python.advancedApi.executePythonAdvanced
|
||||
import com.intellij.python.community.execService.python.impl.PyExecPythonBundle.message
|
||||
import com.jetbrains.python.PYTHON_VERSION_ARG
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.ExecError
|
||||
import com.jetbrains.python.errorProcessing.ExecErrorReason
|
||||
import com.jetbrains.python.errorProcessing.MessageError
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor.getLanguageLevelFromVersionStringStaticSafe
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import kotlin.io.path.pathString
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@ApiStatus.Internal
|
||||
internal suspend fun ExecService.validatePythonAndGetVersionImpl(python: ExecutablePython): PyResult<LanguageLevel> = withContext(Dispatchers.IO) {
|
||||
val options = ExecOptions(timeout = 1.minutes)
|
||||
|
||||
val result: PyResult<LanguageLevel> = run {
|
||||
val smokeTestOutput = executePythonAdvanced(python, { addArgs("-c", "print(1)") }, processInteractiveHandler = transformerToHandler(null, ZeroCodeStdoutTransformer), options = options).getOr { return@run it }.trim()
|
||||
if (smokeTestOutput != "1") {
|
||||
return@run PyResult.localizedError(message("python.get.version.error", python.userReadableName, smokeTestOutput))
|
||||
}
|
||||
|
||||
val versionOutput: EelProcessExecutionResult = executePythonAdvanced(python, options = options, argsBuilder = { addArgs(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))
|
||||
})).getOr { return@run it }
|
||||
// Python 2 might return version as stderr, see https://bugs.python.org/issue18338
|
||||
val versionString = versionOutput.stdoutString.let { it.ifBlank { versionOutput.stderrString } }
|
||||
val languageLevel = getLanguageLevelFromVersionStringStaticSafe(versionString.trim())
|
||||
if (languageLevel == null) {
|
||||
return@run PyResult.localizedError(message("python.get.version.wrong.version", python.userReadableName, versionOutput))
|
||||
}
|
||||
return@run Result.success(languageLevel)
|
||||
}
|
||||
// Return more readable error to user in case of timeout
|
||||
when (result) {
|
||||
is Result.Success -> Unit
|
||||
is Result.Failure -> {
|
||||
when (val e = result.error) {
|
||||
is ExecError -> {
|
||||
when (e.errorReason) {
|
||||
is ExecErrorReason.CantStart, is ExecErrorReason.UnexpectedProcessTermination -> Unit
|
||||
ExecErrorReason.Timeout -> {
|
||||
return@withContext Result.localizedError(message("python.get.version.too.long", python.userReadableName))
|
||||
}
|
||||
}
|
||||
}
|
||||
is MessageError -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
return@withContext result
|
||||
}
|
||||
|
||||
private val ExecutablePython.userReadableName: @NlsSafe String get() = (listOf(binary.pathString) + args).joinToString(" ")
|
||||
@@ -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.execService.python.impl;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -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.execService.python;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
@@ -0,0 +1,64 @@
|
||||
// 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.platform.eel.provider.asNioPath
|
||||
import com.intellij.platform.eel.provider.localEel
|
||||
import com.intellij.platform.testFramework.junit5.eel.params.api.*
|
||||
import com.intellij.python.community.execService.ExecService
|
||||
import com.intellij.python.community.execService.python.executeHelper
|
||||
import com.intellij.python.community.execService.python.validatePythonAndGetVersion
|
||||
import com.intellij.python.community.helpersLocator.PythonHelpersLocator
|
||||
import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase
|
||||
import com.intellij.python.junit5Tests.framework.env.PythonBinaryPath
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.getOrThrow
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.condition.OS
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.deleteExisting
|
||||
import kotlin.io.path.name
|
||||
import kotlin.io.path.writeText
|
||||
|
||||
@TestApplicationWithEel(osesMayNotHaveRemoteEels = [OS.WINDOWS, OS.MAC, OS.LINUX])
|
||||
@PyEnvTestCase
|
||||
class HelpersShowCaseTest() {
|
||||
|
||||
@WslTest("ubuntu", mandatory = false)
|
||||
@DockerTest(image = "python:3.13.4", mandatory = false)
|
||||
@EelSource
|
||||
@ParameterizedTest
|
||||
fun testHelpersWinExample(
|
||||
eelHolder: EelHolder,
|
||||
@PythonBinaryPath localPython: PythonBinary,
|
||||
): Unit = runBlocking {
|
||||
val eel = eelHolder.eel
|
||||
// We might not have local python, so we use one from tests
|
||||
// Unlike docker/wsl, we can't choose local environment
|
||||
val python: Path = if (eel == localEel) localPython else eel.exec.findExeFilesInPath("python3").first().asNioPath()
|
||||
|
||||
val helper = PythonHelpersLocator.getCommunityHelpersRoot().resolve("file.py")
|
||||
val hello = "hello"
|
||||
try {
|
||||
withContext(Dispatchers.IO) {
|
||||
helper.writeText("""
|
||||
print("$hello")
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
val output = ExecService().executeHelper(python, helper.name, listOf("--version")).orThrow().trim()
|
||||
Assertions.assertEquals(hello, output, "wrong helper output")
|
||||
|
||||
val langLevel = ExecService().validatePythonAndGetVersion(python).getOrThrow()
|
||||
Assertions.assertTrue(langLevel.isPy3K, "Wrong lang level:$langLevel")
|
||||
}
|
||||
finally {
|
||||
withContext(Dispatchers.IO) {
|
||||
helper.deleteExisting()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// 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.python.validatePythonAndGetVersion
|
||||
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 kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@PyEnvTestCase
|
||||
class PythonBinaryValidationTest {
|
||||
@Test
|
||||
fun sunnyDayTest(@PythonBinaryPath python: PythonBinary): Unit = runBlocking {
|
||||
val level = python.validatePythonAndGetVersion().orThrow()
|
||||
Assertions.assertNotNull(level, "Failed to get python level")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rainyDayTest(): Unit = runBlocking {
|
||||
when (val r = randomBinary.validatePythonAndGetVersion()) {
|
||||
is Result.Success -> {
|
||||
Assertions.fail("${randomBinary} isn't a python, should fail, but got ${r.result}")
|
||||
}
|
||||
is Result.Failure -> {
|
||||
Assertions.assertTrue(r.error.message.isNotBlank(), "No error returned")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,5 @@
|
||||
<orderEntry type="library" scope="TEST" name="JUnit5Pioneer" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework.common" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.progress" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.helpersLocator" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,5 +1,4 @@
|
||||
py.exec.defaultName.process=Process
|
||||
py.exec.defaultName.helper=Helper
|
||||
py.exec.defaultName.process=Process {0}
|
||||
|
||||
py.exec.start.error={0} Failed to Start: {1} (Code {2})
|
||||
py.exec.timeout.error={0} Timed out (Run More Than {1})
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.intellij.python.community.execService
|
||||
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Listens for start/stop/std{out,err} events
|
||||
@@ -10,7 +11,7 @@ import org.jetbrains.annotations.ApiStatus
|
||||
typealias PyProcessListener = FlowCollector<ProcessEvent>
|
||||
|
||||
sealed interface ProcessEvent {
|
||||
data class ProcessStarted @ApiStatus.Internal constructor(val whatToExec: WhatToExec, val args: List<String>) : ProcessEvent
|
||||
data class ProcessStarted @ApiStatus.Internal constructor(val binary: Path, val args: List<String>) : ProcessEvent
|
||||
data class ProcessOutput @ApiStatus.Internal constructor(val stream: OutputType, val line: String) : ProcessEvent
|
||||
data class ProcessEnded @ApiStatus.Internal constructor(val exitCode: Int) : ProcessEvent
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import kotlinx.coroutines.Deferred
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.CheckReturnValue
|
||||
import org.jetbrains.annotations.Nls
|
||||
import java.nio.file.Path
|
||||
|
||||
|
||||
// This is an advanced API, consider using basic api.kt
|
||||
@@ -25,16 +26,19 @@ import org.jetbrains.annotations.Nls
|
||||
interface ExecService {
|
||||
|
||||
/**
|
||||
* TL;TR: Use extension functions from `api.kt`, do not use it directly!
|
||||
*
|
||||
* Execute code in a so-called "interactive" mode.
|
||||
* This is a quite advanced mode where *you* are responsible for converting a process to output.
|
||||
* You must listen for process stdout/stderr e.t.c.
|
||||
* Use it if you need to get some info from a process before it ends or to interact (i.e. write into stdin).
|
||||
* See [ProcessInteractiveHandler] and [processSemiInteractiveHandler]
|
||||
* See [ProcessInteractiveHandler] and [processSemiInteractiveHandler].
|
||||
* [argsBuilder] is a lambda to build args, see [ArgsBuilder]
|
||||
*/
|
||||
@CheckReturnValue
|
||||
suspend fun <T> execute(
|
||||
whatToExec: WhatToExec,
|
||||
args: List<String> = emptyList(),
|
||||
suspend fun <T> executeAdvanced(
|
||||
binary: Path,
|
||||
argsBuilder: suspend ArgsBuilder.() -> Unit = {},
|
||||
options: ExecOptions = ExecOptions(),
|
||||
processInteractiveHandler: ProcessInteractiveHandler<T>,
|
||||
): PyExecResult<T>
|
||||
@@ -56,7 +60,7 @@ fun interface ProcessInteractiveHandler<T> {
|
||||
* In latter case returns [EelProcessExecutionResult] (created out of collected output) and optional error message.
|
||||
* If no message returned -- the default one is used.
|
||||
*/
|
||||
suspend fun getResultFromProcess(whatToExec: WhatToExec, args: List<String>, process: EelProcess): Result<T, Pair<EelProcessExecutionResult, CustomErrorMessage?>>
|
||||
suspend fun getResultFromProcess(binary: Path, args: List<String>, process: EelProcess): Result<T, Pair<EelProcessExecutionResult, CustomErrorMessage?>>
|
||||
}
|
||||
|
||||
|
||||
@@ -71,3 +75,18 @@ typealias ProcessSemiInteractiveFun<T> = suspend (EelSendChannel, Deferred<EelPr
|
||||
* So, you can only *write* something to process.
|
||||
*/
|
||||
fun <T> processSemiInteractiveHandler(pyProcessListener: PyProcessListener? = null, code: ProcessSemiInteractiveFun<T>): ProcessInteractiveHandler<T> = ProcessSemiInteractiveHandlerImpl(pyProcessListener, code)
|
||||
|
||||
/**
|
||||
* ```kotlin
|
||||
* addLocalFile(helper)
|
||||
* addTextArgs("-v")
|
||||
* ```
|
||||
*/
|
||||
interface ArgsBuilder {
|
||||
fun addArgs(vararg args: String)
|
||||
|
||||
/**
|
||||
* This file will be copied to eel and its remote name will be added to the list of arguments
|
||||
*/
|
||||
suspend fun addLocalFile(localFile: Path)
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import com.intellij.platform.eel.provider.utils.EelProcessExecutionResult
|
||||
import com.intellij.platform.eel.provider.utils.stdoutString
|
||||
import com.intellij.python.community.execService.impl.ExecServiceImpl
|
||||
import com.intellij.python.community.execService.impl.PyExecBundle
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.intellij.python.community.execService.impl.transformerToHandler
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.ExecError
|
||||
import com.jetbrains.python.errorProcessing.PyExecResult
|
||||
@@ -35,7 +35,14 @@ suspend fun ExecService.execGetStdout(
|
||||
args: List<String> = emptyList(),
|
||||
options: ExecOptions = ExecOptions(),
|
||||
procListener: PyProcessListener? = null,
|
||||
): PyExecResult<String> = execGetStdout(WhatToExec.Binary(binary), args, options, procListener)
|
||||
): PyExecResult<String> = execute(
|
||||
binary = binary,
|
||||
args = args,
|
||||
options = options,
|
||||
processOutputTransformer = ZeroCodeStdoutTransformer,
|
||||
procListener = procListener
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* Execute [binaryName] on [eelApi].
|
||||
@@ -48,9 +55,9 @@ suspend fun ExecService.execGetStdout(
|
||||
options: ExecOptions = ExecOptions(),
|
||||
procListener: PyProcessListener? = null,
|
||||
): PyResult<String> {
|
||||
val whatToExec = WhatToExec.Binary.fromRelativeName(eelApi, binaryName)
|
||||
val binary = eelApi.exec.findExeFilesInPath(binaryName).firstOrNull()?.asNioPath()
|
||||
?: return PyResult.localizedError(PyExecBundle.message("py.exec.fileNotFound", binaryName, eelApi.descriptor.userReadableDescription))
|
||||
return execGetStdout(whatToExec, args, options, procListener)
|
||||
return execGetStdout(binary, args, options, procListener)
|
||||
}
|
||||
|
||||
|
||||
@@ -66,11 +73,11 @@ suspend fun ExecService.execGetStdoutInShell(
|
||||
procListener: PyProcessListener? = null,
|
||||
): PyExecResult<String> {
|
||||
val (shell, arg) = eelApi.exec.getShell()
|
||||
return execGetStdout(WhatToExec.Binary(shell.asNioPath()), listOf(arg, commandForShell) + args, options, procListener)
|
||||
return execGetStdout(shell.asNioPath(), listOf(arg, commandForShell) + args, options, procListener)
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute [whatToExec] with [args] and get both stdout/stderr outputs if `errorCode != 0`, returns error otherwise.
|
||||
* Execute [binary] with [args] and get both stdout/stderr outputs if `errorCode != 0`, returns error otherwise.
|
||||
* Function collects output lines and reports them to [procListener] if set
|
||||
*
|
||||
* @param[args] command line arguments
|
||||
@@ -79,31 +86,13 @@ suspend fun ExecService.execGetStdoutInShell(
|
||||
*/
|
||||
@CheckReturnValue
|
||||
suspend fun <T> ExecService.execute(
|
||||
whatToExec: WhatToExec,
|
||||
binary: Path,
|
||||
args: List<String> = emptyList(),
|
||||
options: ExecOptions = ExecOptions(),
|
||||
procListener: PyProcessListener? = null,
|
||||
processOutputTransformer: ProcessOutputTransformer<T>,
|
||||
): PyExecResult<T> = execute(whatToExec, args, options, processSemiInteractiveHandler(procListener) { _, result ->
|
||||
processOutputTransformer(result.await())
|
||||
})
|
||||
): PyExecResult<T> = executeAdvanced(binary, { addArgs(*args.toTypedArray()) }, options, transformerToHandler(procListener, processOutputTransformer))
|
||||
|
||||
/**
|
||||
* See [ExecService.execute]
|
||||
*/
|
||||
@CheckReturnValue
|
||||
suspend fun ExecService.execGetStdout(
|
||||
whatToExec: WhatToExec,
|
||||
args: List<String> = emptyList(),
|
||||
options: ExecOptions = ExecOptions(),
|
||||
procListener: PyProcessListener? = null,
|
||||
): PyExecResult<String> = execute(
|
||||
whatToExec = whatToExec,
|
||||
args = args,
|
||||
options = options,
|
||||
processOutputTransformer = ZeroCodeStdoutTransformer,
|
||||
procListener = procListener
|
||||
)
|
||||
|
||||
/**
|
||||
* Error is an optional additionalMessage, that will be used instead of a default one for the [ExecError]
|
||||
@@ -128,24 +117,3 @@ data class ExecOptions(
|
||||
val processDescription: @Nls String? = null,
|
||||
val timeout: Duration = 1.minutes,
|
||||
)
|
||||
|
||||
sealed interface WhatToExec {
|
||||
/**
|
||||
* [binary] (can reside on local or remote Eel, [EelApi] is calculated out of it)
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
package com.intellij.python.community.execService.impl
|
||||
|
||||
import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.platform.eel.EelApi
|
||||
import com.intellij.platform.eel.EelProcess
|
||||
import com.intellij.platform.eel.ExecuteProcessException
|
||||
import com.intellij.platform.eel.path.EelPath
|
||||
@@ -9,11 +10,10 @@ import com.intellij.platform.eel.provider.asEelPath
|
||||
import com.intellij.platform.eel.provider.getEelDescriptor
|
||||
import com.intellij.platform.eel.provider.utils.EelPathUtils
|
||||
import com.intellij.platform.eel.spawnProcess
|
||||
import com.intellij.python.community.execService.ArgsBuilder
|
||||
import com.intellij.python.community.execService.ExecOptions
|
||||
import com.intellij.python.community.execService.ExecService
|
||||
import com.intellij.python.community.execService.ProcessInteractiveHandler
|
||||
import com.intellij.python.community.execService.WhatToExec
|
||||
import com.intellij.python.community.helpersLocator.PythonHelpersLocator
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.ExecError
|
||||
import com.jetbrains.python.errorProcessing.ExecErrorReason
|
||||
@@ -26,22 +26,24 @@ import kotlinx.coroutines.withTimeout
|
||||
import org.jetbrains.annotations.CheckReturnValue
|
||||
import org.jetbrains.annotations.Nls
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.time.Duration
|
||||
|
||||
|
||||
internal object ExecServiceImpl : ExecService {
|
||||
override suspend fun <T> execute(
|
||||
whatToExec: WhatToExec,
|
||||
args: List<String>,
|
||||
options: ExecOptions,
|
||||
processInteractiveHandler: ProcessInteractiveHandler<T>,
|
||||
): PyExecResult<T> {
|
||||
val executableProcess = whatToExec.buildExecutableProcess(args, options)
|
||||
|
||||
override suspend fun <T> executeAdvanced(binary: Path, argsBuilder: suspend ArgsBuilder.() -> Unit, options: ExecOptions, processInteractiveHandler: ProcessInteractiveHandler<T>): PyExecResult<T> {
|
||||
val args = ArgsBuilderImpl(binary.getEelDescriptor().toEelApi()).apply { argsBuilder() }.args
|
||||
val description = options.processDescription
|
||||
?: PyExecBundle.message("py.exec.defaultName.process", (listOf(toString()) + args).joinToString(" "))
|
||||
|
||||
val executableProcess = EelExecutableProcess(binary.asEelPath(), args, options.env, options.workingDirectory, description)
|
||||
val eelProcess = executableProcess.run().getOr { return it }
|
||||
|
||||
val result = try {
|
||||
withTimeout(options.timeout) {
|
||||
val interactiveResult = processInteractiveHandler.getResultFromProcess(whatToExec, args, eelProcess)
|
||||
val interactiveResult = processInteractiveHandler.getResultFromProcess(binary, args, eelProcess)
|
||||
|
||||
val successResult = interactiveResult.getOr { failure ->
|
||||
val (output, customErrorMessage) = failure.error
|
||||
@@ -66,28 +68,6 @@ private data class EelExecutableProcess(
|
||||
val description: @Nls String,
|
||||
)
|
||||
|
||||
private suspend fun WhatToExec.buildExecutableProcess(args: List<String>, options: ExecOptions): EelExecutableProcess {
|
||||
val (exe, args) = when (this) {
|
||||
is WhatToExec.Binary -> Pair(binary, args)
|
||||
is WhatToExec.Helper -> {
|
||||
val eel = python.getEelDescriptor().toEelApi()
|
||||
val localHelper = withContext(Dispatchers.IO) { PythonHelpersLocator.findPathInHelpers(helper) }
|
||||
val remoteHelper = EelPathUtils.transferLocalContentToRemote(
|
||||
source = localHelper,
|
||||
target = EelPathUtils.TransferTarget.Temporary(eel.descriptor)
|
||||
).asEelPath().toString()
|
||||
Pair(python, listOf(remoteHelper) + 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")
|
||||
}
|
||||
|
||||
return EelExecutableProcess(exe.asEelPath(), args, options.env, options.workingDirectory, description)
|
||||
}
|
||||
|
||||
@CheckReturnValue
|
||||
private suspend fun EelExecutableProcess.run(): PyExecResult<EelProcess> {
|
||||
val workingDirectory = if (workingDirectory != null && !workingDirectory.isAbsolute) workingDirectory.toRealPath() else workingDirectory
|
||||
@@ -98,7 +78,8 @@ private suspend fun EelExecutableProcess.run(): PyExecResult<EelProcess> {
|
||||
.workingDirectory(workingDirectory?.asEelPath()).eelIt()
|
||||
|
||||
return Result.success(executionResult)
|
||||
} catch (e: ExecuteProcessException) {
|
||||
}
|
||||
catch (e: ExecuteProcessException) {
|
||||
return failAsCantStart(e)
|
||||
}
|
||||
}
|
||||
@@ -140,3 +121,20 @@ private fun ExecError.logAndFail(): Result.Failure<ExecError> {
|
||||
fileLogger().warn(message)
|
||||
return failure(this)
|
||||
}
|
||||
|
||||
private class ArgsBuilderImpl(private val eel: EelApi) : ArgsBuilder {
|
||||
private val _args = CopyOnWriteArrayList<String>()
|
||||
val args: List<String> = _args
|
||||
override fun addArgs(vararg args: String) {
|
||||
_args.addAll(args)
|
||||
}
|
||||
|
||||
override suspend fun addLocalFile(localFile: Path): Unit = withContext(Dispatchers.IO) {
|
||||
assert(localFile.exists()) { "No file $localFile, be sure to check it before calling" }
|
||||
val remoteFile = EelPathUtils.transferLocalContentToRemote(
|
||||
source = localFile,
|
||||
target = EelPathUtils.TransferTarget.Temporary(eel.descriptor)
|
||||
).asEelPath().toString()
|
||||
_args.add(remoteFile)
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,15 @@ import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.mapError
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import java.nio.file.Path
|
||||
|
||||
internal class ProcessSemiInteractiveHandlerImpl<T>(
|
||||
private val pyProcessListener: PyProcessListener?,
|
||||
private val code: ProcessSemiInteractiveFun<T>,
|
||||
) : ProcessInteractiveHandler<T> {
|
||||
override suspend fun getResultFromProcess(whatToExec: WhatToExec, args: List<String>, process: EelProcess): Result<T, Pair<EelProcessExecutionResult, CustomErrorMessage?>> =
|
||||
override suspend fun getResultFromProcess(binary: Path, args: List<String>, process: EelProcess): Result<T, Pair<EelProcessExecutionResult, CustomErrorMessage?>> =
|
||||
coroutineScope {
|
||||
pyProcessListener?.emit(ProcessEvent.ProcessStarted(whatToExec, args))
|
||||
pyProcessListener?.emit(ProcessEvent.ProcessStarted(binary, args))
|
||||
val processOutput = async { process.awaitWithReporting(pyProcessListener) }
|
||||
val result = code(process.stdin, processOutput)
|
||||
pyProcessListener?.emit(ProcessEvent.ProcessEnded(process.exitCode.await()))
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// 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.impl
|
||||
|
||||
import com.intellij.python.community.execService.ProcessInteractiveHandler
|
||||
import com.intellij.python.community.execService.ProcessOutputTransformer
|
||||
import com.intellij.python.community.execService.PyProcessListener
|
||||
import com.intellij.python.community.execService.processSemiInteractiveHandler
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
fun <T> transformerToHandler(
|
||||
procListener: PyProcessListener?,
|
||||
processOutputTransformer: ProcessOutputTransformer<T>,
|
||||
): ProcessInteractiveHandler<T> = processSemiInteractiveHandler(procListener) { _, result ->
|
||||
processOutputTransformer(result.await())
|
||||
}
|
||||
@@ -89,7 +89,7 @@ class ExecServiceShowCaseTest {
|
||||
val (shell, execArg) = eel.exec.getShell()
|
||||
val args = listOf(execArg, "echo Alice,25 && echo Bob,48")
|
||||
|
||||
val records = ExecService().execute(WhatToExec.Binary(shell.asNioPath()), args) { output ->
|
||||
val records = ExecService().execute((shell.asNioPath()), args) { output ->
|
||||
val stdout = output.stdoutString.trim()
|
||||
when {
|
||||
output.exitCode == 123 -> {
|
||||
@@ -133,9 +133,9 @@ class ExecServiceShowCaseTest {
|
||||
}
|
||||
}
|
||||
|
||||
val whatToExec = WhatToExec.Binary.fromRelativeName(eel, binaryName) ?: error("Can't find $binaryName")
|
||||
val whatToExec = eel.exec.findExeFilesInPath(binaryName).firstOrNull() ?: error("Can't find $binaryName")
|
||||
|
||||
val output = execService.execGetStdout(whatToExec, args.toList()).getOrThrow()
|
||||
val output = execService.execGetStdout(whatToExec.asNioPath(), args.toList()).getOrThrow()
|
||||
assertThat("Command doesn't have expected output", output, CoreMatchers.containsString(expectedPhrase))
|
||||
}
|
||||
|
||||
@@ -143,8 +143,8 @@ class ExecServiceShowCaseTest {
|
||||
@EelSource
|
||||
fun testInteractive(eelHolder: EelHolder): Unit = timeoutRunBlocking {
|
||||
val string = "abc123"
|
||||
val shell = eelHolder.eel.exec.getShell().first
|
||||
val output = ExecService().execute(WhatToExec.Binary(shell.asNioPath()), emptyList(), processInteractiveHandler = ProcessInteractiveHandler<String> { _, _, process ->
|
||||
val shell = eelHolder.eel.exec.getShell().first.asNioPath()
|
||||
val output = ExecService().executeAdvanced(shell, {}, processInteractiveHandler = ProcessInteractiveHandler<String> { _, _, process ->
|
||||
val stdout = async {
|
||||
process.stdout.readWholeText()
|
||||
}
|
||||
@@ -161,8 +161,8 @@ class ExecServiceShowCaseTest {
|
||||
@CartesianTest.Values(booleans = [true, false]) sunny: Boolean,
|
||||
): Unit = timeoutRunBlocking {
|
||||
val messageToUser = "abc123"
|
||||
val shell = eelHolder.eel.exec.getShell().first
|
||||
val result = ExecService().execute(WhatToExec.Binary(shell.asNioPath()), emptyList(), processInteractiveHandler = processSemiInteractiveHandler<Unit> { channel, exitCode ->
|
||||
val shell = eelHolder.eel.exec.getShell().first.asNioPath()
|
||||
val result = ExecService().executeAdvanced(shell, {}, processInteractiveHandler = processSemiInteractiveHandler<Unit> { channel, exitCode ->
|
||||
channel.sendWholeText("exit\n")
|
||||
assertEquals(0, exitCode.await().exitCode, "Wrong exit code")
|
||||
if (sunny) {
|
||||
@@ -176,7 +176,7 @@ class ExecServiceShowCaseTest {
|
||||
is Result.Failure -> {
|
||||
assertFalse(sunny, "Unexpected failure ${result.error}")
|
||||
assertEquals(messageToUser, result.error.additionalMessageToUser, "Wrong message to user")
|
||||
assertEquals(shell, result.error.exe, "Wrong exe")
|
||||
assertEquals(shell, result.error.exe.asNioPath(), "Wrong exe")
|
||||
}
|
||||
is Result.Success -> {
|
||||
assertTrue(sunny, "Unexpected success")
|
||||
@@ -190,12 +190,11 @@ class ExecServiceShowCaseTest {
|
||||
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.Binary(binary)
|
||||
when (val output = ExecService().execGetStdout(command, listOf(arg))) {
|
||||
when (val output = ExecService().execGetStdout(binary, listOf(arg))) {
|
||||
is Result.Success -> fail("Execution of bad command should lead to an error")
|
||||
is Result.Failure -> {
|
||||
val err = output.error
|
||||
assertEquals(command.binary, err.exe.asNioPath(), "Wrong command reported")
|
||||
assertEquals(binary, err.exe.asNioPath(), "Wrong command reported")
|
||||
assertEquals("foo", err.args[0], "Wrong args reported")
|
||||
}
|
||||
}
|
||||
@@ -206,12 +205,10 @@ class ExecServiceShowCaseTest {
|
||||
@EelSource
|
||||
fun testProgress(eelHolder: EelHolder): Unit = timeoutRunBlocking(10.minutes) {
|
||||
val eel = eelHolder.eel
|
||||
val shell = eel.exec.getShell().first
|
||||
val shell = eel.exec.getShell().first.asNioPath()
|
||||
|
||||
val text = "Once there was a captain brave".split(" ").toTypedArray()
|
||||
|
||||
val whatToExec = WhatToExec.Binary(shell.asNioPath())
|
||||
|
||||
var processStartEvent = false
|
||||
var processEndEvent = false
|
||||
|
||||
@@ -219,7 +216,7 @@ class ExecServiceShowCaseTest {
|
||||
val progressCapturer = PyProcessListener { event ->
|
||||
when (event) {
|
||||
is ProcessEvent.ProcessStarted -> {
|
||||
assertEquals(whatToExec, event.whatToExec, "Wrong args for start event")
|
||||
assertEquals(shell, event.binary, "Wrong args for start event")
|
||||
processStartEvent = true
|
||||
}
|
||||
is ProcessEvent.ProcessOutput -> {
|
||||
@@ -231,7 +228,7 @@ class ExecServiceShowCaseTest {
|
||||
}
|
||||
}
|
||||
|
||||
ExecService().execute(whatToExec, args = emptyList(), processInteractiveHandler = processSemiInteractiveHandler<Unit>(progressCapturer) { stdin, exitCode ->
|
||||
ExecService().executeAdvanced(shell, argsBuilder = {}, processInteractiveHandler = processSemiInteractiveHandler<Unit>(progressCapturer) { stdin, _ ->
|
||||
for (string in text) {
|
||||
stdin.sendWholeText("echo $string\n")
|
||||
delay(500)
|
||||
@@ -267,7 +264,7 @@ class ExecServiceShowCaseTest {
|
||||
is ProcessEvent.ProcessEnded, is ProcessEvent.ProcessStarted -> Unit
|
||||
}
|
||||
}
|
||||
val output = ExecService().execGetStdout(WhatToExec.Binary(shell.asNioPath()), listOf(arg, "echo $text"), procListener = listener).getOrThrow()
|
||||
val output = ExecService().execGetStdout(shell.asNioPath(), listOf(arg, "echo $text"), procListener = listener).getOrThrow()
|
||||
assertTrue(stdoutReported, "No stdout reported")
|
||||
assertEquals(text, output.trim(), "Wrong result")
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.intellij.python.hatch.runtime
|
||||
import com.intellij.platform.eel.EelApi
|
||||
import com.intellij.platform.eel.provider.localEel
|
||||
import com.intellij.python.community.execService.*
|
||||
import com.intellij.python.community.execService.WhatToExec.Binary
|
||||
import com.intellij.python.hatch.*
|
||||
import com.intellij.python.hatch.cli.HatchCli
|
||||
import com.jetbrains.python.PythonBinary
|
||||
@@ -17,7 +16,7 @@ import kotlin.io.path.isDirectory
|
||||
import kotlin.io.path.isExecutable
|
||||
|
||||
class HatchRuntime(
|
||||
val hatchBinary: Binary,
|
||||
val hatchBinary: Path,
|
||||
val execOptions: ExecOptions,
|
||||
private val execService: ExecService = ExecService(),
|
||||
) {
|
||||
@@ -60,12 +59,12 @@ class HatchRuntime(
|
||||
}
|
||||
|
||||
internal suspend fun <T> executeInteractive(vararg arguments: String, processSemiInteractiveFun: ProcessSemiInteractiveFun<T>): PyExecResult<T> {
|
||||
return execService.execute(hatchBinary, arguments.toList(), execOptions, processSemiInteractiveHandler(code = processSemiInteractiveFun))
|
||||
return execService.executeAdvanced(hatchBinary, { addArgs(*arguments) }, execOptions, processSemiInteractiveHandler(code = processSemiInteractiveFun))
|
||||
}
|
||||
|
||||
internal suspend fun resolvePythonVirtualEnvironment(pythonHomePath: PythonHomePath): PyResult<PythonVirtualEnvironment> {
|
||||
val pythonVersion = pythonHomePath.takeIf { it.isDirectory() }?.resolvePythonBinary()?.let { pythonBinaryPath ->
|
||||
execService.execGetStdout(Binary(pythonBinaryPath), listOf("--version")).getOr { return it }.trim()
|
||||
execService.execGetStdout(pythonBinaryPath, listOf("--version")).getOr { return it }.trim()
|
||||
}
|
||||
val pythonVirtualEnvironment = when {
|
||||
pythonVersion == null -> PythonVirtualEnvironment.NotExisting(pythonHomePath)
|
||||
@@ -99,7 +98,7 @@ suspend fun createHatchRuntime(
|
||||
val actualEnvVars = defaultVariables + envVars
|
||||
|
||||
val runtime = HatchRuntime(
|
||||
hatchBinary = Binary(actualHatchExecutable),
|
||||
hatchBinary = actualHatchExecutable,
|
||||
execOptions = ExecOptions(
|
||||
env = actualEnvVars,
|
||||
workingDirectory = workingDirectoryPath
|
||||
|
||||
@@ -83,7 +83,3 @@ path.validation.ends.with.whitespace=Path ends with a whitespace
|
||||
path.validation.file.not.found=File {0} is not found
|
||||
path.validation.invalid=Path is invalid: {0}
|
||||
path.validation.inaccessible=Path is inaccessible
|
||||
|
||||
python.get.version.error={0} returned error: {1}
|
||||
python.get.version.too.long={0} took too long
|
||||
python.get.version.wrong.version={0} has a wrong version: {1}
|
||||
|
||||
@@ -1,68 +1,13 @@
|
||||
package com.jetbrains.python
|
||||
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.platform.eel.EelPlatform
|
||||
import com.intellij.platform.eel.ExecuteProcessException
|
||||
import com.intellij.platform.eel.provider.getEelDescriptor
|
||||
import com.intellij.platform.eel.provider.utils.EelProcessExecutionResult
|
||||
import com.intellij.platform.eel.provider.utils.exec
|
||||
import com.intellij.platform.eel.provider.utils.stderrString
|
||||
import com.intellij.platform.eel.provider.utils.stdoutString
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.PySdkBundle.message
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor.PYTHON_VERSION_ARG
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor.getLanguageLevelFromVersionStringStaticSafe
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import kotlin.io.path.name
|
||||
import kotlin.io.path.pathString
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
|
||||
/**
|
||||
* 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 PythonBinary.validatePythonAndGetVersion(): PyResult<LanguageLevel> = withContext(Dispatchers.IO) {
|
||||
val smokeTestOutput = executeWithResult("-c", "print(1)").getOr { return@withContext it }.stdoutString.trim()
|
||||
if (smokeTestOutput != "1") {
|
||||
return@withContext PyResult.localizedError(message("python.get.version.error", pathString, smokeTestOutput))
|
||||
}
|
||||
|
||||
val versionOutput = executeWithResult(PYTHON_VERSION_ARG).getOr { return@withContext it }
|
||||
// Python 2 might return version as stderr, see https://bugs.python.org/issue18338
|
||||
val versionString = versionOutput.stdoutString.let { it.ifBlank { versionOutput.stderrString } }
|
||||
val languageLevel = getLanguageLevelFromVersionStringStaticSafe(versionString.trim())
|
||||
if (languageLevel == null) {
|
||||
return@withContext PyResult.localizedError(message("python.get.version.wrong.version", pathString, versionOutput))
|
||||
}
|
||||
return@withContext Result.success(languageLevel)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes [this] with [args], returns either output or error (if execution failed or exit code != 0)
|
||||
*/
|
||||
private suspend fun PythonBinary.executeWithResult(vararg args: String): PyResult<@NlsSafe EelProcessExecutionResult> {
|
||||
try {
|
||||
val output = exec(*args, timeout = 5.seconds)
|
||||
return if (output.exitCode != 0) {
|
||||
PyResult.localizedError(message("python.get.version.error", pathString, "code ${output.exitCode}, ${output.stderrString}"))
|
||||
}
|
||||
else {
|
||||
Result.success(output)
|
||||
}
|
||||
} catch (e : ExecuteProcessException) {
|
||||
return PyResult.localizedError(e.localizedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
@ApiStatus.Internal
|
||||
fun PythonBinary.resolvePythonHome(): PythonHomePath = when (getEelDescriptor().platform) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.jetbrains.python.PythonBinaryKt.PYTHON_VERSION_ARG;
|
||||
import static com.jetbrains.python.sdk.flavors.PySdkFlavorUtilKt.getFileExecutionError;
|
||||
import static com.jetbrains.python.sdk.flavors.PySdkFlavorUtilKt.getFileExecutionErrorOnEdt;
|
||||
import static com.jetbrains.python.venvReader.ResolveUtilKt.tryResolvePath;
|
||||
@@ -65,12 +66,6 @@ public abstract class PythonSdkFlavor<D extends PyFlavorData> {
|
||||
|
||||
private static final Pattern VERSION_RE = Pattern.compile("(Python \\S+).*");
|
||||
private static final Logger LOG = Logger.getInstance(PythonSdkFlavor.class);
|
||||
/**
|
||||
* <code>
|
||||
* python --version
|
||||
* </code>
|
||||
*/
|
||||
public static final String PYTHON_VERSION_ARG = "--version";
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,7 @@ jvm_library(
|
||||
"//platform/projectModel-api:projectModel",
|
||||
"//platform/util",
|
||||
"//platform/core-api:core",
|
||||
"//python/python-exec-service/execService.python",
|
||||
],
|
||||
runtime_deps = [":community-impl-venv_resources"]
|
||||
)
|
||||
@@ -57,6 +58,8 @@ jvm_library(
|
||||
"//platform/ide-core-impl",
|
||||
"//platform/execution",
|
||||
"//platform/core-api:core",
|
||||
"//python/python-exec-service/execService.python",
|
||||
"//python/python-exec-service/execService.python:execService.python_test_lib",
|
||||
],
|
||||
runtime_deps = [
|
||||
":community-impl-venv_resources",
|
||||
|
||||
@@ -25,5 +25,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.core.impl" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.execution" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.execService.python" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -2,6 +2,7 @@
|
||||
<dependencies>
|
||||
<module name="intellij.python.community"/>
|
||||
<module name="intellij.python.community.execService"/>
|
||||
<module name="intellij.python.community.execService.python"/>
|
||||
<module name="intellij.python.sdk"/>
|
||||
</dependencies>
|
||||
</idea-plugin>
|
||||
@@ -4,13 +4,15 @@ package com.intellij.python.community.impl.venv
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.python.community.execService.*
|
||||
import com.intellij.python.community.execService.python.HelperName
|
||||
import com.intellij.python.community.execService.python.executeHelper
|
||||
import com.intellij.python.community.execService.python.validatePythonAndGetVersion
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.sdk.PySdkSettings
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
|
||||
import com.jetbrains.python.validatePythonAndGetVersion
|
||||
import com.jetbrains.python.venvReader.Directory
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -43,7 +45,7 @@ suspend fun createVenv(
|
||||
}
|
||||
val version = python.validatePythonAndGetVersion().getOr { return it }
|
||||
val helper = if (version.isAtLeast(LanguageLevel.PYTHON38)) VIRTUALENV_ZIPAPP_NAME else LEGACY_VIRTUALENV_ZIPAPP_NAME
|
||||
execService.execGetStdout(WhatToExec.Helper(python, helper = helper), args, ExecOptions(timeout = 3.minutes)).getOr { return it }
|
||||
execService.executeHelper(python, helper, args, ExecOptions(timeout = 3.minutes)).getOr { return it }
|
||||
|
||||
|
||||
val venvPython = withContext(Dispatchers.IO) {
|
||||
|
||||
@@ -21,6 +21,7 @@ jvm_library(
|
||||
"//python/openapi:community",
|
||||
"@lib//:kotlinx-coroutines-core",
|
||||
"//python/python-sdk:sdk",
|
||||
"//python/python-exec-service/execService.python",
|
||||
],
|
||||
runtime_deps = [":python-community-services-internal-impl_resources"]
|
||||
)
|
||||
@@ -46,6 +47,8 @@ jvm_library(
|
||||
"//python/python-sdk:sdk",
|
||||
"//python/python-sdk:sdk_test_lib",
|
||||
"//python/junit5Tests-framework:community-junit5Tests-framework_test_lib",
|
||||
"//python/python-exec-service/execService.python",
|
||||
"//python/python-exec-service/execService.python:execService.python_test_lib",
|
||||
],
|
||||
runtime_deps = [":python-community-services-internal-impl_resources"]
|
||||
)
|
||||
|
||||
@@ -21,5 +21,6 @@
|
||||
<orderEntry type="library" name="kotlinx-coroutines-core" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.python.sdk" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.junit5Tests.framework" scope="TEST" />
|
||||
<orderEntry type="module" module-name="intellij.python.community.execService.python" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -3,5 +3,6 @@
|
||||
<module name="intellij.python.community"/>
|
||||
<module name="intellij.python.psi.impl"/>
|
||||
<module name="intellij.python.sdk"/>
|
||||
<module name="intellij.python.community.execService.python"/>
|
||||
</dependencies>
|
||||
</idea-plugin>
|
||||
@@ -4,6 +4,7 @@ package com.intellij.python.community.services.internal.impl
|
||||
import com.intellij.platform.eel.EelPlatform
|
||||
import com.intellij.platform.eel.provider.asNioPath
|
||||
import com.intellij.platform.eel.provider.getEelDescriptor
|
||||
import com.intellij.python.community.execService.python.validatePythonAndGetVersion
|
||||
import com.intellij.python.community.services.internal.impl.PythonWithLanguageLevelImpl.Companion.concurrentLimit
|
||||
import com.intellij.python.community.services.internal.impl.PythonWithLanguageLevelImpl.Companion.createByPythonBinary
|
||||
import com.intellij.python.community.services.shared.PythonWithLanguageLevel
|
||||
@@ -11,7 +12,6 @@ import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.validatePythonAndGetVersion
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.vfs.VfsUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.platform.util.progress.withProgressText
|
||||
import com.intellij.python.community.execService.python.validatePythonAndGetVersion
|
||||
import com.intellij.python.community.impl.venv.createVenv
|
||||
import com.intellij.python.community.services.systemPython.SystemPythonService
|
||||
import com.jetbrains.python.*
|
||||
|
||||
@@ -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.jetbrains.python.remote;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
@@ -25,6 +25,8 @@ import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
import static com.jetbrains.python.PythonBinaryKt.PYTHON_VERSION_ARG;
|
||||
|
||||
public final class PyRemoteInterpreterUtil {
|
||||
/**
|
||||
* @param nullForUnparsableVersion if version returns by python can't be parsed -- return null instead of exception
|
||||
@@ -45,7 +47,7 @@ public final class PyRemoteInterpreterUtil {
|
||||
ProcessOutput processOutput;
|
||||
try {
|
||||
try {
|
||||
String[] command = {data.getInterpreterPath(), PythonSdkFlavor.PYTHON_VERSION_ARG};
|
||||
String[] command = {data.getInterpreterPath(), PYTHON_VERSION_ARG};
|
||||
processOutput = PyRemoteProcessStarterManagerUtil.getManager(data).executeRemoteProcess(myProject, command, null,
|
||||
data, new PyRemotePathMapper());
|
||||
if (processOutput.getExitCode() == 0) {
|
||||
|
||||
@@ -4,7 +4,10 @@ package com.jetbrains.python.sdk
|
||||
import com.intellij.execution.process.AnsiEscapeDecoder
|
||||
import com.intellij.execution.process.ProcessOutputTypes
|
||||
import com.intellij.platform.util.progress.reportRawProgress
|
||||
import com.intellij.python.community.execService.*
|
||||
import com.intellij.python.community.execService.ExecOptions
|
||||
import com.intellij.python.community.execService.ExecService
|
||||
import com.intellij.python.community.execService.ProcessEvent
|
||||
import com.intellij.python.community.execService.execGetStdout
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.PyExecResult
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
@@ -26,7 +29,7 @@ import kotlin.time.Duration.Companion.minutes
|
||||
suspend fun runExecutableWithProgress(executable: Path, workDir: Path?, timeout: Duration = 10.minutes, vararg args: String): PyExecResult<String> {
|
||||
val ansiDecoder = AnsiEscapeDecoder()
|
||||
reportRawProgress { reporter ->
|
||||
return ExecService().execGetStdout(WhatToExec.Binary(executable), args.toList(), ExecOptions(workingDirectory = workDir, timeout = timeout), procListener = {
|
||||
return ExecService().execGetStdout(executable, args.toList(), ExecOptions(workingDirectory = workDir, timeout = timeout), procListener = {
|
||||
when (it) {
|
||||
is ProcessEvent.ProcessStarted, is ProcessEvent.ProcessEnded -> Unit
|
||||
is ProcessEvent.ProcessOutput -> {
|
||||
|
||||
@@ -3,8 +3,7 @@ package com.jetbrains.python.sdk
|
||||
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.python.community.execService.ExecService
|
||||
import com.intellij.python.community.execService.WhatToExec
|
||||
import com.intellij.python.community.execService.execGetStdout
|
||||
import com.intellij.python.community.execService.python.executeHelper
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
@@ -28,6 +27,6 @@ fun getPythonExecutableString(): String = if (SystemInfo.isWindows) "py" else "p
|
||||
*/
|
||||
@Internal
|
||||
suspend fun installExecutableViaPythonScript(pythonExecutable: PythonBinary, vararg args: String): PyResult<Path> {
|
||||
val output = ExecService().execGetStdout(WhatToExec.Helper(pythonExecutable, "pycharm_package_installer.py"), args.toList()).getOr { return it }
|
||||
val output = ExecService().executeHelper(pythonExecutable, "pycharm_package_installer.py", args.toList()).getOr { return it }
|
||||
return Result.success(Path.of(output.split("\n").last()))
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.jetbrains.python.PYTHON_VERSION_ARG
|
||||
import com.jetbrains.python.PythonHelper
|
||||
import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory
|
||||
import com.jetbrains.python.run.buildTargetedCommandLine
|
||||
@@ -41,7 +42,7 @@ class PyTargetsIntrospectionFacade(val sdk: Sdk, val project: Project) {
|
||||
val cmdBuilder = TargetedCommandLineBuilder(targetEnvRequest)
|
||||
sdk.configureBuilderToRunPythonOnTarget(cmdBuilder)
|
||||
sdk.sdkFlavor
|
||||
cmdBuilder.addParameter(PythonSdkFlavor.PYTHON_VERSION_ARG)
|
||||
cmdBuilder.addParameter(PYTHON_VERSION_ARG)
|
||||
val cmd = cmdBuilder.build()
|
||||
|
||||
val environment = targetEnvRequest.prepareEnvironment(TargetProgressIndicatorAdapter(indicator))
|
||||
|
||||
@@ -11,7 +11,6 @@ import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.python.community.execService.ExecOptions
|
||||
import com.intellij.python.community.execService.ExecService
|
||||
import com.intellij.python.community.execService.WhatToExec
|
||||
import com.intellij.python.community.execService.execGetStdout
|
||||
import com.intellij.python.community.impl.poetry.poetryPath
|
||||
import com.intellij.util.SystemProperties
|
||||
@@ -115,7 +114,7 @@ suspend fun setupPoetry(projectPath: Path, python: String?, installPackages: Boo
|
||||
if (init) {
|
||||
runPoetry(projectPath, *listOf("init", "-n").toTypedArray())
|
||||
if (python != null) { // Replace a python version in toml
|
||||
ExecService().execGetStdout(WhatToExec.Binary(Path.of(python)), listOf("-c", REPLACE_PYTHON_VERSION), ExecOptions(workingDirectory = projectPath)).getOr { return it }
|
||||
ExecService().execGetStdout(Path.of(python), listOf("-c", REPLACE_PYTHON_VERSION), ExecOptions(workingDirectory = projectPath)).getOr { return it }
|
||||
}
|
||||
}
|
||||
when {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:JvmName("PyInterpreterVersionUtil")
|
||||
|
||||
package com.jetbrains.python.target
|
||||
@@ -15,6 +15,7 @@ import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.remote.RemoteSdkException
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.jetbrains.python.PYTHON_VERSION_ARG
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
|
||||
|
||||
@@ -39,7 +40,7 @@ fun PyTargetAwareAdditionalData.getInterpreterVersion(project: Project?,
|
||||
try {
|
||||
val targetedCommandLineBuilder = TargetedCommandLineBuilder(targetEnvironmentRequest)
|
||||
targetedCommandLineBuilder.setExePath(interpreterPath)
|
||||
targetedCommandLineBuilder.addParameter(PythonSdkFlavor.PYTHON_VERSION_ARG)
|
||||
targetedCommandLineBuilder.addParameter(PYTHON_VERSION_ARG)
|
||||
val targetEnvironment = targetEnvironmentRequest.prepareEnvironment(TargetProgressIndicatorAdapter(indicator))
|
||||
val targetedCommandLine = targetedCommandLineBuilder.build()
|
||||
val process = targetEnvironment.createProcess(targetedCommandLine, indicator)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2022 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.jetbrains
|
||||
|
||||
import com.intellij.execution.processTools.getBareExecutionResult
|
||||
@@ -10,6 +10,7 @@ import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.util.concurrency.ThreadingAssertions
|
||||
import com.jetbrains.python.PYTHON_VERSION_ARG
|
||||
import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory
|
||||
import com.jetbrains.python.sdk.configureBuilderToRunPythonOnTarget
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
|
||||
@@ -29,7 +30,7 @@ internal suspend fun getPythonVersion(sdk: Sdk, request: TargetEnvironmentReques
|
||||
internal suspend fun getPythonVersion(commandLineBuilder: TargetedCommandLineBuilder,
|
||||
flavor: PythonSdkFlavor<*>,
|
||||
request: TargetEnvironmentRequest): String? {
|
||||
commandLineBuilder.addParameter(PythonSdkFlavor.PYTHON_VERSION_ARG)
|
||||
commandLineBuilder.addParameter(PYTHON_VERSION_ARG)
|
||||
val commandLine = commandLineBuilder.build()
|
||||
val result = request
|
||||
.prepareEnvironment(TargetProgressIndicator.EMPTY)
|
||||
|
||||
Reference in New Issue
Block a user