mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-52688 migrate Python tool SDK metadata to shared fields
Back uv, Poetry, Pipenv, and venv SDKs with the common working-directory model while preserving legacy serialized fields for rollback compatibility. Target wrappers copy only project execution metadata, and uv derives its environment root from the SDK interpreter path instead of persisted flavor data. (cherry picked from commit 2f7f3d1b4eeb079736ef838e72a0c34feba4c01b) IJ-MR-215198 GitOrigin-RevId: 4c93917f2003728299513c7fec6a5a3e7e6d59e1
This commit is contained in:
committed by
intellij-monorepo-bot
parent
807f9c632b
commit
d9505303c4
+6
-3
@@ -14,8 +14,11 @@ import java.nio.file.Path
|
||||
|
||||
@ApiStatus.Internal
|
||||
class PyPoetrySdkAdditionalData : PythonSdkAdditionalData {
|
||||
constructor(associatedModulePath: Path?) : super(PyFlavorAndData(PyFlavorData.Empty, PyPoetrySdkFlavor)) {
|
||||
this.associatedModulePath = associatedModulePath?.toString()
|
||||
constructor(workingDirectory: Path) : super(
|
||||
PyFlavorAndData(PyFlavorData.Empty, PyPoetrySdkFlavor),
|
||||
workingDirectory,
|
||||
) {
|
||||
associatedModulePath = workingDirectory.toString()
|
||||
}
|
||||
|
||||
override fun save(element: Element) {
|
||||
@@ -34,7 +37,7 @@ class PyPoetrySdkAdditionalData : PythonSdkAdditionalData {
|
||||
fun load(element: Element): PyPoetrySdkAdditionalData? =
|
||||
when {
|
||||
element.getAttributeValue(IS_POETRY) == "true" -> {
|
||||
PyPoetrySdkAdditionalData(null).apply {
|
||||
PyPoetrySdkAdditionalData(Path.of("")).apply {
|
||||
load(element)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright 2000-2026 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.openapi.util.io.toNioPathOrNull
|
||||
import com.intellij.python.community.impl.poetry.common.icons.PythonCommunityImplPoetryCommonIcons
|
||||
import com.jetbrains.python.sdk.PythonSdkAdditionalData
|
||||
import com.jetbrains.python.sdk.flavors.CPythonSdkFlavor
|
||||
import com.jetbrains.python.sdk.flavors.PyFlavorData
|
||||
import com.jetbrains.python.sdk.flavors.PythonFlavorProvider
|
||||
@@ -20,6 +22,14 @@ object PyPoetrySdkFlavor : CPythonSdkFlavor<PyFlavorData.Empty>() {
|
||||
override fun getIcon(): Icon = PythonCommunityImplPoetryCommonIcons.Poetry
|
||||
override fun getFlavorDataClass(): Class<PyFlavorData.Empty> = PyFlavorData.Empty::class.java
|
||||
|
||||
override fun migrateAdditionalData(
|
||||
additionalData: PythonSdkAdditionalData,
|
||||
data: PyFlavorData.Empty,
|
||||
): AdditionalDataMigration<PyFlavorData.Empty> {
|
||||
val workingDirectory = additionalData.associatedModulePath?.takeIf { it.isNotBlank() }?.toNioPathOrNull()
|
||||
return AdditionalDataMigration(data, workingDirectory)
|
||||
}
|
||||
|
||||
override fun isValidSdkPath(pythonBinaryPath: Path): Boolean = false
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ python.sdk.running.progress.text=Running {0}\u2026
|
||||
python.sdk.running.one.minute.progress.details=About 1 minute left
|
||||
python.sdk.running.sudo.prompt=Enter your password to install {0}
|
||||
python.sdk.cannot.create.tool.should.be.installed=Cannot create SDK without a proper tool installed
|
||||
python.sdk.cannot.create.working.directory.empty=Cannot create SDK without working directory
|
||||
|
||||
# PLEASE add only the keys that are supposed to be re-used by plugins,
|
||||
# PLEASE keep keys below grouped by the topic,
|
||||
|
||||
@@ -26,6 +26,7 @@ interface FileSystem<P : PathHolder> {
|
||||
val isReadOnly: Boolean
|
||||
val isBrowsable: Boolean
|
||||
val isLocal: Boolean
|
||||
val toolPathCanBePersisted: Boolean
|
||||
val userReadableName: @NonNls String
|
||||
val platformAndRoot: PlatformAndRoot
|
||||
|
||||
@@ -69,7 +70,7 @@ interface FileSystem<P : PathHolder> {
|
||||
suspend fun detectSelectableVenv(projectPathPrefix: Path): List<DetectedSelectableInterpreter<P>>
|
||||
fun preferredInterpreterBasePath(): P? = null
|
||||
fun resolvePythonBinary(pythonHome: P): P?
|
||||
fun resolvePythonHome(pythonBinary: P): P
|
||||
fun resolvePythonHome(pythonHomeOrBinary: P): P
|
||||
fun getVenvName(pythonHome: P): String?
|
||||
|
||||
fun getBinaryToExec(path: P, workingDir: Path? = null): BinaryToExec
|
||||
|
||||
@@ -26,9 +26,12 @@ internal const val PYCHARM_HELPERS: String = ".pycharm_helpers"
|
||||
|
||||
open class PyTargetAwareAdditionalData private constructor(
|
||||
private val b: RemoteSdkPropertiesHolder,
|
||||
flavorAndData: PyFlavorAndData<*, *>?,
|
||||
flavorAndData: PyFlavorAndData<*, *>,
|
||||
workingDirectory: Path,
|
||||
requirementsFile: String?,
|
||||
targetEnvironmentConfiguration: TargetEnvironmentConfiguration? = null,
|
||||
) : PythonSdkAdditionalData(flavorAndData), TargetBasedSdkAdditionalData, RemoteSdkProperties by b, PyRemoteSdkAdditionalDataMarker {
|
||||
) : PythonSdkAdditionalData(flavorAndData, workingDirectory), TargetBasedSdkAdditionalData, RemoteSdkProperties by b,
|
||||
PyRemoteSdkAdditionalDataMarker {
|
||||
/**
|
||||
* The source of truth for the target configuration.
|
||||
*/
|
||||
@@ -47,14 +50,28 @@ open class PyTargetAwareAdditionalData private constructor(
|
||||
}
|
||||
|
||||
init {
|
||||
this.requirementsFile = requirementsFile
|
||||
notifyTargetEnvironmentConfigurationChanged()
|
||||
}
|
||||
|
||||
constructor(flavorAndData: PyFlavorAndData<*, *>, targetEnvironmentConfiguration: TargetEnvironmentConfiguration? = null) : this(
|
||||
RemoteSdkPropertiesHolder(
|
||||
PYCHARM_HELPERS),
|
||||
@ApiStatus.Internal
|
||||
constructor(
|
||||
flavorAndData: PyFlavorAndData<*, *>,
|
||||
workingDirectory: Path,
|
||||
targetEnvironmentConfiguration: TargetEnvironmentConfiguration? = null,
|
||||
requirementsFile: String? = null,
|
||||
) : this(
|
||||
RemoteSdkPropertiesHolder(PYCHARM_HELPERS),
|
||||
flavorAndData,
|
||||
targetEnvironmentConfiguration)
|
||||
workingDirectory,
|
||||
requirementsFile,
|
||||
targetEnvironmentConfiguration,
|
||||
)
|
||||
|
||||
@ApiStatus.Internal
|
||||
constructor(additionalData: PythonSdkAdditionalData, targetEnvironmentConfiguration: TargetEnvironmentConfiguration? = null) : this(
|
||||
additionalData.flavorAndData, additionalData.workingDirectory, targetEnvironmentConfiguration, additionalData.requirementsFile
|
||||
)
|
||||
|
||||
override fun save(rootElement: Element) { // store "interpreter paths" (i.e. `PYTHONPATH` elements)
|
||||
super.save(rootElement) // store `INTERPRETER_PATH`, `HELPERS_PATH`, etc
|
||||
@@ -136,7 +153,8 @@ open class PyTargetAwareAdditionalData private constructor(
|
||||
}
|
||||
|
||||
// TODO Python flavor identifier must be stored in `element` and taken from it here
|
||||
val data = PyTargetAwareAdditionalData(flavorAndData = PyFlavorAndData(PyFlavorData.Empty, UnixPythonSdkFlavor.getInstance()))
|
||||
val data = PyTargetAwareAdditionalData(flavorAndData = PyFlavorAndData(PyFlavorData.Empty, UnixPythonSdkFlavor.getInstance()),
|
||||
workingDirectory = Path.of(""))
|
||||
data.interpreterPath = homePath
|
||||
data.load(element)
|
||||
// TODO [targets] Load `SKELETONS_PATH` for Target-based Python SDK from `Element`
|
||||
@@ -149,7 +167,8 @@ open class PyTargetAwareAdditionalData private constructor(
|
||||
}
|
||||
|
||||
private class DummyTargetAwareAdditionalData(base: Element) :
|
||||
PyTargetAwareAdditionalData(flavorAndData = PyFlavorAndData(PyFlavorData.Empty, UnixPythonSdkFlavor.getInstance())),
|
||||
PyTargetAwareAdditionalData(flavorAndData = PyFlavorAndData(PyFlavorData.Empty, UnixPythonSdkFlavor.getInstance()),
|
||||
workingDirectory = Path.of("")),
|
||||
PyRemoteSdkAdditionalDataMarker {
|
||||
val element: Element = base.clone()
|
||||
|
||||
|
||||
+1
-2
@@ -33,8 +33,7 @@ suspend fun PyEnvironmentFactory.createSdk(request: SdkCreationRequest): Pair<Sd
|
||||
Pair(sdk, environment)
|
||||
}
|
||||
is SdkCreationRequest.RemotePython -> {
|
||||
val targetData = PyTargetAwareAdditionalData(PyFlavorAndData(PyFlavorData.Empty, UnixPythonSdkFlavor.getInstance()),
|
||||
request.targetConfig).apply {
|
||||
val targetData = PyTargetAwareAdditionalData(PyFlavorAndData(PyFlavorData.Empty, UnixPythonSdkFlavor.getInstance()), workingDir, request.targetConfig).apply {
|
||||
interpreterPath = PYTHON_PATH_ON_TARGET
|
||||
}
|
||||
try {
|
||||
|
||||
Vendored
+1
-1
@@ -6,7 +6,7 @@ import org.jetbrains.annotations.ApiStatus
|
||||
@ApiStatus.Internal
|
||||
class CachingPyEnvironmentFactory(
|
||||
private val wrapper: PyEnvironmentFactory,
|
||||
) : PyEnvironmentFactory {
|
||||
) : PyEnvironmentFactory by wrapper {
|
||||
|
||||
private val cache = PyEnvironmentCache()
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -11,7 +11,7 @@ import java.nio.file.Path
|
||||
* matching one.
|
||||
*/
|
||||
class DefaultPyEnvironmentFactory(
|
||||
private val workingDir: Path,
|
||||
override val workingDir: Path,
|
||||
private val providers: List<Pair<(PyEnvironmentSpec<*>) -> Boolean, PyEnvironmentProvider<*>>>
|
||||
): PyEnvironmentFactory {
|
||||
|
||||
|
||||
+3
@@ -2,10 +2,13 @@
|
||||
package com.intellij.python.test.env.core
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.nio.file.Path
|
||||
|
||||
@ApiStatus.Internal
|
||||
interface PyEnvironmentFactory: AutoCloseable {
|
||||
|
||||
val workingDir: Path
|
||||
|
||||
suspend fun createEnvironment(spec: PyEnvironmentSpec<*>): PyEnvironment {
|
||||
return createEnvironment(this, spec)
|
||||
}
|
||||
|
||||
+26
-20
@@ -26,26 +26,32 @@ import java.nio.file.Path
|
||||
* Create virtual env in [where]. If [addToSdkTable] then also added to the project jdk table
|
||||
*/
|
||||
fun TestFixture<SdkFixture<PyEnvironment>>.pyVenvFixture(
|
||||
where: TestFixture<Path>,
|
||||
addToSdkTable: Boolean,
|
||||
moduleFixture: TestFixture<Module>? = null,
|
||||
where: TestFixture<Path>,
|
||||
addToSdkTable: Boolean,
|
||||
moduleFixture: TestFixture<Module>? = null,
|
||||
): TestFixture<Sdk> = testFixture {
|
||||
val env = this@pyVenvFixture.init().env
|
||||
withContext(Dispatchers.EDT) {
|
||||
val module = moduleFixture?.init()
|
||||
val venvDir = where.init().resolve(".venv")
|
||||
val venvPython = createVenv(env.pythonPath, venvDir).getOrThrow()
|
||||
val venvSdk = createSdk(PathHolder.Eel(venvPython), createVenvAdditionalData(), advancedOpts = SdkCreationAdvancedOpts(persist = addToSdkTable)).orThrow()
|
||||
if (addToSdkTable) {
|
||||
if (module != null) {
|
||||
module.pythonSdk = venvSdk
|
||||
venvSdk.setAssociationToModule(module)
|
||||
}
|
||||
}
|
||||
initialized(venvSdk) {
|
||||
edtWriteAction {
|
||||
ProjectJdkTable.getInstance().removeJdk(venvSdk)
|
||||
}
|
||||
}
|
||||
val env = this@pyVenvFixture.init().env
|
||||
withContext(Dispatchers.EDT) {
|
||||
val module = moduleFixture?.init()
|
||||
val workingDirectory = where.init()
|
||||
val venvDir = workingDirectory.resolve(".venv")
|
||||
val venvPython = createVenv(env.pythonPath, venvDir).getOrThrow()
|
||||
val additionalData = createVenvAdditionalData(workingDirectory)
|
||||
val venvSdk = createSdk(
|
||||
PathHolder.Eel(venvPython),
|
||||
additionalData,
|
||||
advancedOpts = SdkCreationAdvancedOpts(persist = addToSdkTable),
|
||||
).orThrow()
|
||||
if (addToSdkTable) {
|
||||
if (module != null) {
|
||||
module.pythonSdk = venvSdk
|
||||
venvSdk.setAssociationToModule(module)
|
||||
}
|
||||
}
|
||||
initialized(venvSdk) {
|
||||
edtWriteAction {
|
||||
ProjectJdkTable.getInstance().removeJdk(venvSdk)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,31 +11,26 @@ import kotlin.io.path.pathString
|
||||
@ApiStatus.Internal
|
||||
class UvSdkAdditionalData : PythonSdkAdditionalData {
|
||||
val flavorData: UvSdkFlavorData
|
||||
get() = flavorAndData.data as UvSdkFlavorData
|
||||
|
||||
constructor(uvWorkingDirectory: Path?, usePip: Boolean?, venvPath: FullPathOnTarget?, uvPath: FullPathOnTarget?) : this(UvSdkFlavorData(
|
||||
uvWorkingDirectory,
|
||||
usePip,
|
||||
venvPath,
|
||||
uvPath))
|
||||
constructor(
|
||||
uvWorkingDirectory: Path,
|
||||
usePip: Boolean?,
|
||||
venvPath: FullPathOnTarget?,
|
||||
uvPath: FullPathOnTarget?,
|
||||
) : this(UvSdkFlavorData(uvWorkingDirectory, usePip, venvPath, uvPath), uvWorkingDirectory)
|
||||
|
||||
private constructor(flavorData: UvSdkFlavorData) : super(PyFlavorAndData(flavorData, UvSdkFlavor)) {
|
||||
this.flavorData = flavorData
|
||||
}
|
||||
|
||||
constructor(data: PythonSdkAdditionalData) : super(data) {
|
||||
when (data) {
|
||||
is UvSdkAdditionalData -> this.flavorData = data.flavorData
|
||||
else -> this.flavorData = UvSdkFlavorData(null, null, null, null)
|
||||
}
|
||||
}
|
||||
private constructor(
|
||||
legacyFlavorData: UvSdkFlavorData, uvWorkingDirectory: Path,
|
||||
) : super(PyFlavorAndData(legacyFlavorData, UvSdkFlavor), uvWorkingDirectory)
|
||||
|
||||
override fun save(element: Element) {
|
||||
super.save(element)
|
||||
element.setAttribute(IS_UV, "true")
|
||||
|
||||
// keep backward compatibility with old data
|
||||
if (flavorData.uvWorkingDirectory?.pathString?.isNotBlank() == true) {
|
||||
element.setAttribute(UV_WORKING_DIR, flavorData.uvWorkingDirectory.pathString)
|
||||
val persistedWorkingDirectory = workingDirectory.takeIf { hasValidWorkingDirectory() } ?: flavorData.uvWorkingDirectory
|
||||
if (persistedWorkingDirectory != null) {
|
||||
element.setAttribute(UV_WORKING_DIR, persistedWorkingDirectory.pathString)
|
||||
}
|
||||
|
||||
if (flavorData.usePip == true) {
|
||||
@@ -62,22 +57,22 @@ class UvSdkAdditionalData : PythonSdkAdditionalData {
|
||||
fun load(element: Element): UvSdkAdditionalData? {
|
||||
return when {
|
||||
element.getAttributeValue(IS_UV) == "true" -> {
|
||||
val uvWorkingDirectory =
|
||||
if (element.getAttributeValue(UV_WORKING_DIR).isNullOrEmpty()) null else Path.of(element.getAttributeValue(UV_WORKING_DIR))
|
||||
val uvWorkingDirectory = if (element.getAttributeValue(UV_WORKING_DIR).isNullOrEmpty()) {
|
||||
Path.of("")
|
||||
}
|
||||
else {
|
||||
Path.of(element.getAttributeValue(UV_WORKING_DIR))
|
||||
}
|
||||
val usePip = element.getAttributeValue(USE_PIP)?.toBoolean()
|
||||
val venvPath = if (element.getAttributeValue(UV_VENV_PATH).isNullOrEmpty()) null else element.getAttributeValue(UV_VENV_PATH)
|
||||
val venvPath = element.getAttributeValue(UV_VENV_PATH)?.takeIf { it.isNotBlank() }
|
||||
val uvPath = if (element.getAttributeValue(UV_TOOL_PATH).isNullOrEmpty()) null else element.getAttributeValue(UV_TOOL_PATH)
|
||||
UvSdkAdditionalData(uvWorkingDirectory, usePip, venvPath, uvPath).apply {
|
||||
val legacyFlavorData = UvSdkFlavorData(uvWorkingDirectory, usePip, venvPath, uvPath)
|
||||
UvSdkAdditionalData(legacyFlavorData, uvWorkingDirectory).apply {
|
||||
load(element)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun copy(data: PythonSdkAdditionalData): UvSdkAdditionalData {
|
||||
return UvSdkAdditionalData(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,37 @@
|
||||
package com.jetbrains.python.sdk.uv
|
||||
|
||||
import com.intellij.python.uv.common.icons.PythonUvCommonIcons
|
||||
import com.jetbrains.python.sdk.PythonSdkAdditionalData
|
||||
import com.jetbrains.python.sdk.flavors.CPythonSdkFlavor
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.nio.file.Path
|
||||
import javax.swing.Icon
|
||||
|
||||
internal object UvSdkFlavor : CPythonSdkFlavor<UvSdkFlavorData>() {
|
||||
@ApiStatus.Internal
|
||||
object UvSdkFlavor : CPythonSdkFlavor<UvSdkFlavorData>() {
|
||||
override fun getIcon(): Icon = PythonUvCommonIcons.UV
|
||||
override fun getFlavorDataClass(): Class<UvSdkFlavorData> = UvSdkFlavorData::class.java
|
||||
|
||||
override fun migrateAdditionalData(
|
||||
additionalData: PythonSdkAdditionalData,
|
||||
data: UvSdkFlavorData,
|
||||
): AdditionalDataMigration<UvSdkFlavorData> {
|
||||
val workingDirectory = additionalData.workingDirectory.takeIf { additionalData.hasValidWorkingDirectory() }
|
||||
?: data.uvWorkingDirectory
|
||||
val migratedData = data.copy(
|
||||
uvWorkingDirectory = workingDirectory,
|
||||
usePip = data.usePip,
|
||||
venvPath = data.venvPath,
|
||||
uvPath = data.uvPath,
|
||||
)
|
||||
return AdditionalDataMigration(migratedData, workingDirectory)
|
||||
}
|
||||
|
||||
override fun withWorkingDirectory(
|
||||
data: UvSdkFlavorData,
|
||||
workingDirectory: Path,
|
||||
): UvSdkFlavorData = data.copy(uvWorkingDirectory = workingDirectory)
|
||||
|
||||
override fun isValidSdkPath(pythonBinaryPath: Path): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -10,7 +10,13 @@ import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.nio.file.Path
|
||||
|
||||
// TODO PY-87712 Move to a separate storage
|
||||
/**
|
||||
* TODO PY-87712 Should drop as a whole
|
||||
* uvWorkingDirectory - workingDirectory in PythonSdkAdditionalData
|
||||
* usePip - can be deduced based on requirementsFile in PythonSdkAdditionalData
|
||||
* venvPath - sdkHome
|
||||
* uvPath - stored as a setting for local EELs, for targets we can use detection only
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
data class UvSdkFlavorData(
|
||||
val uvWorkingDirectory: Path?,
|
||||
@@ -25,9 +31,12 @@ data class UvSdkFlavorData(
|
||||
throw IllegalArgumentException("Sdk ${sdk} doesn't have interpreter path set")
|
||||
}
|
||||
targetCommandLineBuilder.setExePath(interpreterPath)
|
||||
targetCommandLineBuilder.addEnvironmentVariable("UV_PROJECT_ENVIRONMENT", venvPath)
|
||||
val separator = targetCommandLineBuilder.request.targetPlatform.platform.fileSeparator
|
||||
targetCommandLineBuilder.addEnvironmentVariable("UV_PROJECT_ENVIRONMENT", interpreterPath.parentPath(separator).parentPath(separator))
|
||||
if (!PythonSdkUtil.isRemote(sdk)) {
|
||||
PySdkUtil.activateVirtualEnv(sdk)
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.parentPath(separator: Char): String = removeSuffix(separator.toString()).substringBeforeLast(separator)
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.intellij.python.venv
|
||||
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.diagnostic.fileLogger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.platform.eel.provider.asEelPath
|
||||
import com.intellij.python.community.execService.BinaryToExec
|
||||
import com.intellij.python.community.execService.ExecOptions
|
||||
@@ -17,17 +18,21 @@ import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.errorProcessing.getOr
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.sdk.ModuleOrProject
|
||||
import com.jetbrains.python.sdk.PySdkSettings
|
||||
import com.jetbrains.python.sdk.PythonSdkAdditionalData
|
||||
import com.jetbrains.python.sdk.flavors.PyFlavorAndData
|
||||
import com.jetbrains.python.sdk.flavors.PyFlavorData
|
||||
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
|
||||
import com.jetbrains.python.sdk.impl.PySdkBundle
|
||||
import com.jetbrains.python.sdk.workingDirectory
|
||||
import com.jetbrains.python.venvReader.Directory
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import org.jetbrains.annotations.CheckReturnValue
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.pathString
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@@ -99,8 +104,18 @@ const val VIRTUALENV_ZIPAPP_NAME: HelperName = "virtualenv-py3.pyz"
|
||||
val MINIMUM_SUPPORTED_VENV_PYTHON_VERSION: LanguageLevel = LanguageLevel.PYTHON38
|
||||
|
||||
/**
|
||||
* Creates [PythonSdkAdditionalData] for virtual env
|
||||
* Creates [PythonSdkAdditionalData] for virtual env using working directory
|
||||
*/
|
||||
@Internal
|
||||
fun createVenvAdditionalData(): PythonSdkAdditionalData =
|
||||
PythonSdkAdditionalData(PyFlavorAndData(PyFlavorData.Empty, VirtualEnvSdkFlavor.getInstance()))
|
||||
fun createVenvAdditionalData(workingDirectory: Path): PythonSdkAdditionalData =
|
||||
PythonSdkAdditionalData(PyFlavorAndData(PyFlavorData.Empty, VirtualEnvSdkFlavor.getInstance()), workingDirectory)
|
||||
|
||||
/**
|
||||
* Creates [PythonSdkAdditionalData] for virtual env using module baseDir as working directory
|
||||
*/
|
||||
@Internal
|
||||
fun createVenvAdditionalData(module: Module): PyResult<PythonSdkAdditionalData> {
|
||||
return ModuleOrProject.ModuleAndProject(module).workingDirectory?.let {
|
||||
PyResult.success(createVenvAdditionalData(it))
|
||||
} ?: PyResult.localizedError(PySdkBundle.message("python.sdk.cannot.create.working.directory.empty"))
|
||||
}
|
||||
+1
-1
@@ -109,7 +109,7 @@ internal class PyPipfileSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
|
||||
val sdk = createSdk(
|
||||
PathHolder.Eel(file.toNioPath()),
|
||||
PyPipEnvSdkAdditionalData(),
|
||||
PyPipEnvSdkAdditionalData(Path.of(basePath)),
|
||||
suggestedSdkName(basePath)
|
||||
).getOr { return@withBackgroundProgress it }
|
||||
|
||||
|
||||
+1
-1
@@ -152,7 +152,7 @@ internal class PyPoetrySdkConfiguration : PyProjectTomlConfigurationExtension {
|
||||
LOGGER.debug("Setting up associated poetry environment: $path, $basePath")
|
||||
val sdk = createSdk(
|
||||
PathHolder.Eel(file.toNioPath()),
|
||||
PyPoetrySdkAdditionalData(module.baseDir?.path?.let { Path.of(it) }),
|
||||
PyPoetrySdkAdditionalData(basePath),
|
||||
suggestedSdkName(basePath)
|
||||
).getOr { return@withBackgroundProgress it }
|
||||
|
||||
|
||||
@@ -101,7 +101,6 @@ suspend fun createVenvAndSdk(
|
||||
}
|
||||
|
||||
logger.info("using venv python $venvPython")
|
||||
val sdk = getSdk(venvPython).getOr { return it }
|
||||
if (moduleOrProject.moduleIfExists == null && project.modules.isEmpty()) {
|
||||
edtWriteAction {
|
||||
val projectPath = vfsPath.toNioPath()
|
||||
@@ -111,6 +110,7 @@ suspend fun createVenvAndSdk(
|
||||
}
|
||||
val module = moduleOrProject.moduleIfExists ?: project.modules.first()
|
||||
ensureModuleHasRoot(module, vfsPath)
|
||||
val sdk = getSdk(venvPython, module).getOr { return it }
|
||||
withContext(Dispatchers.IO) {
|
||||
// generated files should be readable by VFS
|
||||
VfsUtil.markDirtyAndRefresh(false, true, true, vfsPath)
|
||||
@@ -192,11 +192,12 @@ private suspend fun ensureModuleHasRoot(module: Module, root: VirtualFile): Unit
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getSdk(pythonPath: PythonBinary): PyResult<Sdk> =
|
||||
private suspend fun getSdk(pythonPath: PythonBinary, module: Module): PyResult<Sdk> =
|
||||
withProgressText(ProjectBundle.message("progress.text.configuring.sdk")) {
|
||||
val allJdks = PythonSdkUtil.getAllSdks().toTypedArray()
|
||||
val currentSdk = allJdks.firstOrNull { sdk -> sdk.homeDirectory?.toNioPath() == pythonPath }
|
||||
if (currentSdk != null) return@withProgressText PyResult.success(currentSdk)
|
||||
|
||||
return@withProgressText createSdk(PathHolder.Eel(pythonPath), createVenvAdditionalData())
|
||||
val additionalData = createVenvAdditionalData(module).getOr { return@withProgressText it }
|
||||
return@withProgressText createSdk(PathHolder.Eel(pythonPath), additionalData)
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ data class EelFileSystem(
|
||||
override val isBrowsable: Boolean = true
|
||||
override val isReadOnly: Boolean = false
|
||||
override val isLocal: Boolean = eelApi == localEel
|
||||
override val toolPathCanBePersisted: Boolean = isLocal
|
||||
override val userReadableName: @NonNls String = eelApi.descriptor.name
|
||||
override val platformAndRoot: PlatformAndRoot = eelApi.getPlatformAndRoot()
|
||||
|
||||
@@ -143,6 +144,7 @@ data class EelFileSystem(
|
||||
targetPanelExtension: TargetPanelExtension?,
|
||||
suggestedSdkName: String?,
|
||||
): PyResult<Sdk> {
|
||||
require(sdkAdditionalData.hasValidWorkingDirectory()) { "Python SDK working directory must be initialized before setup" }
|
||||
return createSdk(pythonBinaryPath, sdkAdditionalData, suggestedSdkName)
|
||||
}
|
||||
|
||||
@@ -279,8 +281,8 @@ data class EelFileSystem(
|
||||
return pythonHome.path.resolvePythonBinary()?.let { PathHolder.Eel(it) }
|
||||
}
|
||||
|
||||
override fun resolvePythonHome(pythonBinary: PathHolder.Eel): PathHolder.Eel {
|
||||
return PathHolder.Eel(pythonBinary.path.resolvePythonHome())
|
||||
override fun resolvePythonHome(pythonHomeOrBinary: PathHolder.Eel): PathHolder.Eel {
|
||||
return PathHolder.Eel(pythonHomeOrBinary.path.resolvePythonHome())
|
||||
}
|
||||
|
||||
override fun getVenvName(pythonHome: PathHolder.Eel): String? {
|
||||
@@ -382,6 +384,7 @@ data class TargetFileSystem(
|
||||
override val isBrowsable: Boolean
|
||||
get() = targetEnvironmentConfiguration.getTargetType() is BrowsableTargetEnvironmentType
|
||||
override val isLocal: Boolean = false
|
||||
override val toolPathCanBePersisted: Boolean = false
|
||||
override val userReadableName: @NonNls String = targetEnvironmentConfiguration.displayName
|
||||
override val platformAndRoot: PlatformAndRoot = targetEnvironmentConfiguration.getPlatformAndRoot()
|
||||
|
||||
@@ -435,11 +438,11 @@ data class TargetFileSystem(
|
||||
targetPanelExtension: TargetPanelExtension?,
|
||||
suggestedSdkName: String?,
|
||||
): PyResult<Sdk> {
|
||||
require(sdkAdditionalData.hasValidWorkingDirectory()) { "Python SDK working directory must be initialized before setup" }
|
||||
val languageLevel = getBinaryToExec(pythonBinaryPath).validatePythonAndGetInfo().getOr { return it }.languageLevel
|
||||
|
||||
val (additionalData, customSdkSuggestedName) = run {
|
||||
val flavorAndData = sdkAdditionalData.flavorAndData
|
||||
val data = PyTargetAwareAdditionalData(flavorAndData).also {
|
||||
val data = PyTargetAwareAdditionalData(sdkAdditionalData, targetEnvironmentConfiguration).also {
|
||||
it.interpreterPath = pythonBinaryPath.toString()
|
||||
it.targetEnvironmentConfiguration = targetEnvironmentConfiguration
|
||||
}
|
||||
@@ -566,8 +569,17 @@ data class TargetFileSystem(
|
||||
return PathHolder.Target(VirtualEnvReader().findPythonInPythonRootForTarget(pythonHomeString, platform))
|
||||
}
|
||||
|
||||
override fun resolvePythonHome(pythonBinary: PathHolder.Target): PathHolder.Target {
|
||||
return PathHolder.Target(pythonBinary.pathString.substringBeforeLast("/bin/"))
|
||||
override fun resolvePythonHome(pythonHomeOrBinary: PathHolder.Target): PathHolder.Target {
|
||||
val separator = targetEnvironmentConfiguration.getPlatformAndRoot().platform.fileSeparator
|
||||
val path = pythonHomeOrBinary.pathString.removeSuffix(separator.toString())
|
||||
val fileName = path.substringAfterLast(separator).lowercase()
|
||||
val binaryDirectory = path.substringBeforeLast(separator)
|
||||
val binaryDirectoryName = binaryDirectory.substringAfterLast(separator)
|
||||
if (!fileName.startsWith("python") ||
|
||||
!(binaryDirectoryName.equals("bin", ignoreCase = true) || binaryDirectoryName.equals("scripts", ignoreCase = true))) {
|
||||
return pythonHomeOrBinary
|
||||
}
|
||||
return PathHolder.Target(binaryDirectory.substringBeforeLast(separator))
|
||||
}
|
||||
|
||||
override fun getVenvName(pythonHome: PathHolder.Target): String? {
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.intellij.python.uv.common.UV_UI_INFO
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.sdk.ModuleOrProject
|
||||
import com.jetbrains.python.sdk.workingDirectory
|
||||
import com.jetbrains.python.sdk.add.v2.CustomExistingEnvironmentSelector
|
||||
import com.jetbrains.python.sdk.add.v2.DetectedSelectableInterpreter
|
||||
import com.jetbrains.python.sdk.add.v2.PathHolder
|
||||
@@ -15,7 +16,6 @@ import com.jetbrains.python.sdk.add.v2.PythonMutableTargetAddInterpreterModel
|
||||
import com.jetbrains.python.sdk.add.v2.ToolValidator
|
||||
import com.jetbrains.python.sdk.add.v2.ValidatedPath
|
||||
import com.jetbrains.python.sdk.add.v2.savePathForEelOnly
|
||||
import com.jetbrains.python.sdk.baseDir
|
||||
import com.jetbrains.python.sdk.uv.impl.setUvExecutableLocal
|
||||
import com.jetbrains.python.sdk.uv.setupExistingEnvAndSdk
|
||||
import com.jetbrains.python.statistics.InterpreterType
|
||||
@@ -37,12 +37,8 @@ internal class UvExistingEnvironmentSelector<P : PathHolder>(model: PythonMutabl
|
||||
val selectedInterpreterPath =
|
||||
sdkHomePath ?: return PyResult.localizedError(PyBundle.message("python.sdk.provided.path.is.invalid", sdkHomePath))
|
||||
|
||||
val associatedModule = extractModule(moduleOrProject)
|
||||
|
||||
val basePathString = associatedModule?.baseDir?.path
|
||||
?: moduleOrProject.project.basePath
|
||||
?: return PyResult.localizedError(PyBundle.message("python.sdk.provided.path.is.invalid", null))
|
||||
val workingDir = Path.of(basePathString)
|
||||
val workingDir = moduleOrProject.workingDirectory
|
||||
?: return PyResult.localizedError(PyBundle.message("python.sdk.project.working.directory.not.found"))
|
||||
|
||||
return setupExistingEnvAndSdk(
|
||||
pythonBinary = selectedInterpreterPath,
|
||||
@@ -61,10 +57,4 @@ internal class UvExistingEnvironmentSelector<P : PathHolder>(model: PythonMutabl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractModule(moduleOrProject: ModuleOrProject): Module? =
|
||||
when (moduleOrProject) {
|
||||
is ModuleOrProject.ModuleAndProject -> moduleOrProject.module
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ internal suspend fun PyCondaCommand.createCondaSdkFromExistingEnvironment(
|
||||
SdkCreationRequest.EelSdk(Path(interpreterPath), PythonSdkAdditionalData(flavorAndData, workingDirectory))
|
||||
}
|
||||
else {
|
||||
val addData = PyTargetAwareAdditionalData(flavorAndData, targetConfig).also {
|
||||
val addData = PyTargetAwareAdditionalData(flavorAndData, workingDirectory, targetConfig).also {
|
||||
it.interpreterPath = interpreterPath
|
||||
}
|
||||
SdkCreationRequest.TargetSdk(interpreterPath, addData)
|
||||
|
||||
@@ -84,7 +84,7 @@ internal suspend fun setupPipEnvSdkWithProgressReport(
|
||||
|
||||
return createSdk(
|
||||
pythonBinaryPath = PathHolder.Eel(pythonExecutablePath),
|
||||
sdkAdditionalData = PyPipEnvSdkAdditionalData()
|
||||
sdkAdditionalData = PyPipEnvSdkAdditionalData(moduleBasePath)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,17 @@
|
||||
package com.jetbrains.python.sdk.pipenv
|
||||
|
||||
import com.jetbrains.python.sdk.PythonSdkAdditionalData
|
||||
import com.jetbrains.python.sdk.flavors.PyFlavorAndData
|
||||
import com.jetbrains.python.sdk.flavors.PyFlavorData
|
||||
import org.jdom.Element
|
||||
import java.nio.file.Path
|
||||
|
||||
/**
|
||||
* Additional Pipenv data associated with an SDK.
|
||||
*
|
||||
*/
|
||||
class PyPipEnvSdkAdditionalData : PythonSdkAdditionalData {
|
||||
constructor() : super(PyPipEnvSdkFlavor)
|
||||
constructor(data: PythonSdkAdditionalData) : super(data)
|
||||
constructor(workingDirectory: Path) : super(PyFlavorAndData(PyFlavorData.Empty, PyPipEnvSdkFlavor), workingDirectory)
|
||||
|
||||
override fun save(element: Element) {
|
||||
super.save(element)
|
||||
@@ -28,18 +30,11 @@ class PyPipEnvSdkAdditionalData : PythonSdkAdditionalData {
|
||||
fun load(element: Element): PyPipEnvSdkAdditionalData? =
|
||||
when {
|
||||
element.getAttributeValue(IS_PIPENV) == "true" -> {
|
||||
PyPipEnvSdkAdditionalData().apply {
|
||||
PyPipEnvSdkAdditionalData(Path.of("")).apply {
|
||||
load(element)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance of data with copied fields.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun copy(data: PythonSdkAdditionalData): PyPipEnvSdkAdditionalData =
|
||||
PyPipEnvSdkAdditionalData(data)
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import com.intellij.platform.eel.EelApi
|
||||
import com.intellij.platform.eel.provider.localEel
|
||||
import com.intellij.python.community.execService.python.validatePythonAndGetInfo
|
||||
import com.intellij.python.community.impl.poetry.common.poetryPath
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.PythonHomePath
|
||||
import com.jetbrains.python.errorProcessing.ErrorSink
|
||||
@@ -29,9 +28,9 @@ import com.jetbrains.python.sdk.add.v2.EelFileSystem
|
||||
import com.jetbrains.python.sdk.add.v2.FileSystem
|
||||
import com.jetbrains.python.sdk.add.v2.PathHolder
|
||||
import com.jetbrains.python.sdk.add.v2.toEelFileSystem
|
||||
import com.jetbrains.python.sdk.associatedModulePath
|
||||
import com.jetbrains.python.sdk.impl.PySdkBundle
|
||||
import com.jetbrains.python.sdk.pyRichSdkAsync
|
||||
import com.jetbrains.python.sdk.pySdkAdditionalData
|
||||
import com.jetbrains.python.sdk.runTool
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader
|
||||
import io.github.z4kn4fein.semver.Version
|
||||
@@ -40,7 +39,6 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.apache.tuweni.toml.Toml
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import org.jetbrains.annotations.Nls
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.name
|
||||
import kotlin.io.path.pathString
|
||||
@@ -48,7 +46,6 @@ import kotlin.io.path.pathString
|
||||
/**
|
||||
* This source code is edited by @koxudaxi Koudai Aono <koxudaxi@gmail.com>
|
||||
*/
|
||||
private val poetryNotFoundException: @Nls String = PyBundle.message("python.sdk.poetry.execution.exception.no.poetry.message")
|
||||
private val VERSION_2 = "2.0.0".toVersion()
|
||||
|
||||
|
||||
@@ -103,8 +100,7 @@ internal suspend fun getPoetryExecutable(eel: EelApi = localEel): Path? =
|
||||
*/
|
||||
@Internal
|
||||
internal suspend fun runPoetryWithSdk(sdk: Sdk, vararg args: String): PyResult<String> {
|
||||
val projectPath = sdk.associatedModulePath?.let { Path.of(it) }
|
||||
?: return PyResult.localizedError(poetryNotFoundException) // Choose a correct sdk
|
||||
val projectPath = sdk.pySdkAdditionalData.workingDirectory
|
||||
val pythonHomePath = sdk.pyRichSdkAsync().pythonHomePath
|
||||
?: return PyResult.localizedError(PySdkBundle.message("python.sdk.broken.configuration", sdk.name))
|
||||
val env = buildMap {
|
||||
|
||||
@@ -26,6 +26,7 @@ import com.jetbrains.python.sdk.add.v2.PathHolder
|
||||
import com.jetbrains.python.sdk.add.v2.TargetFileSystem
|
||||
import com.jetbrains.python.sdk.pySdkAdditionalData
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.pySdkAdditionalData
|
||||
import com.jetbrains.python.sdk.uv.impl.createUvCli
|
||||
import com.jetbrains.python.sdk.uv.impl.createUvLowLevel
|
||||
import com.jetbrains.python.sdk.uv.impl.getUvExecutable
|
||||
@@ -90,7 +91,7 @@ internal sealed interface UvExecutionContext<P : PathHolder> {
|
||||
|
||||
private suspend fun createEelUvExecutionContext(
|
||||
workingDir: Path,
|
||||
venvPathString: String?,
|
||||
pythonBinaryPath: String,
|
||||
uvPathString: String?,
|
||||
): UvExecutionContext.Eel {
|
||||
val eelApi = workingDir.getEelDescriptor().toEelApi()
|
||||
@@ -98,7 +99,7 @@ private suspend fun createEelUvExecutionContext(
|
||||
val uvPath = getUvExecutable(fileSystem, uvPathString)
|
||||
return UvExecutionContext.Eel(
|
||||
workingDir = workingDir,
|
||||
venvPath = venvPathString?.let { PathHolder.Eel(Path.of(it)) },
|
||||
venvPath = fileSystem.resolvePythonHome(PathHolder.Eel(Path.of(pythonBinaryPath))),
|
||||
fileSystem = fileSystem,
|
||||
uvPath = uvPath
|
||||
)
|
||||
@@ -106,7 +107,7 @@ private suspend fun createEelUvExecutionContext(
|
||||
|
||||
private suspend fun createTargetUvExecutionContext(
|
||||
workingDir: Path,
|
||||
venvPathString: FullPathOnTarget?,
|
||||
pythonBinaryPath: FullPathOnTarget,
|
||||
uvPathString: FullPathOnTarget?,
|
||||
targetConfig: TargetEnvironmentConfiguration,
|
||||
): UvExecutionContext.Target {
|
||||
@@ -114,7 +115,7 @@ private suspend fun createTargetUvExecutionContext(
|
||||
val uvPath = getUvExecutable(fileSystem, uvPathString)
|
||||
return UvExecutionContext.Target(
|
||||
workingDir = workingDir,
|
||||
venvPath = venvPathString?.let { PathHolder.Target(it) },
|
||||
venvPath = fileSystem.resolvePythonHome(PathHolder.Target(pythonBinaryPath)),
|
||||
fileSystem = fileSystem,
|
||||
uvPath = uvPath
|
||||
)
|
||||
@@ -122,23 +123,23 @@ private suspend fun createTargetUvExecutionContext(
|
||||
|
||||
internal fun Sdk.getUvExecutionContextAsync(scope: CoroutineScope, project: Project? = null): Deferred<UvExecutionContext<*>>? {
|
||||
val data = sdkAdditionalData
|
||||
val uvWorkingDirectory = uvFlavorData?.uvWorkingDirectory
|
||||
val venvPathString = uvFlavorData?.venvPath
|
||||
val uvWorkingDirectory = pySdkAdditionalData.workingDirectory.takeIf { pySdkAdditionalData.hasValidWorkingDirectory() }
|
||||
val uvPathString = uvFlavorData?.uvPath
|
||||
val pythonBinaryPath = homePath ?: return null
|
||||
|
||||
return when (data) {
|
||||
is UvSdkAdditionalData -> {
|
||||
val defaultWorkingDir = project?.basePath?.let { Path.of(it) }
|
||||
val cwd = uvWorkingDirectory ?: defaultWorkingDir ?: return null
|
||||
scope.async(start = CoroutineStart.LAZY) {
|
||||
createEelUvExecutionContext(cwd, venvPathString, uvPathString)
|
||||
createEelUvExecutionContext(cwd, pythonBinaryPath, uvPathString)
|
||||
}
|
||||
}
|
||||
is PyTargetAwareAdditionalData -> {
|
||||
val targetConfig = data.targetEnvironmentConfiguration ?: return null
|
||||
val cwd = uvWorkingDirectory ?: return null
|
||||
scope.async(start = CoroutineStart.LAZY) {
|
||||
createTargetUvExecutionContext(cwd, venvPathString, uvPathString, targetConfig)
|
||||
createTargetUvExecutionContext(cwd, pythonBinaryPath, uvPathString, targetConfig)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
@@ -223,7 +224,8 @@ internal suspend fun <P : PathHolder> setupExistingEnvAndSdk(
|
||||
fileSystem: FileSystem<P>,
|
||||
usePip: Boolean,
|
||||
): PyResult<Sdk> = withProgressText(PyBundle.message("python.sdk.progress.uv.configuring")) {
|
||||
val sdkAdditionalData = UvSdkAdditionalData(workingDir, usePip, fileSystem.resolvePythonHome(pythonBinary).toString(), uvPath.toString())
|
||||
val venvPath = fileSystem.resolvePythonHome(pythonBinary).toString()
|
||||
val sdkAdditionalData = UvSdkAdditionalData(workingDir, usePip, venvPath, uvPath.toString())
|
||||
val sdk = fileSystem.setupSdk(null, pythonBinary, sdkAdditionalData, null, null)
|
||||
sdk
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@ import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.Result
|
||||
import com.jetbrains.python.onFailure
|
||||
import com.jetbrains.python.run.AbstractPythonRunConfiguration
|
||||
import com.jetbrains.python.sdk.pySdkAdditionalData
|
||||
import com.jetbrains.python.sdk.associatedModulePath
|
||||
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
|
||||
import com.jetbrains.python.sdk.pythonSdk
|
||||
import com.jetbrains.python.sdk.uv.uvFlavorData
|
||||
import com.jetbrains.python.venvReader.tryResolvePath
|
||||
import org.jdom.Element
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
@@ -39,14 +39,14 @@ data class UvRunConfigurationOptions(
|
||||
var checkSync: Boolean = true,
|
||||
var uvSdkKey: String? = null,
|
||||
var uvArgs: List<String> = listOf(),
|
||||
var debugJustMyCode: Boolean = false
|
||||
var debugJustMyCode: Boolean = false,
|
||||
) {
|
||||
val uvSdk: Sdk?
|
||||
get() = uvSdkKey?.let { PythonSdkUtil.findSdkByKey(it)}
|
||||
get() = uvSdkKey?.let { PythonSdkUtil.findSdkByKey(it) }
|
||||
|
||||
val workingDirectory: Path?
|
||||
get() = uvSdk?.uvFlavorData?.uvWorkingDirectory
|
||||
?: tryResolvePath(uvSdk?.associatedModulePath)
|
||||
get() = uvSdk?.pySdkAdditionalData?.workingDirectory
|
||||
?: tryResolvePath(uvSdk?.associatedModulePath)
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
|
||||
@@ -66,10 +66,11 @@ internal class PyVenvSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
getVirtualEnv(venvsInModule)?.refreshAndFindVirtualFile()
|
||||
} ?: return PyResult.failure(MessageError(PyBundle.message("sdk.cannot.find.venv.for.module")))
|
||||
|
||||
val additionalData = createVenvAdditionalData(module).getOr { return it }
|
||||
val sdk = withContext(Dispatchers.IO) {
|
||||
createSdk(
|
||||
PathHolder.Eel(pythonBinary.toNioPath()),
|
||||
createVenvAdditionalData(),
|
||||
additionalData,
|
||||
null,
|
||||
)
|
||||
}.getOr { return it }
|
||||
|
||||
@@ -55,15 +55,17 @@ public final class PySdkTools {
|
||||
* @return sdk
|
||||
*/
|
||||
public static @NotNull Sdk createTempSdk(final @NotNull VirtualFile sdkHome,
|
||||
final @NotNull SdkCreationType sdkCreationType,
|
||||
final @Nullable Module module,
|
||||
@Nullable Disposable parentDisposable
|
||||
final @NotNull SdkCreationType sdkCreationType,
|
||||
final @Nullable Module module,
|
||||
@Nullable Disposable parentDisposable
|
||||
)
|
||||
throws InvalidSdkException {
|
||||
final Ref<Sdk> ref = Ref.create();
|
||||
ApplicationManager.getApplication().invokeAndWait(() -> {
|
||||
// sdkHome guarantees SDK name uniqueness. SdkUtil can't do that since no current SDK are provided.
|
||||
var additionalData = createVenvAdditionalData();
|
||||
var additionalData = module != null
|
||||
? createVenvAdditionalData(module).getSuccessOrNull()
|
||||
: createVenvAdditionalData(sdkHome.toNioPath().getParent());
|
||||
final Sdk sdk = SdkConfigurationUtil.setupSdk(NO_SDK, sdkHome, PythonSdkType.getInstance(), additionalData, sdkHome.getPath());
|
||||
Assert.assertNotNull("Failed to create SDK on " + sdkHome, sdk);
|
||||
|
||||
|
||||
Vendored
+7
-6
@@ -6,6 +6,7 @@ import com.intellij.codeInspection.ex.InspectionProfileImpl
|
||||
import com.intellij.lang.annotation.HighlightSeverity
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.application.edtWriteAction
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.modules
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||
@@ -53,17 +54,17 @@ import com.jetbrains.python.sdk.poetry.PyPoetrySdkAdditionalData
|
||||
import com.jetbrains.python.sdk.pythonSdk
|
||||
import com.jetbrains.python.sdk.setAssociationToModule
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Assertions.assertTrue
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.Timeout
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.nio.file.Path as NioPath
|
||||
import kotlin.io.path.ExperimentalPathApi
|
||||
import kotlin.io.path.copyToRecursively
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import java.nio.file.Path as NioPath
|
||||
|
||||
/**
|
||||
* Inspection tests for [com.jetbrains.python.inspections.dependencies.DependenciesPsiProvider] (migrated from JUnit3).
|
||||
@@ -221,13 +222,13 @@ internal class DependenciesPsiProviderTest {
|
||||
fun legacyPoetryDependencies() =
|
||||
genericProviderTest(
|
||||
requirementsProviderType = RequirementsProviderType.PYPROJECT_TOML,
|
||||
additionalData = PyPoetrySdkAdditionalData(null)
|
||||
additionalData = { module -> PyPoetrySdkAdditionalData(createVenvAdditionalData(module).getOrThrow().workingDirectory) }
|
||||
)
|
||||
|
||||
private fun runTest(
|
||||
requirementsProviderType: RequirementsProviderType,
|
||||
packageManagerProvider: TestPackageManagerProvider = TestPackageManagerProvider(),
|
||||
additionalData: PythonSdkAdditionalData = createVenvAdditionalData(),
|
||||
additionalData: (Module) -> PythonSdkAdditionalData = { createVenvAdditionalData(it).getOrThrow() },
|
||||
beforePackageManager: () -> Unit = {},
|
||||
body: suspend () -> Unit,
|
||||
) {
|
||||
@@ -243,7 +244,7 @@ internal class DependenciesPsiProviderTest {
|
||||
val module = moduleFixture.get()
|
||||
val venvDir = tempPathFixture.get().resolve(".venv")
|
||||
val venvPython = createVenv(env.pythonPath, venvDir).getOrThrow()
|
||||
createSdk(PathHolder.Eel(venvPython), additionalData)
|
||||
createSdk(PathHolder.Eel(venvPython), additionalData(module))
|
||||
.orThrow()
|
||||
.also {
|
||||
module.pythonSdk = it
|
||||
@@ -272,7 +273,7 @@ internal class DependenciesPsiProviderTest {
|
||||
|
||||
private fun genericProviderTest(
|
||||
requirementsProviderType: RequirementsProviderType,
|
||||
additionalData: PythonSdkAdditionalData = createVenvAdditionalData(),
|
||||
additionalData: (Module) -> PythonSdkAdditionalData = { createVenvAdditionalData(it).getOrThrow() },
|
||||
) =
|
||||
runTest(
|
||||
requirementsProviderType = requirementsProviderType,
|
||||
|
||||
+2
-1
@@ -25,7 +25,8 @@ class PyVenvCreationManuallyShowCaseTest {
|
||||
@Test
|
||||
fun createVenvTest(@PythonBinaryPath python: PythonBinary, @TempDir venvDir: Directory): Unit = timeoutRunBlocking(5.minutes) {
|
||||
val venvPython = createVenv(python, venvDir).getOrThrow()
|
||||
val sdk = createSdk(PathHolder.Eel(venvPython), createVenvAdditionalData()).getOrThrow()
|
||||
val additionalData = createVenvAdditionalData(venvDir.parent)
|
||||
val sdk = createSdk(PathHolder.Eel(venvPython), additionalData).getOrThrow()
|
||||
val flavorAndData = sdk.pySdkAdditionalData.flavorAndData
|
||||
assertTrue(flavorAndData.sdkSeemsValid(sdk, null),
|
||||
"Sdk not valid after creation")
|
||||
|
||||
Reference in New Issue
Block a user