mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 22:51:17 +07:00
[pycharm] PY-79132 Replace base python with python version selection for uv
GitOrigin-RevId: b1b7cb2147244d2054ce9b3a8e2dfbd2bced567b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
4d0b1dfecc
commit
04d67a09a7
@@ -51,6 +51,7 @@ jvm_library(
|
||||
"@lib//:jetbrains-annotations",
|
||||
"//python/python-pyproject:pyproject",
|
||||
"//python/python-hatch:hatch",
|
||||
"@lib//:io-github-z4kn4fein-semver-jvm",
|
||||
],
|
||||
runtime_deps = [":impl_resources"]
|
||||
)
|
||||
@@ -112,6 +113,7 @@ jvm_library(
|
||||
"//platform/testFramework/junit5",
|
||||
"//platform/testFramework/junit5:junit5_test_lib",
|
||||
"@lib//:junit5",
|
||||
"@lib//:io-github-z4kn4fein-semver-jvm",
|
||||
],
|
||||
runtime_deps = [":impl_resources"]
|
||||
)
|
||||
|
||||
@@ -49,5 +49,6 @@
|
||||
<orderEntry type="module" module-name="intellij.python.hatch" />
|
||||
<orderEntry type="module" module-name="intellij.platform.testFramework.junit5" scope="TEST" />
|
||||
<orderEntry type="library" scope="TEST" name="JUnit5" level="project" />
|
||||
<orderEntry type="library" name="io.github.z4kn4fein.semver.jvm" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -10,6 +10,8 @@ import com.intellij.execution.target.TargetEnvironmentWizard
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.project.Project
|
||||
@@ -61,6 +63,9 @@ private class AddLocalInterpreterAction(
|
||||
private val onSdkCreated: Consumer<Sdk>,
|
||||
) : AnAction(PyBundle.messagePointer("python.sdk.action.add.local.interpreter.text"), AllIcons.Nodes.HomeFolder), DumbAware {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
runInEdt {
|
||||
FileDocumentManager.getInstance().saveAllDocuments()
|
||||
}
|
||||
addLocalInterpreter(moduleOrProject, onSdkCreated)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,26 @@
|
||||
package com.jetbrains.python.sdk.add.v2.uv
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.observable.properties.AtomicBooleanProperty
|
||||
import com.intellij.openapi.observable.properties.ObservableMutableProperty
|
||||
import com.intellij.openapi.observable.util.not
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.ui.validation.DialogValidationRequestor
|
||||
import com.intellij.python.pyproject.PyProjectToml
|
||||
import com.intellij.ui.dsl.builder.AlignX
|
||||
import com.intellij.ui.dsl.builder.Panel
|
||||
import com.intellij.ui.dsl.builder.bindItem
|
||||
import com.intellij.ui.dsl.gridLayout.UnscaledGaps
|
||||
import com.intellij.ui.dsl.listCellRenderer.textListCellRenderer
|
||||
import com.intellij.uiDesigner.core.Spacer
|
||||
import com.intellij.util.text.nullize
|
||||
import com.intellij.util.ui.AsyncProcessIcon
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.getOrNull
|
||||
import com.jetbrains.python.newProjectWizard.collector.PythonNewProjectWizardCollector
|
||||
import com.jetbrains.python.sdk.add.v2.CustomNewEnvironmentCreator
|
||||
import com.jetbrains.python.sdk.add.v2.PythonInterpreterSelectionMethod.SELECT_EXISTING
|
||||
@@ -15,17 +29,27 @@ import com.jetbrains.python.sdk.add.v2.PythonMutableTargetAddInterpreterModel
|
||||
import com.jetbrains.python.sdk.add.v2.PythonSupportedEnvironmentManagers.PYTHON
|
||||
import com.jetbrains.python.sdk.add.v2.PythonSupportedEnvironmentManagers.UV
|
||||
import com.jetbrains.python.sdk.add.v2.VenvExistenceValidationState
|
||||
import com.jetbrains.python.sdk.add.v2.executableSelector
|
||||
import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.uv.impl.createUvCli
|
||||
import com.jetbrains.python.sdk.uv.impl.createUvLowLevel
|
||||
import com.jetbrains.python.sdk.uv.impl.setUvExecutable
|
||||
import com.jetbrains.python.sdk.uv.setupNewUvSdkAndEnvUnderProgress
|
||||
import com.jetbrains.python.statistics.InterpreterType
|
||||
import com.jetbrains.python.venvReader.tryResolvePath
|
||||
import io.github.z4kn4fein.semver.Version
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.awt.Dimension
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.inputStream
|
||||
|
||||
|
||||
internal class EnvironmentCreatorUv(
|
||||
@@ -35,20 +59,103 @@ internal class EnvironmentCreatorUv(
|
||||
) : CustomNewEnvironmentCreator("uv", model, errorSink) {
|
||||
override val interpreterType: InterpreterType = InterpreterType.UV
|
||||
override val executable: ObservableMutableProperty<String> = model.state.uvExecutable
|
||||
private val executableFlow = MutableStateFlow(model.state.uvExecutable.get())
|
||||
private lateinit var pythonVersion: ObservableMutableProperty<Version?>
|
||||
private lateinit var versionComboBox: ComboBox<Version?>
|
||||
private val loading = AtomicBooleanProperty(false)
|
||||
|
||||
init {
|
||||
executable.afterChange {
|
||||
executableFlow.value = it
|
||||
}
|
||||
}
|
||||
|
||||
override fun setupUI(panel: Panel, validationRequestor: DialogValidationRequestor) {
|
||||
with(panel) {
|
||||
row(message("sdk.create.python.version")) {
|
||||
pythonVersion = propertyGraph.property(null)
|
||||
versionComboBox = comboBox(listOf<Version?>(null), textListCellRenderer {
|
||||
it?.let { "${it.major}.${it.minor}" } ?: "Default"
|
||||
})
|
||||
.bindItem(pythonVersion)
|
||||
.enabledIf(loading.not())
|
||||
.component
|
||||
|
||||
cell(AsyncProcessIcon("loader"))
|
||||
.align(AlignX.LEFT)
|
||||
.customize(UnscaledGaps(0))
|
||||
.visibleIf(loading)
|
||||
}
|
||||
|
||||
executableSelector(
|
||||
executable = executable,
|
||||
validationRequestor = validationRequestor,
|
||||
labelText = message("sdk.create.custom.venv.executable.path", "uv"),
|
||||
missingExecutableText = message("sdk.create.custom.venv.missing.text", "uv"),
|
||||
installAction = createInstallFix(errorSink)
|
||||
)
|
||||
|
||||
row("") {
|
||||
venvExistenceValidationAlert(validationRequestor) {
|
||||
onVenvSelectExisting()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShown(scope: CoroutineScope) {
|
||||
super.onShown(scope)
|
||||
|
||||
model.projectPathFlows.projectPathWithDefault.onEach { projectPath ->
|
||||
model
|
||||
.projectPathFlows
|
||||
.projectPathWithDefault
|
||||
.combine(executableFlow) { projectPath, executable -> projectPath to executable }
|
||||
.onEach { (projectPath, executable) ->
|
||||
val venvPath = projectPath.resolve(".venv")
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
venvExistenceValidationState.set(
|
||||
if (venvPath.exists())
|
||||
VenvExistenceValidationState.Error(Paths.get(".venv"))
|
||||
else
|
||||
VenvExistenceValidationState.Invisible
|
||||
)
|
||||
}.launchIn(scope)
|
||||
}
|
||||
|
||||
if (executable == "") {
|
||||
return@onEach
|
||||
}
|
||||
|
||||
versionComboBox.removeAllItems()
|
||||
versionComboBox.addItem(null)
|
||||
versionComboBox.selectedItem = null
|
||||
|
||||
try {
|
||||
loading.set(true)
|
||||
|
||||
val pyProjectTomlPath = projectPath.resolve("pyproject.toml")
|
||||
|
||||
val pythonVersions = withContext(Dispatchers.IO) {
|
||||
val versionRequest = if (pyProjectTomlPath.exists()) {
|
||||
PyProjectToml.parse(pyProjectTomlPath.inputStream()).getOrNull()?.project?.requiresPython
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val cli = createUvCli(Path.of(executable))
|
||||
val uvLowLevel = createUvLowLevel(Path.of(""), cli)
|
||||
uvLowLevel.listSupportedPythonVersions(versionRequest)
|
||||
.getOr { return@withContext emptyList() }
|
||||
.toList()
|
||||
.sortedDescending()
|
||||
}
|
||||
|
||||
pythonVersions.forEach {
|
||||
versionComboBox.addItem(it)
|
||||
}
|
||||
} finally {
|
||||
loading.set(false)
|
||||
}
|
||||
}
|
||||
.launchIn(scope)
|
||||
}
|
||||
|
||||
override fun onVenvSelectExisting() {
|
||||
@@ -67,14 +174,20 @@ internal class EnvironmentCreatorUv(
|
||||
setUvExecutable(savingPath)
|
||||
}
|
||||
|
||||
override suspend fun setupEnvSdk(project: Project, module: Module?, baseSdks: List<Sdk>, projectPath: String, homePath: String?, installPackages: Boolean): PyResult<Sdk> {
|
||||
override suspend fun setupEnvSdk(
|
||||
project: Project,
|
||||
module: Module?,
|
||||
baseSdks: List<Sdk>,
|
||||
projectPath: String,
|
||||
homePath: String?,
|
||||
installPackages: Boolean,
|
||||
): PyResult<Sdk> {
|
||||
val workingDir = module?.basePath?.let { tryResolvePath(it) } ?: project.basePath?.let { tryResolvePath(it) }
|
||||
if (workingDir == null) {
|
||||
return PyResult.localizedError("working dir is not specified for uv environment setup")
|
||||
}
|
||||
|
||||
val python = homePath?.let { Path.of(it) }
|
||||
return setupNewUvSdkAndEnvUnderProgress(project, workingDir, baseSdks, python)
|
||||
return setupNewUvSdkAndEnvUnderProgress(project, workingDir, baseSdks, pythonVersion.get())
|
||||
}
|
||||
|
||||
override suspend fun detectExecutable() {
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.jetbrains.python.packaging.common.NormalizedPythonPackageName
|
||||
import com.jetbrains.python.packaging.common.PythonOutdatedPackage
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import com.jetbrains.python.packaging.management.PythonPackageInstallRequest
|
||||
import io.github.z4kn4fein.semver.Version
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.nio.file.Path
|
||||
|
||||
@@ -17,9 +18,10 @@ interface UvCli {
|
||||
|
||||
@ApiStatus.Internal
|
||||
interface UvLowLevel {
|
||||
suspend fun initializeEnvironment(init: Boolean, python: Path?): PyResult<Path>
|
||||
suspend fun initializeEnvironment(init: Boolean, version: Version?): PyResult<Path>
|
||||
|
||||
suspend fun listUvPythons(): PyResult<Set<Path>>
|
||||
suspend fun listSupportedPythonVersions(versionRequest: String? = null): PyResult<Set<Version>>
|
||||
|
||||
/**
|
||||
* Manage project dependencies by adding/removing them to the project along side installation
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.jetbrains.python.sdk.createSdk
|
||||
import com.jetbrains.python.sdk.getOrCreateAdditionalData
|
||||
import com.jetbrains.python.sdk.uv.impl.createUvCli
|
||||
import com.jetbrains.python.sdk.uv.impl.createUvLowLevel
|
||||
import io.github.z4kn4fein.semver.Version
|
||||
import java.nio.file.Path
|
||||
import javax.swing.Icon
|
||||
import kotlin.io.path.exists
|
||||
@@ -49,13 +50,13 @@ suspend fun setupNewUvSdkAndEnvUnderProgress(
|
||||
suspend fun setupNewUvSdkAndEnv(
|
||||
workingDir: Path,
|
||||
existingSdks: List<Sdk>,
|
||||
basePython: Path?,
|
||||
version: Version?,
|
||||
): PyResult<Sdk> {
|
||||
val toml = workingDir.resolve(PY_PROJECT_TOML)
|
||||
val init = !toml.exists()
|
||||
|
||||
val uv = createUvLowLevel(workingDir, createUvCli())
|
||||
val envExecutable = uv.initializeEnvironment(init, basePython)
|
||||
val envExecutable = uv.initializeEnvironment(init, version)
|
||||
.getOr {
|
||||
return it
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.jetbrains.python.sdk.uv.UvCli
|
||||
import com.jetbrains.python.sdk.uv.UvLowLevel
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader
|
||||
import com.jetbrains.python.venvReader.tryResolvePath
|
||||
import io.github.z4kn4fein.semver.Version
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.nio.file.Path
|
||||
@@ -27,13 +28,14 @@ import kotlin.io.path.pathString
|
||||
|
||||
private const val NO_METADATA_MESSAGE = "does not contain a PEP 723 metadata tag"
|
||||
private const val OUTDATED_ENV_MESSAGE = "The environment is outdated"
|
||||
private val versionRegex = Regex("(\\d+\\.\\d+)\\.\\d+-.+\\s")
|
||||
|
||||
private class UvLowLevelImpl(val cwd: Path, private val uvCli: UvCli) : UvLowLevel {
|
||||
override suspend fun initializeEnvironment(init: Boolean, python: Path?): PyResult<Path> {
|
||||
override suspend fun initializeEnvironment(init: Boolean, version: Version?): PyResult<Path> {
|
||||
val addPythonArg: (MutableList<String>) -> Unit = { args ->
|
||||
python?.let {
|
||||
version?.let {
|
||||
args.add("--python")
|
||||
args.add(python.pathString)
|
||||
args.add("${version.major}.${version.minor}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +91,26 @@ private class UvLowLevelImpl(val cwd: Path, private val uvCli: UvCli) : UvLowLev
|
||||
return PyResult.success(pythons)
|
||||
}
|
||||
|
||||
override suspend fun listSupportedPythonVersions(versionRequest: String?): PyResult<Set<Version>> {
|
||||
val args = mutableListOf("python", "list")
|
||||
|
||||
if (versionRequest != null) {
|
||||
args += versionRequest
|
||||
}
|
||||
|
||||
val out = uvCli.runUv(cwd, *args.toTypedArray()).getOr { return it }
|
||||
val matches = versionRegex.findAll(out)
|
||||
|
||||
return PyResult.success(
|
||||
matches.map {
|
||||
Version.parse(
|
||||
it.groupValues[1],
|
||||
strict = false
|
||||
)
|
||||
}.toSet()
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun listPackages(): PyExecResult<List<PythonPackage>> {
|
||||
val out = uvCli.runUv(cwd, "pip", "list", "--format", "json")
|
||||
.getOr { return it }
|
||||
|
||||
Reference in New Issue
Block a user