mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 21:11:28 +07:00
PY-84953: Unify tool detection approach
Our tool detection approach varies a lot. We have different logic for uv, poetry and other tools. Also, uv detection is not suspendable and doesn't have any explicit thread requirements, even though it performs I/O operations. This change makes such detection unified and suspendable (where possible) and moves it to BGT. GitOrigin-RevId: 18e9c4cc085c8d373c82ad2874033b53711f09c6
This commit is contained in:
committed by
intellij-monorepo-bot
parent
cc191b2b3d
commit
27838bc2da
@@ -31,6 +31,7 @@ import com.jetbrains.python.sdk.PythonSdkType
|
||||
import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.configuration.*
|
||||
import com.jetbrains.python.sdk.findAmongRoots
|
||||
import com.jetbrains.python.sdk.impl.PySdkBundle
|
||||
import com.jetbrains.python.sdk.impl.resolvePythonBinary
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.pipenv.*
|
||||
@@ -139,12 +140,12 @@ class PyPipfileSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
|
||||
val path = withContext(Dispatchers.IO) { VirtualEnvReader.Instance.findPythonInPythonRoot(Path.of(pipEnv)) }
|
||||
if (path == null) {
|
||||
return@withBackgroundProgress PyResult.localizedError(PyBundle.message("cannot.find.executable", "python", pipEnv))
|
||||
return@withBackgroundProgress PyResult.localizedError(PySdkBundle.message("cannot.find.executable", "python", pipEnv))
|
||||
}
|
||||
|
||||
val file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path.toString())
|
||||
if (file == null) {
|
||||
return@withBackgroundProgress PyResult.localizedError(PyBundle.message("cannot.find.executable", "python", path))
|
||||
return@withBackgroundProgress PyResult.localizedError(PySdkBundle.message("cannot.find.executable", "python", path))
|
||||
}
|
||||
|
||||
PySdkConfigurationCollector.logPipEnv(module.project, PipEnvResult.CREATED)
|
||||
|
||||
@@ -24,6 +24,7 @@ import com.jetbrains.python.projectModel.poetry.POETRY_TOOL_ID
|
||||
import com.jetbrains.python.sdk.PythonSdkType
|
||||
import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.configuration.*
|
||||
import com.jetbrains.python.sdk.impl.PySdkBundle
|
||||
import com.jetbrains.python.sdk.impl.resolvePythonBinary
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.poetry.*
|
||||
@@ -97,10 +98,10 @@ class PyPoetrySdkConfiguration : PyProjectTomlConfigurationExtension {
|
||||
val tomlFile = PyProjectToml.findFile(module)
|
||||
val poetry = setupPoetry(basePath, null, true, tomlFile == null).getOr { return@withBackgroundProgress it }
|
||||
val path = poetry.resolvePythonBinary()
|
||||
?: return@withBackgroundProgress PyResult.localizedError(PyBundle.message("cannot.find.executable", "python", poetry))
|
||||
?: return@withBackgroundProgress PyResult.localizedError(PySdkBundle.message("cannot.find.executable", "python", poetry))
|
||||
|
||||
val file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path.pathString)
|
||||
?: return@withBackgroundProgress PyResult.localizedError(PyBundle.message("cannot.find.executable", "python", path))
|
||||
?: return@withBackgroundProgress PyResult.localizedError(PySdkBundle.message("cannot.find.executable", "python", path))
|
||||
|
||||
LOGGER.debug("Setting up associated poetry environment: $path, $basePath")
|
||||
val sdk = SdkConfigurationUtil.setupSdk(
|
||||
|
||||
@@ -1742,7 +1742,6 @@ command.name.add.package.to.conda.environments.yml=Add a package to conda enviro
|
||||
command.name.add.package.to.requirements.txt=Add a package to requirements.txt
|
||||
command.name.add.package.to.setup.py=Add a package to setup.py
|
||||
python.sdk.conda.requirements.file.not.found=Conda Environment.yml file is not found
|
||||
cannot.find.executable=Cannot find executable "{0}" in {1}
|
||||
|
||||
python.uv.lockfile.out.of.sync=The lock file at `uv.lock` is out of sync
|
||||
python.uv.update.lock=Update uv lock
|
||||
|
||||
@@ -3,11 +3,9 @@ package com.intellij.python.hatch
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.platform.eel.EelApi
|
||||
import com.intellij.platform.eel.LocalEelApi
|
||||
import com.intellij.platform.eel.provider.asNioPath
|
||||
import com.intellij.platform.eel.provider.localEel
|
||||
import com.intellij.platform.eel.where
|
||||
import com.intellij.python.hatch.runtime.getHatchCommand
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.sdk.detectTool
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.isExecutable
|
||||
|
||||
@@ -40,10 +38,6 @@ object HatchConfiguration {
|
||||
return result
|
||||
}
|
||||
|
||||
suspend fun detectHatchExecutable(eelApi: EelApi): Path? {
|
||||
val hatchCommand = eelApi.getHatchCommand()
|
||||
val hatchPath = eelApi.exec.where(hatchCommand)?.asNioPath()
|
||||
return hatchPath
|
||||
}
|
||||
suspend fun detectHatchExecutable(eelApi: EelApi): Path? = detectTool("hatch", eelApi).successOrNull
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
package com.intellij.python.hatch.runtime
|
||||
|
||||
import com.intellij.platform.eel.EelApi
|
||||
import com.intellij.platform.eel.EelPlatform
|
||||
|
||||
fun EelApi.getHatchCommand(): String = when (platform) {
|
||||
is EelPlatform.Windows -> "hatch.exe"
|
||||
else -> "hatch"
|
||||
}
|
||||
@@ -82,3 +82,5 @@ 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
|
||||
|
||||
cannot.find.executable=Cannot find executable "{0}" in {1}
|
||||
|
||||
81
python/python-sdk/src/com/jetbrains/python/sdk/toolCli.kt
Normal file
81
python/python-sdk/src/com/jetbrains/python/sdk/toolCli.kt
Normal file
@@ -0,0 +1,81 @@
|
||||
// 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.sdk
|
||||
|
||||
import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.platform.eel.EelApi
|
||||
import com.intellij.platform.eel.environmentVariables
|
||||
import com.intellij.platform.eel.fs.getPath
|
||||
import com.intellij.platform.eel.isWindows
|
||||
import com.intellij.platform.eel.provider.asNioPath
|
||||
import com.intellij.platform.eel.provider.getEelDescriptor
|
||||
import com.intellij.platform.eel.provider.localEel
|
||||
import com.intellij.platform.eel.where
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.sdk.impl.PySdkBundle
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.isExecutable
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
/**
|
||||
* Detects the path to a CLI tool executable in the given Eel environment.
|
||||
*
|
||||
* Search order (first match wins):
|
||||
* - [EelApi.exec.where] for the given [toolName].
|
||||
* - User-local locations depending on the target platform:
|
||||
* - Unix-like: `~/.local/bin/<toolName>`
|
||||
* - Windows: `%APPDATA%/Python/Scripts/<toolName>.exe`
|
||||
* - Provided [additionalSearchPaths], with each candidate resolved as `<path>/<toolName>` (or
|
||||
* `<toolName>.exe|.bat` on Windows).
|
||||
*
|
||||
* Notes:
|
||||
* - Entries in [additionalSearchPaths] must belong to the same Eel descriptor as [eel];
|
||||
* paths with a different descriptor are skipped and a warning is logged.
|
||||
*
|
||||
* @param toolName Name of the tool to locate (without an extension).
|
||||
* @param eel Eel environment to search in; defaults to the local Eel.
|
||||
* @param additionalSearchPaths Extra directories to probe, must be on the same descriptor as [eel].
|
||||
* @return [PyResult] containing the resolved executable [Path] on success; otherwise a localized error
|
||||
* explaining that the executable could not be found on the target machine.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
suspend fun detectTool(
|
||||
toolName: String,
|
||||
eel: EelApi = localEel,
|
||||
additionalSearchPaths: List<Path> = listOf(),
|
||||
): PyResult<Path> = withContext(Dispatchers.IO) {
|
||||
val binary = eel.exec.where(toolName)?.asNioPath()
|
||||
if (binary != null) {
|
||||
return@withContext PyResult.success(binary)
|
||||
}
|
||||
|
||||
val binaryName = if (eel.platform.isWindows) "$toolName.exe" else toolName
|
||||
val paths = buildList {
|
||||
if (eel.platform.isWindows) addWindowsPaths(eel, binaryName) else addUnixPaths(eel, binaryName)
|
||||
additionalSearchPaths.forEach {
|
||||
if (it.getEelDescriptor() != eel.descriptor) {
|
||||
fileLogger().warn("Additional search paths should be on the same descriptor as Eel API, skipping ${it.pathString}")
|
||||
}
|
||||
else add(it.resolve(binaryName))
|
||||
}
|
||||
}
|
||||
|
||||
paths.firstOrNull { it.isExecutable() }?.let { PyResult.success(it) }
|
||||
?: PyResult.localizedError(PySdkBundle.message("cannot.find.executable", toolName, localEel.descriptor.machine.name))
|
||||
}
|
||||
|
||||
private fun MutableList<Path>.addUnixPaths(eel: EelApi, binaryName: String) {
|
||||
add(eel.userInfo.home.asNioPath().resolve(Path.of(".local", "bin", binaryName)))
|
||||
}
|
||||
|
||||
private suspend fun MutableList<Path>.addWindowsPaths(eel: EelApi, binaryName: String) {
|
||||
val env = eel.exec.environmentVariables().eelIt().await()
|
||||
val envsToCheck = listOf("APPDATA", "LOCALAPPDATA")
|
||||
envsToCheck.forEach { envToCheck ->
|
||||
env[envToCheck]?.let {
|
||||
add(eel.fs.getPath(it).asNioPath().resolve(Path.of("Python", "Scripts", binaryName)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ internal class HatchPackageManager(project: Project, sdk: Sdk) : PipPythonPackag
|
||||
}
|
||||
|
||||
internal class HatchPackageManagerProvider : PythonPackageManagerProvider {
|
||||
override fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager? = when {
|
||||
override suspend fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager? = when {
|
||||
sdk.isHatch -> HatchPackageManager(project, sdk)
|
||||
else -> null
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Experimental
|
||||
class CondaPackageManagerProvider : PythonPackageManagerProvider {
|
||||
override fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager? =
|
||||
override suspend fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager? =
|
||||
if (sdk.isCondaVirtualEnv) createCondaPackageManager(project, sdk) else null
|
||||
|
||||
private fun createCondaPackageManager(project: Project, sdk: Sdk): PythonPackageManager =
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.util.cancelOnDispose
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.intellij.util.messages.Topic
|
||||
import com.jetbrains.python.NON_INTERACTIVE_ROOT_TRACE_CONTEXT
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
@@ -56,7 +57,6 @@ abstract class PythonPackageManager(val project: Project, val sdk: Sdk) : Dispos
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ApiStatus.Internal
|
||||
@Volatile
|
||||
protected var installedPackages: List<PythonPackage> = emptyList()
|
||||
@@ -228,7 +228,7 @@ abstract class PythonPackageManager(val project: Project, val sdk: Sdk) : Dispos
|
||||
@ApiStatus.Internal
|
||||
open suspend fun extractDependencies(): PyResult<List<PythonPackage>>? = null
|
||||
|
||||
@ApiStatus.Internal
|
||||
@ApiStatus.Internal
|
||||
suspend fun waitForInit() {
|
||||
initializationJob?.join()
|
||||
if (shouldBeInitInstantly()) {
|
||||
@@ -256,18 +256,18 @@ abstract class PythonPackageManager(val project: Project, val sdk: Sdk) : Dispos
|
||||
private fun shouldBeInitInstantly(): Boolean = ApplicationManager.getApplication().isUnitTestMode
|
||||
|
||||
companion object {
|
||||
@RequiresBackgroundThread
|
||||
fun forSdk(project: Project, sdk: Sdk): PythonPackageManager {
|
||||
val pythonPackageManagerService = project.service<PythonPackageManagerService>()
|
||||
val manager = pythonPackageManagerService.forSdk(project, sdk)
|
||||
return runBlockingMaybeCancellable {
|
||||
val manager = pythonPackageManagerService.forSdk(project, sdk)
|
||||
|
||||
|
||||
if (manager.shouldBeInitInstantly()) {
|
||||
runBlockingMaybeCancellable {
|
||||
if (manager.shouldBeInitInstantly()) {
|
||||
manager.initInstalledPackages()
|
||||
}
|
||||
}
|
||||
|
||||
return manager
|
||||
manager
|
||||
}
|
||||
}
|
||||
|
||||
@Topic.AppLevel
|
||||
@@ -277,7 +277,7 @@ abstract class PythonPackageManager(val project: Project, val sdk: Sdk) : Dispos
|
||||
@ApiStatus.Internal
|
||||
data class PackageManagerErrorMessage(
|
||||
@param:Nls val descriptionMessage: String,
|
||||
@param:Nls val fixCommandMessage: String
|
||||
@param:Nls val fixCommandMessage: String,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ interface PythonPackageManagerProvider {
|
||||
* package management files etc.
|
||||
* Sdk is expected to be a Python Sdk and have PythonSdkAdditionalData.
|
||||
*/
|
||||
fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager?
|
||||
suspend fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager?
|
||||
|
||||
companion object {
|
||||
val EP_NAME = ExtensionPointName.create<PythonPackageManagerProvider>("Pythonid.pythonPackageManagerProvider")
|
||||
@@ -26,7 +26,7 @@ interface PythonPackageManagerProvider {
|
||||
@ApiStatus.Internal
|
||||
@ApiStatus.Experimental
|
||||
interface PythonPackageManagerService {
|
||||
fun forSdk(project: Project, sdk: Sdk): PythonPackageManager
|
||||
suspend fun forSdk(project: Project, sdk: Sdk): PythonPackageManager
|
||||
|
||||
/**
|
||||
* Provides an implementation bridge for Python package management operations
|
||||
|
||||
@@ -11,26 +11,30 @@ import com.jetbrains.python.packaging.utils.PyPackageCoroutine
|
||||
import com.jetbrains.python.sdk.PythonSdkAdditionalData
|
||||
import com.jetbrains.python.sdk.getOrCreateAdditionalData
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.async
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
internal class PythonPackageManagerServiceImpl(private val serviceScope: CoroutineScope) : PythonPackageManagerService, Disposable {
|
||||
private val cache = ConcurrentHashMap<UUID, PythonPackageManager>()
|
||||
private val cache = ConcurrentHashMap<UUID, Deferred<PythonPackageManager>>()
|
||||
|
||||
private val bridgeCache = ConcurrentHashMap<UUID, PythonPackageManagementServiceBridge>()
|
||||
|
||||
/**
|
||||
* Requires Sdk to be Python Sdk and have PythonSdkAdditionalData.
|
||||
*/
|
||||
override fun forSdk(project: Project, sdk: Sdk): PythonPackageManager {
|
||||
override suspend fun forSdk(project: Project, sdk: Sdk): PythonPackageManager {
|
||||
val cacheKey = (sdk.getOrCreateAdditionalData()).uuid
|
||||
|
||||
return cache.computeIfAbsent(cacheKey) {
|
||||
val createdSdk = PythonPackageManagerProvider.EP_NAME.extensionList.firstNotNullOf { it.createPackageManagerForSdk(project, sdk) }
|
||||
Disposer.register(PyPackageCoroutine.getInstance(project), createdSdk)
|
||||
PythonRequirementTxtSdkUtils.migrateRequirementsTxtPathFromModuleToSdk(project, sdk)
|
||||
createdSdk
|
||||
}
|
||||
serviceScope.async {
|
||||
val createdSdk = PythonPackageManagerProvider.EP_NAME.extensionList.firstNotNullOf { it.createPackageManagerForSdk(project, sdk) }
|
||||
Disposer.register(PyPackageCoroutine.getInstance(project), createdSdk)
|
||||
PythonRequirementTxtSdkUtils.migrateRequirementsTxtPathFromModuleToSdk(project, sdk)
|
||||
createdSdk
|
||||
}
|
||||
}.await()
|
||||
}
|
||||
|
||||
override fun bridgeForSdk(project: Project, sdk: Sdk): PythonPackageManagementServiceBridge {
|
||||
|
||||
@@ -10,7 +10,7 @@ import org.jetbrains.annotations.ApiStatus
|
||||
@ApiStatus.Experimental
|
||||
@ApiStatus.Internal
|
||||
class PipPackageManagerProvider : PythonPackageManagerProvider {
|
||||
override fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager {
|
||||
override suspend fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager {
|
||||
return PipPythonPackageManager(project, sdk)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
class PipEnvPackageManagerProvider : PythonPackageManagerProvider {
|
||||
override fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager? =
|
||||
override suspend fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager? =
|
||||
if (sdk.isPipEnv) PipEnvPackageManager(project, sdk) else null
|
||||
|
||||
}
|
||||
@@ -54,6 +54,7 @@ import com.intellij.remote.RemoteSdkProperties;
|
||||
import com.intellij.remote.TargetAwarePathMappingProvider;
|
||||
import com.intellij.util.PathMappingSettings;
|
||||
import com.intellij.util.PlatformUtils;
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.execution.ParametersListUtil;
|
||||
@@ -366,7 +367,7 @@ public abstract class PythonCommandLineState extends CommandLineState {
|
||||
if (sdk != null && getEnableRunTool()) {
|
||||
PyRunToolProvider runToolProvider = PyRunToolProvider.forSdk(sdk);
|
||||
if (runToolProvider != null && useRunTool(myConfig, sdk)) {
|
||||
runToolParameters = runToolProvider.getRunToolParameters();
|
||||
runToolParameters = PythonCommandLineStateExKt.getRunToolParametersForJvm(runToolProvider);
|
||||
PyRunToolUsageCollector.logRun(myConfig.getProject(), PyRunToolIds.idOf(runToolProvider));
|
||||
}
|
||||
}
|
||||
@@ -626,6 +627,7 @@ public abstract class PythonCommandLineState extends CommandLineState {
|
||||
* @param helpersAwareRequest the request
|
||||
* @return the representation of Python script or module execution
|
||||
*/
|
||||
@RequiresBackgroundThread
|
||||
protected @NotNull PythonExecution buildPythonExecution(@NotNull HelpersAwareTargetEnvironmentRequest helpersAwareRequest) {
|
||||
throw new UnsupportedOperationException("The implementation of Run Configuration based on Targets API is absent");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.run
|
||||
|
||||
import com.intellij.openapi.progress.runBlockingMaybeCancellable
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.run.features.PyRunToolParameters
|
||||
import com.jetbrains.python.run.features.PyRunToolProvider
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
/**
|
||||
* To be used by [PythonCommandLineState] only
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
@RequiresBackgroundThread
|
||||
fun PyRunToolProvider.getRunToolParametersForJvm(): PyRunToolParameters = runBlockingMaybeCancellable { getRunToolParameters() }
|
||||
@@ -2,8 +2,10 @@
|
||||
package com.jetbrains.python.run.features
|
||||
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.progress.runBlockingMaybeCancellable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jetbrains.annotations.NonNls
|
||||
@@ -35,7 +37,7 @@ data class PyRunToolData(
|
||||
@param:NonNls val id: PyRunToolId,
|
||||
@param:Nls val name: String,
|
||||
@param:Nls val group: String,
|
||||
@param:NonNls val idForStatistics: String = id.value
|
||||
@param:NonNls val idForStatistics: String = id.value,
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -69,7 +71,7 @@ interface PyRunToolProvider {
|
||||
* Represents the parameters required to configure and run a Python tool.
|
||||
* This includes the path to the executable and a list of associated arguments.
|
||||
*/
|
||||
val runToolParameters: PyRunToolParameters
|
||||
suspend fun getRunToolParameters(): PyRunToolParameters
|
||||
|
||||
/**
|
||||
* Represents the initial state of the tool, determining whether it is enabled or not by default.
|
||||
@@ -82,13 +84,16 @@ interface PyRunToolProvider {
|
||||
* @param sdk the SDK to check the availability for
|
||||
* @return true if the tool is available for the specified SDK, false otherwise
|
||||
*/
|
||||
fun isAvailable(sdk: Sdk): Boolean
|
||||
suspend fun isAvailable(sdk: Sdk): Boolean
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val EP: ExtensionPointName<PyRunToolProvider> = ExtensionPointName.create("Pythonid.pyRunToolProvider")
|
||||
|
||||
@JvmStatic
|
||||
fun forSdk(sdk: Sdk): PyRunToolProvider? = EP.extensionList.firstOrNull { it.isAvailable(sdk) }
|
||||
@RequiresBackgroundThread
|
||||
fun forSdk(sdk: Sdk): PyRunToolProvider? = runBlockingMaybeCancellable {
|
||||
EP.extensionList.firstOrNull { it.isAvailable(sdk) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,9 +10,7 @@ import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.platform.eel.EelApi
|
||||
import com.intellij.platform.eel.provider.asNioPath
|
||||
import com.intellij.platform.eel.provider.localEel
|
||||
import com.intellij.platform.eel.where
|
||||
import com.intellij.python.community.execService.*
|
||||
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
|
||||
import com.intellij.python.community.services.internal.impl.VanillaPythonWithPythonInfoImpl
|
||||
@@ -188,9 +186,7 @@ sealed interface FileSystem<P : PathHolder> {
|
||||
return pythonHome.path.resolvePythonBinary()?.let { PathHolder.Eel(it) }
|
||||
}
|
||||
|
||||
override suspend fun which(cmd: String): PathHolder.Eel? {
|
||||
return eelApi.exec.where(cmd)?.asNioPath()?.let { PathHolder.Eel(it) }
|
||||
}
|
||||
override suspend fun which(cmd: String): PathHolder.Eel? = detectTool(cmd, eelApi).mapSuccess { PathHolder.Eel(it) }.successOrNull
|
||||
}
|
||||
|
||||
data class Target(
|
||||
|
||||
@@ -161,7 +161,7 @@ internal class PythonSdkPanelBuilderAndSdkCreator(
|
||||
}
|
||||
}
|
||||
|
||||
private fun initialize(scope: CoroutineScope) {
|
||||
private suspend fun initialize(scope: CoroutineScope) {
|
||||
model.initialize(scope)
|
||||
|
||||
pythonBaseVersionComboBox.initialize(scope, model.baseInterpreters)
|
||||
|
||||
@@ -8,8 +8,6 @@ import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.openapi.diagnostic.rethrowControlFlowException
|
||||
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||
import com.intellij.openapi.observable.properties.PropertyGraph
|
||||
import com.intellij.platform.eel.EelApi
|
||||
import com.intellij.platform.eel.EelPlatform
|
||||
import com.intellij.platform.eel.provider.localEel
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.conda.loadLocalPythonCondaPath
|
||||
@@ -52,7 +50,7 @@ class CondaViewModel<P : PathHolder>(
|
||||
}
|
||||
}
|
||||
|
||||
fileSystem.which(eelApi.getCondaCommand())?.let { return@ToolValidator it }
|
||||
fileSystem.which("conda")?.let { return@ToolValidator it }
|
||||
|
||||
// legacy slow fallback detection via the defined list of paths in case of there is no conda on the PATH (PY-85060),
|
||||
// not sure if it is worth it to keep it, because if there is no conda on the PATH the installation might be broken
|
||||
@@ -115,11 +113,3 @@ class CondaViewModel<P : PathHolder>(
|
||||
return@withContext PyResult.success(environments)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* correctly detects the platform only for eelApi, for targets Windows is not supported
|
||||
*/
|
||||
private fun EelApi?.getCondaCommand(): String = when {
|
||||
this?.platform is EelPlatform.Windows -> "conda.bat"
|
||||
else -> "conda"
|
||||
}
|
||||
@@ -36,14 +36,14 @@ internal class UvInterpreterSection(
|
||||
}.visibleIf(_uv)
|
||||
}
|
||||
|
||||
fun onShown(scope: CoroutineScope) {
|
||||
suspend fun onShown(scope: CoroutineScope) {
|
||||
selectUvIfExists()
|
||||
uvCreator.onShown(scope)
|
||||
}
|
||||
|
||||
fun hintVisiblePredicate() = _uv and model.uvViewModel.uvExecutable.isNotNull()
|
||||
|
||||
private fun selectUvIfExists() {
|
||||
private suspend fun selectUvIfExists() {
|
||||
if (PropertiesComponent.getInstance().getValue(FAV_MODE) != null) return
|
||||
if (hasUvExecutable() && selectedMode.get() != PythonInterpreterSelectionMode.PROJECT_UV) {
|
||||
selectedMode.set(PythonInterpreterSelectionMode.PROJECT_UV)
|
||||
|
||||
@@ -4,10 +4,8 @@ package com.jetbrains.python.sdk.pipenv
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.progress.runBlockingCancellable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.platform.eel.provider.asNioPath
|
||||
import com.intellij.platform.eel.EelApi
|
||||
import com.intellij.platform.eel.provider.localEel
|
||||
import com.intellij.platform.eel.where
|
||||
import com.intellij.python.community.execService.ProcessOutputTransformer
|
||||
import com.intellij.python.community.execService.ZeroCodeStdoutTransformer
|
||||
import com.intellij.python.community.impl.pipenv.pipenvPath
|
||||
@@ -17,6 +15,7 @@ import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.getOrNull
|
||||
import com.jetbrains.python.sdk.add.v2.PathHolder
|
||||
import com.jetbrains.python.sdk.createSdk
|
||||
import com.jetbrains.python.sdk.detectTool
|
||||
import com.jetbrains.python.sdk.runExecutableWithProgress
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -41,18 +40,7 @@ suspend fun <T> runPipEnv(dirPath: Path?, vararg args: String, transformer: Proc
|
||||
* Detects the pipenv executable in `$PATH`.
|
||||
*/
|
||||
@Internal
|
||||
suspend fun detectPipEnvExecutable(): PyResult<Path> {
|
||||
val name = when {
|
||||
SystemInfo.isWindows -> "pipenv.exe"
|
||||
else -> "pipenv"
|
||||
}
|
||||
val executablePath = localEel.exec.where(name)?.asNioPath()
|
||||
if (executablePath == null) {
|
||||
return PyResult.localizedError(PyBundle.message("cannot.find.executable", name, localEel.descriptor.machine.name))
|
||||
}
|
||||
|
||||
return PyResult.success(executablePath)
|
||||
}
|
||||
suspend fun detectPipEnvExecutable(eel: EelApi = localEel): PyResult<Path> = detectTool("pipenv", eel)
|
||||
|
||||
@Internal
|
||||
fun detectPipEnvExecutableOrNull(): Path? {
|
||||
@@ -121,4 +109,4 @@ private suspend fun setUpPipEnv(moduleBasePath: Path, basePythonBinaryPath: Pyth
|
||||
VirtualEnvReader.Instance.findPythonInPythonRoot(Path.of(pipEnv))?.toString()
|
||||
} ?: return PyResult.localizedError(PyBundle.message("python.sdk.provided.path.is.invalid", pipEnv))
|
||||
return PyResult.success(Path.of(pipEnvExecutablePathString))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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.sdk.poetry
|
||||
|
||||
import com.intellij.execution.Platform
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
@@ -11,7 +10,6 @@ import com.intellij.platform.eel.EelApi
|
||||
import com.intellij.platform.eel.provider.asNioPath
|
||||
import com.intellij.platform.eel.provider.getEelDescriptor
|
||||
import com.intellij.platform.eel.provider.localEel
|
||||
import com.intellij.platform.eel.provider.systemOs
|
||||
import com.intellij.python.community.execService.Args
|
||||
import com.intellij.python.community.execService.BinOnEel
|
||||
import com.intellij.python.community.execService.ExecService
|
||||
@@ -25,10 +23,7 @@ import com.jetbrains.python.packaging.PyRequirement
|
||||
import com.jetbrains.python.packaging.PyRequirementParser
|
||||
import com.jetbrains.python.packaging.common.PythonOutdatedPackage
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import com.jetbrains.python.sdk.PyDetectedSdk
|
||||
import com.jetbrains.python.sdk.associatedModulePath
|
||||
import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.runExecutableWithProgress
|
||||
import com.jetbrains.python.sdk.*
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader
|
||||
import io.github.z4kn4fein.semver.Version
|
||||
import io.github.z4kn4fein.semver.toVersion
|
||||
@@ -61,24 +56,9 @@ suspend fun runPoetry(projectPath: Path?, vararg args: String): PyResult<String>
|
||||
/**
|
||||
* Detects the poetry executable in `$PATH`.
|
||||
*/
|
||||
internal suspend fun detectPoetryExecutable(eel: EelApi = localEel): PyResult<Path> {
|
||||
val windows = eel.systemOs().platform == Platform.WINDOWS
|
||||
val poetryBinNames = if (windows) {
|
||||
setOf("poetry.exe", "poetry.bat")
|
||||
}
|
||||
else {
|
||||
setOf("poetry")
|
||||
}
|
||||
|
||||
internal suspend fun detectPoetryExecutable(eel: EelApi = localEel): PyResult<Path> =
|
||||
// TODO: Poetry from store isn't detected because local eel doesn't obey appx binaries. We need to fix it on eel side
|
||||
val userHomePoetry = eel.userInfo.home.resolve(".poetry").resolve(".bin")
|
||||
val executablePath = withContext(Dispatchers.IO) {
|
||||
poetryBinNames.flatMap { eel.exec.findExeFilesInPath(it) }.firstOrNull()?.asNioPath()
|
||||
?: poetryBinNames.map { userHomePoetry.resolve(it).asNioPath() }.firstOrNull { it.exists() }
|
||||
}
|
||||
|
||||
return executablePath?.let { PyResult.success(it) } ?: PyResult.localizedError(poetryNotFoundException)
|
||||
}
|
||||
detectTool("poetry", eel, listOf(eel.userInfo.home.asNioPath().resolve(Path.of(".poetry", ".bin"))))
|
||||
|
||||
/**
|
||||
* Returns the configured poetry executable or detects it automatically.
|
||||
|
||||
@@ -10,5 +10,5 @@ import com.jetbrains.python.packaging.management.PythonPackageManagerProvider
|
||||
*/
|
||||
|
||||
class PoetryPackageManagerProvider : PythonPackageManagerProvider {
|
||||
override fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager? = if (sdk.isPoetry) PoetryPackageManager(project, sdk) else null
|
||||
override suspend fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager? = if (sdk.isPoetry) PoetryPackageManager(project, sdk) else null
|
||||
}
|
||||
@@ -70,9 +70,9 @@ internal class UvPackageManager(project: Project, sdk: Sdk, private val uv: UvLo
|
||||
return it
|
||||
}.map { it.name }
|
||||
|
||||
val categorizedPackages = packages
|
||||
val categorizedPackages = packages
|
||||
.map { PyPackageName.from(it) }
|
||||
.partition { it.name !in dependencyNames || sdk.uvUsePackageManagement }
|
||||
.partition { it.name !in dependencyNames || sdk.uvUsePackageManagement }
|
||||
|
||||
return PyResult.success(categorizedPackages)
|
||||
}
|
||||
@@ -128,7 +128,7 @@ internal class UvPackageManager(project: Project, sdk: Sdk, private val uv: UvLo
|
||||
}
|
||||
|
||||
class UvPackageManagerProvider : PythonPackageManagerProvider {
|
||||
override fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager? {
|
||||
override suspend fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager? {
|
||||
if (!sdk.isUv) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,16 +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.jetbrains.python.sdk.uv.impl
|
||||
|
||||
import com.intellij.execution.configurations.PathEnvironmentVariableUtil
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.ui.ValidationInfo
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.util.SystemProperties
|
||||
import com.intellij.platform.eel.EelApi
|
||||
import com.intellij.platform.eel.provider.localEel
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.pathValidation.PlatformAndRoot
|
||||
import com.jetbrains.python.pathValidation.ValidationRequest
|
||||
import com.jetbrains.python.pathValidation.validateExecutableFile
|
||||
import com.jetbrains.python.sdk.detectTool
|
||||
import com.jetbrains.python.sdk.runExecutableWithProgress
|
||||
import com.jetbrains.python.sdk.uv.UvCli
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
@@ -54,42 +54,21 @@ private class UvCliImpl(val dispatcher: CoroutineDispatcher, val uv: Path) : UvC
|
||||
}
|
||||
}
|
||||
|
||||
fun detectUvExecutable(): Path? {
|
||||
val name = when {
|
||||
SystemInfo.isWindows -> "uv.exe"
|
||||
else -> "uv"
|
||||
}
|
||||
suspend fun detectUvExecutable(eel: EelApi): Path? = detectTool("uv", eel).successOrNull
|
||||
|
||||
val binary = PathEnvironmentVariableUtil.findInPath(name)?.toPath()
|
||||
if (binary != null) {
|
||||
return binary
|
||||
}
|
||||
|
||||
val userHome = SystemProperties.getUserHome()
|
||||
val appData = if (SystemInfo.isWindows) System.getenv("APPDATA") else null
|
||||
val paths = mutableListOf<Path>().apply {
|
||||
add(Path.of(userHome, ".local", "bin", name))
|
||||
if (appData != null) {
|
||||
add(Path.of(appData, "Python", "Scripts", name))
|
||||
}
|
||||
}
|
||||
|
||||
return paths.firstOrNull { it.exists() }
|
||||
}
|
||||
|
||||
fun getUvExecutable(): Path? {
|
||||
return PropertiesComponent.getInstance().uvPath?.takeIf { it.exists() } ?: detectUvExecutable()
|
||||
suspend fun getUvExecutable(eel: EelApi = localEel): Path? {
|
||||
return PropertiesComponent.getInstance().uvPath?.takeIf { it.exists() } ?: detectUvExecutable(eel)
|
||||
}
|
||||
|
||||
fun setUvExecutable(path: Path) {
|
||||
PropertiesComponent.getInstance().uvPath = path
|
||||
}
|
||||
|
||||
fun hasUvExecutable(): Boolean {
|
||||
suspend fun hasUvExecutable(): Boolean {
|
||||
return getUvExecutable() != null
|
||||
}
|
||||
|
||||
fun createUvCli(uv: Path? = null, dispatcher: CoroutineDispatcher = Dispatchers.IO): PyResult<UvCli> {
|
||||
suspend fun createUvCli(uv: Path? = null, dispatcher: CoroutineDispatcher = Dispatchers.IO): PyResult<UvCli> {
|
||||
val path = uv ?: getUvExecutable()
|
||||
val error = validateUvExecutable(path)
|
||||
return if (error != null) {
|
||||
|
||||
@@ -6,8 +6,6 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.errorProcessing.*
|
||||
import com.jetbrains.python.errorProcessing.PyExecResult
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.onFailure
|
||||
import com.jetbrains.python.packaging.PyPackageName
|
||||
import com.jetbrains.python.packaging.common.PythonOutdatedPackage
|
||||
@@ -278,7 +276,7 @@ private class UvLowLevelImpl(val cwd: Path, private val uvCli: UvCli) : UvLowLev
|
||||
|
||||
override suspend fun sync(): PyResult<String> {
|
||||
return uvCli.runUv(cwd, "sync")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun lock(): PyResult<String> {
|
||||
return uvCli.runUv(cwd, "lock")
|
||||
@@ -318,7 +316,7 @@ fun createUvLowLevel(cwd: Path, uvCli: UvCli): UvLowLevel {
|
||||
return UvLowLevelImpl(cwd, uvCli)
|
||||
}
|
||||
|
||||
fun createUvLowLevel(cwd: Path): PyResult<UvLowLevel> = createUvCli().mapSuccess { createUvLowLevel(cwd, it) }
|
||||
suspend fun createUvLowLevel(cwd: Path): PyResult<UvLowLevel> = createUvCli().mapSuccess { createUvLowLevel(cwd, it) }
|
||||
|
||||
private fun tryExtractStderr(err: PyError): String? =
|
||||
when (err) {
|
||||
|
||||
@@ -16,7 +16,7 @@ import kotlin.io.path.pathString
|
||||
|
||||
@ApiStatus.Internal
|
||||
@RequiresBackgroundThread(generateAssertion = false)
|
||||
fun buildUvRunConfigurationCli(options: UvRunConfigurationOptions, isDebug: Boolean): PythonExecution {
|
||||
suspend fun buildUvRunConfigurationCli(options: UvRunConfigurationOptions, isDebug: Boolean): PythonExecution {
|
||||
val toolPath = requireNotNull(getUvExecutable()) { "Unable to find uv executable." }
|
||||
val toolParams = mutableListOf("run")
|
||||
|
||||
@@ -47,9 +47,11 @@ fun buildUvRunConfigurationCli(options: UvRunConfigurationOptions, isDebug: Bool
|
||||
toolPath.pathString,
|
||||
options.scriptOrModule
|
||||
) + toolParams
|
||||
} else if (!isDebug) {
|
||||
}
|
||||
else if (!isDebug) {
|
||||
toolParams + "--script"
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
toolParams
|
||||
},
|
||||
pythonScriptPath = constant(Path.of(options.scriptOrModule))
|
||||
|
||||
@@ -5,8 +5,10 @@ import com.intellij.execution.runners.ExecutionEnvironment
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.progress.runBlockingMaybeCancellable
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.Result
|
||||
@@ -41,9 +43,15 @@ class UvRunConfigurationState(
|
||||
)
|
||||
}
|
||||
|
||||
override fun buildPythonExecution(helpersAwareRequest: HelpersAwareTargetEnvironmentRequest): PythonExecution {
|
||||
return buildUvRunConfigurationCli(uvRunConfiguration.options, isDebug)
|
||||
}
|
||||
/**
|
||||
* I'd prefer this whole function to be suspendable, but unfortunately it's an override from Java code, so we have to use
|
||||
* [runBlockingMaybeCancellable] here :(
|
||||
*/
|
||||
@RequiresBackgroundThread
|
||||
override fun buildPythonExecution(helpersAwareRequest: HelpersAwareTargetEnvironmentRequest): PythonExecution =
|
||||
runBlockingMaybeCancellable {
|
||||
buildUvRunConfigurationCli(uvRunConfiguration.options, isDebug)
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
@@ -63,12 +71,12 @@ fun canRun(
|
||||
}
|
||||
|
||||
val workingDirectory = options.workingDirectory
|
||||
val uvExecutable = getUvExecutable()
|
||||
var isError = false
|
||||
var isUnsynced = false
|
||||
runWithModalProgressBlocking(project, PyBundle.message("uv.run.configuration.state.progress.name")) {
|
||||
val uvExecutable = getUvExecutable()
|
||||
|
||||
if (workingDirectory != null && uvExecutable != null) {
|
||||
runWithModalProgressBlocking(project, PyBundle.message("uv.run.configuration.state.progress.name")) {
|
||||
if (workingDirectory != null && uvExecutable != null) {
|
||||
val uv = createUvCli(uvExecutable).mapSuccess { createUvLowLevel(workingDirectory, it) }.getOrNull()
|
||||
|
||||
when (uv?.let { requiresSync(it, options, logger) }?.getOrNull()) {
|
||||
@@ -77,9 +85,9 @@ fun canRun(
|
||||
null -> isError = true
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
isError = true
|
||||
else {
|
||||
isError = true
|
||||
}
|
||||
}
|
||||
|
||||
if (isError || isUnsynced) {
|
||||
|
||||
@@ -9,30 +9,43 @@ import com.jetbrains.python.run.features.PyRunToolParameters
|
||||
import com.jetbrains.python.run.features.PyRunToolProvider
|
||||
import com.jetbrains.python.sdk.uv.impl.getUvExecutable
|
||||
import com.jetbrains.python.sdk.uv.isUv
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
||||
/**
|
||||
* PyRunToolProvider implementation that runs scripts/modules using `uv run`.
|
||||
*/
|
||||
private class UvRunToolProvider : PyRunToolProvider {
|
||||
|
||||
override suspend fun getRunToolParameters(): PyRunToolParameters {
|
||||
if (!runToolParameters.isCompleted) {
|
||||
runToolParametersMutex.withLock {
|
||||
if (!runToolParameters.isCompleted) {
|
||||
runToolParameters.complete(
|
||||
PyRunToolParameters(requireNotNull(getUvExecutable()?.toString()) { "Unable to find uv executable." }, listOf("run"))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return runToolParameters.await()
|
||||
}
|
||||
|
||||
override val runToolData: PyRunToolData = PyRunToolData(
|
||||
PyRunToolId("uv.run"),
|
||||
PyBundle.message("uv.run.configuration.type.display.name"),
|
||||
PyBundle.message("python.run.configuration.fragments.python.group"),
|
||||
)
|
||||
|
||||
override val initialToolState: Boolean = true
|
||||
|
||||
override suspend fun isAvailable(sdk: Sdk): Boolean = sdk.isUv && getUvExecutable() != null
|
||||
|
||||
/**
|
||||
* We use runToolParameters only if a tool provider is available. So we need to have a lazy initialization here
|
||||
* to construct these parameters iff the validation has passed.
|
||||
*/
|
||||
override val runToolParameters: PyRunToolParameters by lazy {
|
||||
PyRunToolParameters(
|
||||
requireNotNull(getUvExecutable()?.toString()) { "Unable to find uv executable." },
|
||||
listOf("run")
|
||||
)
|
||||
}
|
||||
|
||||
override val initialToolState: Boolean = true
|
||||
|
||||
override fun isAvailable(sdk: Sdk): Boolean = sdk.isUv && getUvExecutable() != null
|
||||
private val runToolParameters = CompletableDeferred<PyRunToolParameters>()
|
||||
private val runToolParametersMutex = Mutex()
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class TestPackageManagerProvider : PythonPackageManagerProvider {
|
||||
}
|
||||
|
||||
|
||||
override fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager {
|
||||
override suspend fun createPackageManagerForSdk(project: Project, sdk: Sdk): PythonPackageManager {
|
||||
return TestPythonPackageManager(project, sdk)
|
||||
.withPackageNames(packageNames)
|
||||
.withPackageDetails(packageDetails)
|
||||
|
||||
@@ -15,7 +15,7 @@ import org.jetbrains.annotations.TestOnly
|
||||
@TestOnly
|
||||
class TestPythonPackageManagerService(val installedPackages: List<PythonPackage> = emptyList()) : PythonPackageManagerService {
|
||||
|
||||
override fun forSdk(project: Project, sdk: Sdk): PythonPackageManager {
|
||||
override suspend fun forSdk(project: Project, sdk: Sdk): PythonPackageManager {
|
||||
installedPackages.ifEmpty {
|
||||
return TestPythonPackageManager(project, sdk).also { Disposer.register(project, it) }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user