mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 06:50:54 +07:00
PY-78001 Support adding existing venv's as UV
Add UvExistingEnvironmentSelector (cherry picked from commit be8827506d521c5487cf4fbb3ca15d979f760d44) GitOrigin-RevId: 5a8cdf35bbd89e0473724554d3390d6d9eb19311
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1c33aab97d
commit
dca31999f3
@@ -11,13 +11,10 @@ import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
|
|||||||
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
|
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
|
||||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||||
import com.jetbrains.python.sdk.*
|
import com.jetbrains.python.sdk.*
|
||||||
import com.jetbrains.python.sdk.basePath
|
|
||||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||||
import com.jetbrains.python.sdk.uv.PY_PROJECT_TOML
|
import com.jetbrains.python.sdk.uv.PY_PROJECT_TOML
|
||||||
import com.jetbrains.python.sdk.uv.impl.getUvExecutable
|
import com.jetbrains.python.sdk.uv.impl.getUvExecutable
|
||||||
import com.jetbrains.python.sdk.uv.setupUvSdkUnderProgress
|
import com.jetbrains.python.sdk.uv.setupUvSdkUnderProgress
|
||||||
import java.io.FileNotFoundException
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
class PyUvSdkConfiguration : PyProjectSdkConfigurationExtension {
|
class PyUvSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||||
companion object {
|
companion object {
|
||||||
@@ -54,12 +51,7 @@ class PyUvSdkConfiguration : PyProjectSdkConfigurationExtension {
|
|||||||
override fun supportsHeadlessModel(): Boolean = true
|
override fun supportsHeadlessModel(): Boolean = true
|
||||||
|
|
||||||
private suspend fun createUv(module: Module): Result<Sdk> {
|
private suspend fun createUv(module: Module): Result<Sdk> {
|
||||||
val basePath = module.basePath?.let { Path.of(it) }
|
val sdk = setupUvSdkUnderProgress(ModuleOrProject.ModuleAndProject(module), ProjectJdkTable.getInstance().allJdks.toList(), null)
|
||||||
if (basePath == null) {
|
|
||||||
return Result.failure(FileNotFoundException("Can't find module base path"))
|
|
||||||
}
|
|
||||||
|
|
||||||
val sdk = setupUvSdkUnderProgress(module, basePath, ProjectJdkTable.getInstance().allJdks.toList(), null)
|
|
||||||
sdk.onSuccess {
|
sdk.onSuccess {
|
||||||
SdkConfigurationUtil.addSdk(it)
|
SdkConfigurationUtil.addSdk(it)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
// 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
|
package com.jetbrains.python.sdk.add.v2
|
||||||
|
|
||||||
|
import com.intellij.openapi.module.Module
|
||||||
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||||
import com.intellij.openapi.observable.util.notEqualsTo
|
import com.intellij.openapi.observable.util.notEqualsTo
|
||||||
import com.intellij.openapi.ui.validation.DialogValidationRequestor
|
import com.intellij.openapi.ui.validation.DialogValidationRequestor
|
||||||
@@ -11,7 +12,6 @@ import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
|
|||||||
import com.jetbrains.python.sdk.ModuleOrProject
|
import com.jetbrains.python.sdk.ModuleOrProject
|
||||||
import com.jetbrains.python.sdk.PySdkUtil
|
import com.jetbrains.python.sdk.PySdkUtil
|
||||||
import com.jetbrains.python.sdk.PythonSdkUtil
|
import com.jetbrains.python.sdk.PythonSdkUtil
|
||||||
import com.jetbrains.python.sdk.poetry.pyProjectToml
|
|
||||||
import com.jetbrains.python.statistics.InterpreterCreationMode
|
import com.jetbrains.python.statistics.InterpreterCreationMode
|
||||||
import com.jetbrains.python.statistics.InterpreterType
|
import com.jetbrains.python.statistics.InterpreterType
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -31,7 +31,7 @@ abstract class CustomExistingEnvironmentSelector(private val name: String, model
|
|||||||
model.scope.launch {
|
model.scope.launch {
|
||||||
val modulePath = when (moduleOrProject) {
|
val modulePath = when (moduleOrProject) {
|
||||||
is ModuleOrProject.ProjectOnly -> moduleOrProject.project.basePath?.let { Path.of(it) }
|
is ModuleOrProject.ProjectOnly -> moduleOrProject.project.basePath?.let { Path.of(it) }
|
||||||
is ModuleOrProject.ModuleAndProject -> pyProjectToml(moduleOrProject.module)?.let { Path.of(it.parent.path) }
|
is ModuleOrProject.ModuleAndProject -> findModulePath(moduleOrProject.module)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modulePath != null) {
|
if (modulePath != null) {
|
||||||
@@ -49,6 +49,12 @@ abstract class CustomExistingEnvironmentSelector(private val name: String, model
|
|||||||
message("sdk.create.custom.venv.missing.text", name),
|
message("sdk.create.custom.venv.missing.text", name),
|
||||||
).component
|
).component
|
||||||
|
|
||||||
|
addInterpretersComboBox(panel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected open fun addInterpretersComboBox(panel: Panel) {
|
||||||
|
with(panel) {
|
||||||
row(message("sdk.create.custom.existing.env.title", name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() })) {
|
row(message("sdk.create.custom.existing.env.title", name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() })) {
|
||||||
comboBox = pythonInterpreterComboBox(selectedEnv, model, { path -> addEnvByPath(path) }, model.interpreterLoading)
|
comboBox = pythonInterpreterComboBox(selectedEnv, model, { path -> addEnvByPath(path) }, model.interpreterLoading)
|
||||||
.align(Align.FILL)
|
.align(Align.FILL)
|
||||||
@@ -82,4 +88,5 @@ abstract class CustomExistingEnvironmentSelector(private val name: String, model
|
|||||||
internal abstract val executable: ObservableMutableProperty<String>
|
internal abstract val executable: ObservableMutableProperty<String>
|
||||||
internal abstract val interpreterType: InterpreterType
|
internal abstract val interpreterType: InterpreterType
|
||||||
internal abstract suspend fun detectEnvironments(modulePath: Path)
|
internal abstract suspend fun detectEnvironments(modulePath: Path)
|
||||||
|
internal abstract suspend fun findModulePath(module: Module): Path?
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ class EnvironmentCreatorUv(model: PythonMutableTargetAddInterpreterModel, privat
|
|||||||
}
|
}
|
||||||
|
|
||||||
val python = homePath?.let { Path.of(it) }
|
val python = homePath?.let { Path.of(it) }
|
||||||
return setupUvSdkUnderProgress(module, Path.of(projectPath), baseSdks, python)
|
return setupUvSdkUnderProgress(ModuleOrProject.ModuleAndProject(module), baseSdks, python)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun detectExecutable() {
|
override suspend fun detectExecutable() {
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
// 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
|
package com.jetbrains.python.sdk.add.v2
|
||||||
|
|
||||||
|
import com.intellij.openapi.module.Module
|
||||||
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||||
import com.intellij.openapi.projectRoots.Sdk
|
import com.intellij.openapi.projectRoots.Sdk
|
||||||
|
import com.intellij.openapi.vfs.toNioPathOrNull
|
||||||
import com.jetbrains.python.sdk.ModuleOrProject
|
import com.jetbrains.python.sdk.ModuleOrProject
|
||||||
import com.jetbrains.python.sdk.poetry.detectPoetryEnvs
|
import com.jetbrains.python.sdk.poetry.detectPoetryEnvs
|
||||||
import com.jetbrains.python.sdk.poetry.isPoetry
|
import com.jetbrains.python.sdk.poetry.isPoetry
|
||||||
|
import com.jetbrains.python.sdk.poetry.pyProjectToml
|
||||||
import com.jetbrains.python.sdk.poetry.setupPoetrySdkUnderProgress
|
import com.jetbrains.python.sdk.poetry.setupPoetrySdkUnderProgress
|
||||||
import com.jetbrains.python.statistics.InterpreterType
|
import com.jetbrains.python.statistics.InterpreterType
|
||||||
import com.jetbrains.python.statistics.version
|
import com.jetbrains.python.statistics.version
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import kotlin.io.path.pathString
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
class PoetryExistingEnvironmentSelector(model: PythonMutableTargetAddInterpreterModel, moduleOrProject: ModuleOrProject) : CustomExistingEnvironmentSelector("poetry", model, moduleOrProject) {
|
internal class PoetryExistingEnvironmentSelector(model: PythonMutableTargetAddInterpreterModel, moduleOrProject: ModuleOrProject) : CustomExistingEnvironmentSelector("poetry", model, moduleOrProject) {
|
||||||
override val executable: ObservableMutableProperty<String> = model.state.poetryExecutable
|
override val executable: ObservableMutableProperty<String> = model.state.poetryExecutable
|
||||||
override val interpreterType: InterpreterType = InterpreterType.POETRY
|
override val interpreterType: InterpreterType = InterpreterType.POETRY
|
||||||
|
|
||||||
@@ -37,4 +40,6 @@ class PoetryExistingEnvironmentSelector(model: PythonMutableTargetAddInterpreter
|
|||||||
|
|
||||||
existingEnvironments.value = existingEnvs
|
existingEnvironments.value = existingEnvs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun findModulePath(module: Module): Path? = pyProjectToml(module)?.toNioPathOrNull()?.parent
|
||||||
}
|
}
|
||||||
@@ -38,6 +38,7 @@ class PythonAddCustomInterpreter(val model: PythonMutableTargetAddInterpreterMod
|
|||||||
put(PYTHON, PythonExistingEnvironmentSelector(model))
|
put(PYTHON, PythonExistingEnvironmentSelector(model))
|
||||||
put(CONDA, CondaExistingEnvironmentSelector(model, errorSink))
|
put(CONDA, CondaExistingEnvironmentSelector(model, errorSink))
|
||||||
if (moduleOrProject != null) put(POETRY, PoetryExistingEnvironmentSelector(model, moduleOrProject))
|
if (moduleOrProject != null) put(POETRY, PoetryExistingEnvironmentSelector(model, moduleOrProject))
|
||||||
|
if (moduleOrProject != null) put(UV, UvExistingEnvironmentSelector(model, moduleOrProject))
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentSdkManager: PythonAddEnvironment
|
val currentSdkManager: PythonAddEnvironment
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
// 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.add.v2
|
||||||
|
|
||||||
|
import com.intellij.openapi.module.Module
|
||||||
|
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||||
|
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||||
|
import com.intellij.openapi.projectRoots.Sdk
|
||||||
|
import com.intellij.openapi.vfs.toNioPathOrNull
|
||||||
|
import com.jetbrains.python.sdk.ModuleOrProject
|
||||||
|
import com.jetbrains.python.sdk.associatedModulePath
|
||||||
|
import com.jetbrains.python.sdk.isAssociatedWithModule
|
||||||
|
import com.jetbrains.python.sdk.uv.isUv
|
||||||
|
import com.jetbrains.python.sdk.uv.pyProjectToml
|
||||||
|
import com.jetbrains.python.sdk.uv.setupUvSdkUnderProgress
|
||||||
|
import com.jetbrains.python.statistics.InterpreterType
|
||||||
|
import com.jetbrains.python.statistics.version
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
|
internal class UvExistingEnvironmentSelector(model: PythonMutableTargetAddInterpreterModel, moduleOrProject: ModuleOrProject)
|
||||||
|
: CustomExistingEnvironmentSelector("uv", model, moduleOrProject) {
|
||||||
|
override val executable: ObservableMutableProperty<String> = model.state.uvExecutable
|
||||||
|
override val interpreterType: InterpreterType = InterpreterType.UV
|
||||||
|
|
||||||
|
override suspend fun getOrCreateSdk(moduleOrProject: ModuleOrProject): Result<Sdk> {
|
||||||
|
val selectedInterpreterPath = selectedEnv.get()?.homePath ?: return Result.failure(FileNotFoundException("No selected interpreter"))
|
||||||
|
val existingSdk = ProjectJdkTable.getInstance().allJdks.find { it.homePath == selectedInterpreterPath }
|
||||||
|
val associatedModule = extractModule(moduleOrProject)
|
||||||
|
|
||||||
|
// uv sdk in current module
|
||||||
|
if (existingSdk != null && existingSdk.isUv && existingSdk.isAssociatedWithModule(associatedModule)) {
|
||||||
|
return Result.success(existingSdk)
|
||||||
|
}
|
||||||
|
|
||||||
|
val existingWorkingDir = existingSdk?.associatedModulePath?.let { Path.of(it) }
|
||||||
|
val usePip = existingWorkingDir!= null && !existingSdk.isUv
|
||||||
|
|
||||||
|
return setupUvSdkUnderProgress(moduleOrProject, ProjectJdkTable.getInstance().allJdks.toList(), Path.of(selectedInterpreterPath), existingWorkingDir, usePip)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun detectEnvironments(modulePath: Path) {
|
||||||
|
val existingEnvs = ProjectJdkTable.getInstance().allJdks.filter {
|
||||||
|
it.isUv && (it.associatedModulePath == modulePath.pathString || it.associatedModulePath == null)
|
||||||
|
}.mapNotNull { env ->
|
||||||
|
env.homePath?.let { path -> DetectedSelectableInterpreter(path, env.version) }
|
||||||
|
}
|
||||||
|
|
||||||
|
existingEnvironments.value = existingEnvs
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun findModulePath(module: Module): Path? = pyProjectToml(module)?.toNioPathOrNull()?.parent
|
||||||
|
|
||||||
|
private fun extractModule(moduleOrProject: ModuleOrProject): Module? =
|
||||||
|
when (moduleOrProject) {
|
||||||
|
is ModuleOrProject.ModuleAndProject -> moduleOrProject.module
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -264,12 +264,10 @@ internal fun Row.pythonInterpreterComboBox(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PythonInterpreterComboBox(
|
internal class PythonInterpreterComboBox(
|
||||||
val backingProperty: ObservableMutableProperty<PythonSelectableInterpreter?>,
|
private val backingProperty: ObservableMutableProperty<PythonSelectableInterpreter?>,
|
||||||
val controller: PythonAddInterpreterModel,
|
val controller: PythonAddInterpreterModel,
|
||||||
val onPathSelected: (String) -> Unit,
|
val onPathSelected: (String) -> Unit,
|
||||||
) : ComboBox<PythonSelectableInterpreter?>() {
|
) : ComboBox<PythonSelectableInterpreter?>() {
|
||||||
|
|||||||
@@ -16,6 +16,6 @@ interface UvLowLevel {
|
|||||||
suspend fun listPackages(): Result<List<PythonPackage>>
|
suspend fun listPackages(): Result<List<PythonPackage>>
|
||||||
suspend fun listOutdatedPackages(): Result<List<PythonOutdatedPackage>>
|
suspend fun listOutdatedPackages(): Result<List<PythonOutdatedPackage>>
|
||||||
|
|
||||||
suspend fun installPackage(name: PythonPackageSpecification, options: List<String>): Result<Unit>
|
suspend fun installPackage(name: PythonPackageSpecification, options: List<String>, usePip: Boolean = false): Result<Unit>
|
||||||
suspend fun uninstallPackage(name: PythonPackage): Result<Unit>
|
suspend fun uninstallPackage(name: PythonPackage, usePip: Boolean = false): Result<Unit>
|
||||||
}
|
}
|
||||||
@@ -5,24 +5,24 @@ import com.intellij.openapi.module.Module
|
|||||||
import com.intellij.openapi.projectRoots.Sdk
|
import com.intellij.openapi.projectRoots.Sdk
|
||||||
import com.intellij.openapi.util.NlsSafe
|
import com.intellij.openapi.util.NlsSafe
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import com.intellij.openapi.vfs.toNioPathOrNull
|
||||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||||
import com.intellij.util.PathUtil
|
import com.intellij.util.PathUtil
|
||||||
import com.jetbrains.python.PyBundle
|
import com.jetbrains.python.PyBundle
|
||||||
import com.jetbrains.python.icons.PythonIcons
|
import com.jetbrains.python.icons.PythonIcons
|
||||||
import com.jetbrains.python.sdk.createSdk
|
import com.jetbrains.python.sdk.*
|
||||||
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.createUvCli
|
||||||
import com.jetbrains.python.sdk.uv.impl.createUvLowLevel
|
import com.jetbrains.python.sdk.uv.impl.createUvLowLevel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import javax.swing.Icon
|
||||||
import kotlin.io.path.pathString
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
internal val Sdk.isUv: Boolean
|
internal val Sdk.isUv: Boolean
|
||||||
get() = sdkAdditionalData is UvSdkAdditionalData
|
get() = sdkAdditionalData is UvSdkAdditionalData
|
||||||
|
|
||||||
internal suspend fun uvLock(module: com.intellij.openapi.module.Module): VirtualFile? {
|
internal suspend fun uvLock(module: Module): VirtualFile? {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
findAmongRoots(module, UV_LOCK)
|
findAmongRoots(module, UV_LOCK)
|
||||||
}
|
}
|
||||||
@@ -38,32 +38,54 @@ internal fun suggestedSdkName(basePath: Path): @NlsSafe String {
|
|||||||
return "uv (${PathUtil.getFileName(basePath.pathString)})"
|
return "uv (${PathUtil.getFileName(basePath.pathString)})"
|
||||||
}
|
}
|
||||||
|
|
||||||
val UV_ICON = PythonIcons.UV
|
val UV_ICON: Icon = PythonIcons.UV
|
||||||
val UV_LOCK: String = "uv.lock"
|
const val UV_LOCK: String = "uv.lock"
|
||||||
|
|
||||||
// FIXME: move pyprojecttoml code out to common package
|
// FIXME: move pyprojecttoml code out to common package
|
||||||
val PY_PROJECT_TOML: String = "pyproject.toml"
|
const val PY_PROJECT_TOML: String = "pyproject.toml"
|
||||||
|
|
||||||
suspend fun setupUvSdkUnderProgress(
|
suspend fun setupUvSdkUnderProgress(
|
||||||
module: Module,
|
moduleOrProject: ModuleOrProject,
|
||||||
projectPath: Path,
|
|
||||||
existingSdks: List<Sdk>,
|
existingSdks: List<Sdk>,
|
||||||
python: Path?
|
python: Path?,
|
||||||
|
existingSdkWorkingDir: Path? = null,
|
||||||
|
usePip: Boolean = false,
|
||||||
): Result<Sdk> {
|
): Result<Sdk> {
|
||||||
val uv = createUvLowLevel(projectPath, createUvCli())
|
|
||||||
|
|
||||||
val init = pyProjectToml(module) == null
|
val (pyProjectToml, moduleWorkingDirectory) = resolveWorkingDirectory(moduleOrProject)
|
||||||
|
val init = pyProjectToml == null
|
||||||
|
val uvWorkingDir = existingSdkWorkingDir ?: moduleWorkingDirectory
|
||||||
|
val uv = createUvLowLevel(uvWorkingDir, createUvCli())
|
||||||
|
|
||||||
val envExecutable =
|
val envExecutable =
|
||||||
withBackgroundProgress(module.project, PyBundle.message("python.sdk.dialog.title.setting.up.uv.environment"), true) {
|
if (existingSdkWorkingDir == null) {
|
||||||
uv.initializeEnvironment(init, python)
|
withBackgroundProgress(moduleOrProject.project, PyBundle.message("python.sdk.dialog.title.setting.up.uv.environment"), true) {
|
||||||
}.getOrElse {
|
uv.initializeEnvironment(init, python)
|
||||||
return Result.failure(it)
|
}.getOrElse {
|
||||||
|
return Result.failure(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
python
|
||||||
|
} ?: throw IllegalArgumentException("Python executable is required to setup uv environment")
|
||||||
|
|
||||||
val sdk = createSdk(envExecutable, existingSdks, projectPath.pathString, suggestedSdkName(projectPath), UvSdkAdditionalData())
|
val sdk = createSdk(envExecutable, existingSdks, moduleWorkingDirectory.pathString, suggestedSdkName(moduleWorkingDirectory), UvSdkAdditionalData(existingSdkWorkingDir, usePip))
|
||||||
sdk.onSuccess {
|
sdk.onSuccess {
|
||||||
it.setAssociationToModule(module)
|
it.setAssociationToPath(moduleWorkingDirectory.pathString)
|
||||||
}
|
}
|
||||||
|
|
||||||
return sdk
|
return sdk
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun resolveWorkingDirectory(moduleOrProject: ModuleOrProject): Pair<VirtualFile?, Path> {
|
||||||
|
var pyProjectToml: VirtualFile? = null
|
||||||
|
val workingDirectory = when (moduleOrProject) {
|
||||||
|
is ModuleOrProject.ModuleAndProject -> {
|
||||||
|
pyProjectToml = pyProjectToml(moduleOrProject.module)
|
||||||
|
pyProjectToml?.toNioPathOrNull()?.parent ?: moduleOrProject.module.basePath?.let { Path.of(it) }
|
||||||
|
}
|
||||||
|
else -> moduleOrProject.project.basePath?.let { Path.of(it) }
|
||||||
|
} ?: throw IllegalArgumentException("Path to module or working directory is required")
|
||||||
|
|
||||||
|
return Pair(pyProjectToml, workingDirectory)
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ internal class UvPackageManager(project: Project, sdk: Sdk, val uv: UvLowLevel)
|
|||||||
var outdatedPackages: Map<String, PythonOutdatedPackage> = emptyMap()
|
var outdatedPackages: Map<String, PythonOutdatedPackage> = emptyMap()
|
||||||
|
|
||||||
override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List<String>): Result<String> {
|
override suspend fun installPackageCommand(specification: PythonPackageSpecification, options: List<String>): Result<String> {
|
||||||
uv.installPackage(specification, options).getOrElse {
|
uv.installPackage(specification, options, (sdk.sdkAdditionalData as? UvSdkAdditionalData)?.usePip ?: false).getOrElse {
|
||||||
return Result.failure(it)
|
return Result.failure(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ internal class UvPackageManager(project: Project, sdk: Sdk, val uv: UvLowLevel)
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result<String> {
|
override suspend fun updatePackageCommand(specification: PythonPackageSpecification): Result<String> {
|
||||||
uv.installPackage(specification, emptyList()).getOrElse {
|
uv.installPackage(specification, emptyList(), (sdk.sdkAdditionalData as? UvSdkAdditionalData)?.usePip ?: false).getOrElse {
|
||||||
return Result.failure(it)
|
return Result.failure(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ internal class UvPackageManager(project: Project, sdk: Sdk, val uv: UvLowLevel)
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result<String> {
|
override suspend fun uninstallPackageCommand(pkg: PythonPackage): Result<String> {
|
||||||
uv.uninstallPackage(pkg).getOrElse {
|
uv.uninstallPackage(pkg, (sdk.sdkAdditionalData as? UvSdkAdditionalData)?.usePip ?: false).getOrElse {
|
||||||
return Result.failure(it)
|
return Result.failure(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +64,8 @@ class UvPackageManagerProvider : PythonPackageManagerProvider {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val uv = createUvLowLevel(Path.of(project.basePath!!), createUvCli())
|
val uvWorkingDirectory = (sdk.sdkAdditionalData as? UvSdkAdditionalData)?.uvWorkingDirectory ?: Path.of(project.basePath!!)
|
||||||
|
val uv = createUvLowLevel(uvWorkingDirectory, createUvCli())
|
||||||
return UvPackageManager(project, sdk, uv)
|
return UvPackageManager(project, sdk, uv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,15 +6,30 @@ import com.jetbrains.python.sdk.flavors.PyFlavorData
|
|||||||
import com.jetbrains.python.sdk.flavors.PythonFlavorProvider
|
import com.jetbrains.python.sdk.flavors.PythonFlavorProvider
|
||||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
|
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
|
||||||
import org.jdom.Element
|
import org.jdom.Element
|
||||||
|
import java.nio.file.Path
|
||||||
|
import javax.swing.Icon
|
||||||
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
|
|
||||||
class UvSdkAdditionalData : PythonSdkAdditionalData {
|
class UvSdkAdditionalData : PythonSdkAdditionalData {
|
||||||
constructor() : super(UvSdkFlavor)
|
val uvWorkingDirectory: Path?
|
||||||
constructor(data: PythonSdkAdditionalData) : super(data)
|
val usePip: Boolean
|
||||||
|
|
||||||
|
constructor(uvWorkingDirectory: Path? = null, usePip: Boolean = false) : super(UvSdkFlavor) {
|
||||||
|
this.uvWorkingDirectory = uvWorkingDirectory
|
||||||
|
this.usePip = usePip
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(data: PythonSdkAdditionalData, uvWorkingDirectory: Path? = null, usePip: Boolean = false) : super(data) {
|
||||||
|
this.uvWorkingDirectory = uvWorkingDirectory
|
||||||
|
this.usePip = usePip
|
||||||
|
}
|
||||||
|
|
||||||
override fun save(element: Element) {
|
override fun save(element: Element) {
|
||||||
super.save(element)
|
super.save(element)
|
||||||
element.setAttribute(IS_UV, "true")
|
element.setAttribute(IS_UV, "true")
|
||||||
|
element.setAttribute(UV_WORKING_DIR, uvWorkingDirectory?.pathString ?: "")
|
||||||
|
element.setAttribute(USE_PIP, usePip.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@@ -24,7 +39,9 @@ class UvSdkAdditionalData : PythonSdkAdditionalData {
|
|||||||
fun load(element: Element): UvSdkAdditionalData? {
|
fun load(element: Element): UvSdkAdditionalData? {
|
||||||
return when {
|
return when {
|
||||||
element.getAttributeValue(IS_UV) == "true" -> {
|
element.getAttributeValue(IS_UV) == "true" -> {
|
||||||
UvSdkAdditionalData().apply {
|
val uvWorkingDirectory = if (element.getAttributeValue(UV_WORKING_DIR).isNullOrEmpty()) null else Path.of(element.getAttributeValue(UV_WORKING_DIR))
|
||||||
|
val usePip = element.getAttributeValue(USE_PIP)?.toBoolean() ?: false
|
||||||
|
UvSdkAdditionalData(uvWorkingDirectory, usePip).apply {
|
||||||
load(element)
|
load(element)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,8 +56,8 @@ class UvSdkAdditionalData : PythonSdkAdditionalData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object UvSdkFlavor : PythonSdkFlavor<PyFlavorData.Empty>() {
|
object UvSdkFlavor : CPythonSdkFlavor<PyFlavorData.Empty>() {
|
||||||
override fun getIcon() = UV_ICON
|
override fun getIcon(): Icon = UV_ICON
|
||||||
override fun getFlavorDataClass(): Class<PyFlavorData.Empty> = PyFlavorData.Empty::class.java
|
override fun getFlavorDataClass(): Class<PyFlavorData.Empty> = PyFlavorData.Empty::class.java
|
||||||
|
|
||||||
override fun isValidSdkPath(pathStr: String): Boolean {
|
override fun isValidSdkPath(pathStr: String): Boolean {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import java.nio.file.Path
|
|||||||
import kotlin.io.path.exists
|
import kotlin.io.path.exists
|
||||||
import kotlin.io.path.pathString
|
import kotlin.io.path.pathString
|
||||||
|
|
||||||
internal class UvLowLevelImpl(val cwd: Path, val uvCli: UvCli) : UvLowLevel {
|
internal class UvLowLevelImpl(val cwd: Path, private val uvCli: UvCli) : UvLowLevel {
|
||||||
override suspend fun initializeEnvironment(init: Boolean, python: Path?): Result<Path> {
|
override suspend fun initializeEnvironment(init: Boolean, python: Path?): Result<Path> {
|
||||||
val addPythonArg: (MutableList<String>) -> Unit = { args ->
|
val addPythonArg: (MutableList<String>) -> Unit = { args ->
|
||||||
python?.let {
|
python?.let {
|
||||||
@@ -93,25 +93,29 @@ internal class UvLowLevelImpl(val cwd: Path, val uvCli: UvCli) : UvLowLevel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun installPackage(spec: PythonPackageSpecification, options: List<String>): Result<Unit> {
|
override suspend fun installPackage(name: PythonPackageSpecification, options: List<String>, usePip: Boolean): Result<Unit> {
|
||||||
val version = if (spec.versionSpecs.isNullOrBlank()) spec.name else "${spec.name}${spec.versionSpecs}"
|
val version = if (name.versionSpecs.isNullOrBlank()) name.name else "${name.name}${name.versionSpecs}"
|
||||||
uvCli.runUv(cwd, "add", version, *options.toTypedArray()).getOrElse {
|
val command = if (usePip) listOf("pip", "install") else listOf("add")
|
||||||
|
uvCli.runUv(cwd, *command.toTypedArray(), version, *options.toTypedArray()).getOrElse {
|
||||||
return Result.failure(it)
|
return Result.failure(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun uninstallPackage(name: PythonPackage): Result<Unit> {
|
override suspend fun uninstallPackage(name: PythonPackage, usePip: Boolean): Result<Unit> {
|
||||||
// TODO: check if package is in dependencies
|
// TODO: check if package is in dependencies
|
||||||
val result = uvCli.runUv(cwd, "remove", name.name)
|
val command = if (usePip) listOf("pip", "uninstall") else listOf("remove")
|
||||||
if (result.isFailure) {
|
val result = uvCli.runUv(cwd, *command.toTypedArray(), name.name)
|
||||||
|
if (result.isFailure && !usePip) {
|
||||||
// try just to uninstall
|
// try just to uninstall
|
||||||
uvCli.runUv(cwd, "pip", "uninstall", name.name).onFailure {
|
uvCli.runUv(cwd, "pip", "uninstall", name.name).onFailure {
|
||||||
return Result.failure(it)
|
return Result.failure(it)
|
||||||
}
|
}
|
||||||
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.onFailure { return Result.failure(it) }
|
||||||
return Result.success(Unit)
|
return Result.success(Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
package com.jetbrains.python.sdk.uv.ui
|
package com.jetbrains.python.sdk.uv.ui
|
||||||
|
|
||||||
import com.intellij.application.options.ModuleListCellRenderer
|
import com.intellij.application.options.ModuleListCellRenderer
|
||||||
import com.intellij.ide.util.PropertiesComponent
|
|
||||||
import com.intellij.openapi.components.service
|
import com.intellij.openapi.components.service
|
||||||
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory
|
||||||
import com.intellij.openapi.module.Module
|
import com.intellij.openapi.module.Module
|
||||||
@@ -25,6 +24,7 @@ import com.jetbrains.python.PyBundle
|
|||||||
import com.jetbrains.python.PySdkBundle
|
import com.jetbrains.python.PySdkBundle
|
||||||
import com.jetbrains.python.PythonModuleTypeBase
|
import com.jetbrains.python.PythonModuleTypeBase
|
||||||
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
|
import com.jetbrains.python.newProject.collector.InterpreterStatisticsInfo
|
||||||
|
import com.jetbrains.python.sdk.ModuleOrProject
|
||||||
import com.jetbrains.python.sdk.PySdkSettings
|
import com.jetbrains.python.sdk.PySdkSettings
|
||||||
import com.jetbrains.python.sdk.PythonSdkCoroutineService
|
import com.jetbrains.python.sdk.PythonSdkCoroutineService
|
||||||
import com.jetbrains.python.sdk.add.PyAddNewEnvPanel
|
import com.jetbrains.python.sdk.add.PyAddNewEnvPanel
|
||||||
@@ -161,7 +161,7 @@ class PyAddNewUvPanel(
|
|||||||
setUvExecutable(it)
|
setUvExecutable(it)
|
||||||
}
|
}
|
||||||
val sdk = runBlockingCancellable {
|
val sdk = runBlockingCancellable {
|
||||||
setupUvSdkUnderProgress(module, Path.of(path), existingSdks, Path.of(python))
|
setupUvSdkUnderProgress(ModuleOrProject.ModuleAndProject(module), existingSdks, Path.of(python))
|
||||||
}
|
}
|
||||||
|
|
||||||
sdk.onSuccess {
|
sdk.onSuccess {
|
||||||
|
|||||||
Reference in New Issue
Block a user