diff --git a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/configuration/PyUvSdkConfiguration.kt b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/configuration/PyUvSdkConfiguration.kt index 83dc69a1a691..6b689f1dd9fa 100644 --- a/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/configuration/PyUvSdkConfiguration.kt +++ b/python/ide/impl/src/com/intellij/pycharm/community/ide/impl/configuration/PyUvSdkConfiguration.kt @@ -14,7 +14,7 @@ import com.jetbrains.python.sdk.* import com.jetbrains.python.sdk.basePath import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension import com.jetbrains.python.sdk.uv.PY_PROJECT_TOML -import com.jetbrains.python.sdk.uv.getUvExecutable +import com.jetbrains.python.sdk.uv.impl.getUvExecutable import com.jetbrains.python.sdk.uv.setupUvSdkUnderProgress import java.io.FileNotFoundException import java.nio.file.Path diff --git a/python/src/com/jetbrains/python/sdk/add/v2/EnvironmentCreatorUv.kt b/python/src/com/jetbrains/python/sdk/add/v2/EnvironmentCreatorUv.kt index 91607135a144..928ae19c969c 100644 --- a/python/src/com/jetbrains/python/sdk/add/v2/EnvironmentCreatorUv.kt +++ b/python/src/com/jetbrains/python/sdk/add/v2/EnvironmentCreatorUv.kt @@ -1,13 +1,12 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.python.sdk.add.v2 -import com.intellij.ide.util.PropertiesComponent import com.intellij.openapi.module.Module import com.intellij.openapi.observable.properties.ObservableMutableProperty import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk import com.jetbrains.python.sdk.ModuleOrProject -import com.jetbrains.python.sdk.uv.uvPath +import com.jetbrains.python.sdk.uv.impl.setUvExecutable import com.jetbrains.python.sdk.uv.setupUvSdkUnderProgress import com.jetbrains.python.statistics.InterpreterType import java.nio.file.Path @@ -24,7 +23,7 @@ class EnvironmentCreatorUv(model: PythonMutableTargetAddInterpreterModel, privat } override fun savePathToExecutableToProperties() { - PropertiesComponent.getInstance().uvPath = Path.of(executable.get()) + setUvExecutable(Path.of(executable.get())) } override suspend fun setupEnvSdk(project: Project?, module: Module?, baseSdks: List, projectPath: String, homePath: String?, installPackages: Boolean): Result { diff --git a/python/src/com/jetbrains/python/sdk/add/v2/models.kt b/python/src/com/jetbrains/python/sdk/add/v2/models.kt index 6a341a2e49ec..47b5f1669341 100644 --- a/python/src/com/jetbrains/python/sdk/add/v2/models.kt +++ b/python/src/com/jetbrains/python/sdk/add/v2/models.kt @@ -23,7 +23,7 @@ import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity import com.jetbrains.python.sdk.pipenv.getPipEnvExecutable import com.jetbrains.python.sdk.poetry.getPoetryExecutable -import com.jetbrains.python.sdk.uv.getUvExecutable +import com.jetbrains.python.sdk.uv.impl.getUvExecutable import com.jetbrains.python.util.ErrorSink import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/python/src/com/jetbrains/python/sdk/uv/UvExt.kt b/python/src/com/jetbrains/python/sdk/uv/UvExt.kt index 3faf20b1600e..9085cbb4950e 100644 --- a/python/src/com/jetbrains/python/sdk/uv/UvExt.kt +++ b/python/src/com/jetbrains/python/sdk/uv/UvExt.kt @@ -1,7 +1,6 @@ // Copyright 2000-2024 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 -import com.intellij.ide.util.PropertiesComponent import com.intellij.openapi.module.Module import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.util.NlsSafe @@ -15,15 +14,11 @@ import com.jetbrains.python.sdk.findAmongRoots import com.jetbrains.python.sdk.setAssociationToModule import com.jetbrains.python.sdk.uv.impl.createUvCli import com.jetbrains.python.sdk.uv.impl.createUvLowLevel -import com.jetbrains.python.sdk.uv.impl.detectUvExecutable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.nio.file.Path -import kotlin.io.path.exists import kotlin.io.path.pathString -internal const val UV_PATH_SETTING: String = "PyCharm.UV.Path" - internal val Sdk.isUv: Boolean get() = sdkAdditionalData is UvSdkAdditionalData @@ -50,18 +45,6 @@ val UV_LOCK: String = "uv.lock" // FIXME: move pyprojecttoml code out to common package val PY_PROJECT_TOML: String = "pyproject.toml" -var PropertiesComponent.uvPath: Path? - get() { - return getValue(UV_PATH_SETTING)?.let { Path.of(it) } - } - set(value) { - setValue(UV_PATH_SETTING, value.toString()) - } - -fun getUvExecutable(): Path? { - return PropertiesComponent.getInstance().uvPath?.takeIf { it.exists() } ?: detectUvExecutable() -} - suspend fun setupUvSdkUnderProgress( module: Module, projectPath: Path, diff --git a/python/src/com/jetbrains/python/sdk/uv/impl/UvCli.kt b/python/src/com/jetbrains/python/sdk/uv/impl/UvCli.kt index e0b626499916..71676b168246 100644 --- a/python/src/com/jetbrains/python/sdk/uv/impl/UvCli.kt +++ b/python/src/com/jetbrains/python/sdk/uv/impl/UvCli.kt @@ -2,7 +2,9 @@ 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.jetbrains.python.PyBundle import com.jetbrains.python.pathValidation.PlatformAndRoot @@ -12,20 +14,21 @@ import com.jetbrains.python.sdk.runExecutable import com.jetbrains.python.sdk.uv.UvCli import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers - import java.nio.file.Path - import kotlin.io.path.exists import kotlin.io.path.pathString -internal fun detectUvExecutable(): Path? { - val name = "uv" - return PathEnvironmentVariableUtil.findInPath(name)?.toPath() ?: SystemProperties.getUserHome().let { homePath -> - Path.of(homePath, ".cargo", "bin", name).takeIf { it.exists() } - } -} +private const val UV_PATH_SETTING: String = "PyCharm.Uv.Path" -internal fun validateUvExecutable(uvPath: Path?): ValidationInfo? { +private var PropertiesComponent.uvPath: Path? + get() { + return getValue(UV_PATH_SETTING)?.let { Path.of(it) } + } + set(value) { + setValue(UV_PATH_SETTING, value.toString()) + } + +private fun validateUvExecutable(uvPath: Path?): ValidationInfo? { return validateExecutableFile(ValidationRequest( path = uvPath?.pathString, fieldIsEmpty = PyBundle.message("python.sdk.uv.executable.not.found"), @@ -34,15 +37,15 @@ internal fun validateUvExecutable(uvPath: Path?): ValidationInfo? { )) } -internal suspend fun runUv(uv: Path, workingDir: Path, vararg args: String): Result { +private suspend fun runUv(uv: Path, workingDir: Path, vararg args: String): Result { return runExecutable(uv, workingDir, *args) } -internal class UvCliImpl(val dispatcher: CoroutineDispatcher, uvPath: Path?): UvCli { +private class UvCliImpl(val dispatcher: CoroutineDispatcher, uvPath: Path?) : UvCli { val uv: Path init { - val path = uvPath ?: detectUvExecutable() + val path = uvPath ?: getUvExecutable() val error = validateUvExecutable(path) if (error != null) { throw RuntimeException(error.message) @@ -52,12 +55,31 @@ internal class UvCliImpl(val dispatcher: CoroutineDispatcher, uvPath: Path?): Uv } override suspend fun runUv(workingDir: Path, vararg args: String): Result { - with(Dispatchers.IO) { + with(dispatcher) { return runUv(uv, workingDir, *args) } } } +fun detectUvExecutable(): Path? { + val name = when { + SystemInfo.isWindows -> "uv.exe" + else -> "uv" + } + + return PathEnvironmentVariableUtil.findInPath(name)?.toPath() ?: SystemProperties.getUserHome().let { homePath -> + Path.of(homePath, ".local", "bin", name).takeIf { it.exists() } ?: Path.of(homePath, ".cargo", "bin", name).takeIf { it.exists() } + } +} + +fun getUvExecutable(): Path? { + return PropertiesComponent.getInstance().uvPath?.takeIf { it.exists() } ?: detectUvExecutable() +} + +fun setUvExecutable(path: Path) { + PropertiesComponent.getInstance().uvPath = path +} + fun createUvCli(dispatcher: CoroutineDispatcher = Dispatchers.IO, uv: Path? = null): UvCli { return UvCliImpl(dispatcher, uv) } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/uv/ui/AddNewUvPanel.kt b/python/src/com/jetbrains/python/sdk/uv/ui/AddNewUvPanel.kt index 48fba4937f04..cac560e4cf98 100644 --- a/python/src/com/jetbrains/python/sdk/uv/ui/AddNewUvPanel.kt +++ b/python/src/com/jetbrains/python/sdk/uv/ui/AddNewUvPanel.kt @@ -33,6 +33,8 @@ import com.jetbrains.python.sdk.add.addInterpretersAsync import com.jetbrains.python.sdk.basePath import com.jetbrains.python.sdk.uv.* import com.jetbrains.python.sdk.uv.impl.detectUvExecutable +import com.jetbrains.python.sdk.uv.impl.getUvExecutable +import com.jetbrains.python.sdk.uv.impl.setUvExecutable import com.jetbrains.python.statistics.InterpreterTarget import com.jetbrains.python.statistics.InterpreterType import kotlinx.coroutines.Dispatchers @@ -96,8 +98,10 @@ class PyAddNewUvPanel( addBrowseFolderListener(null, FileChooserDescriptorFactory.createSingleFileDescriptor()) val field = textField as? JBTextField ?: return@apply service().cs.launch { - detectUvExecutable()?.let { field.emptyText.text = "Auto-detected: ${it.absolutePathString()}" } - PropertiesComponent.getInstance().uvPath?.let { + detectUvExecutable()?.let { + field.emptyText.text = "Auto-detected: ${it.absolutePathString()}" + } + getUvExecutable()?.let { field.text = it.pathString } } @@ -153,7 +157,9 @@ class PyAddNewUvPanel( } val uvPath = uvPathField.text.nullize()?.let { Path.of(it) } - uvPath?.let { PropertiesComponent.getInstance().uvPath = it } + uvPath?.let { + setUvExecutable(it) + } val sdk = runBlockingCancellable { setupUvSdkUnderProgress(module, Path.of(path), existingSdks, Path.of(python)) }