mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-82119 Packaging: Rewrite Conda to use EEL
GitOrigin-RevId: 8329d24191bdc1411775dc8e58a65172a2804788
This commit is contained in:
committed by
intellij-monorepo-bot
parent
693c5014a8
commit
2c3a5a9f7a
+54
-54
@@ -1,7 +1,6 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.pycharm.community.ide.impl
|
||||
|
||||
import com.intellij.concurrency.SensitiveProgressWrapper
|
||||
import com.intellij.ide.trustedProjects.TrustedProjects
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
@@ -9,9 +8,6 @@ import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.diagnostic.debug
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.module.ModuleManager
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.Task
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
@@ -24,16 +20,23 @@ import com.intellij.openapi.util.UserDataHolderBase
|
||||
import com.intellij.openapi.util.use
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.platform.DirectoryProjectConfigurator
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.platform.util.progress.reportRawProgress
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PySdkBundle
|
||||
import com.jetbrains.python.orLogException
|
||||
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
|
||||
import com.jetbrains.python.sdk.*
|
||||
import com.jetbrains.python.sdk.conda.PyCondaSdkCustomizer
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration.setReadyToUseSdk
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration.setSdkUsingExtension
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfiguration.suppressTipAndInspectionsFor
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
/**
|
||||
* @see [PyConfigureSdkOnWslTest]
|
||||
@@ -51,9 +54,6 @@ class PythonSdkConfigurator : DirectoryProjectConfigurator {
|
||||
return ProjectRootManager.getInstance(ProjectManager.getInstance().defaultProject).projectSdk?.takeIf { it.sdkType is PythonSdkType }
|
||||
}
|
||||
|
||||
private fun <T> guardIndicator(indicator: ProgressIndicator, computable: () -> T): T {
|
||||
return ProgressManager.getInstance().runProcess(computable, SensitiveProgressWrapper(indicator))
|
||||
}
|
||||
}
|
||||
|
||||
override fun configureProject(project: Project, baseDir: VirtualFile, moduleRef: Ref<Module>, isProjectCreatedWithWizard: Boolean) {
|
||||
@@ -67,73 +67,73 @@ class PythonSdkConfigurator : DirectoryProjectConfigurator {
|
||||
}
|
||||
|
||||
val module = getModule(moduleRef, project) ?: return
|
||||
val extension = findExtension(module)
|
||||
val lifetime = extension?.let { suppressTipAndInspectionsFor(module, it) }
|
||||
|
||||
|
||||
StartupManager.getInstance(project).runWhenProjectIsInitialized {
|
||||
ProgressManager.getInstance().run(
|
||||
object : Task.Backgroundable(project, PySdkBundle.message("python.configuring.interpreter.progress"), extension == null) {
|
||||
override fun run(indicator: ProgressIndicator) = lifetime.use { configureSdk(project, module, extension, indicator) }
|
||||
PyPackageCoroutine.launch(project) {
|
||||
val extension = findExtension(module)
|
||||
val lifetime = extension?.let { suppressTipAndInspectionsFor(module, it) }
|
||||
|
||||
withBackgroundProgress(project, PySdkBundle.message("python.configuring.interpreter.progress"), extension == null) {
|
||||
lifetime.use { configureSdk(project, module, extension) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findExtension(module: Module): PyProjectSdkConfigurationExtension? {
|
||||
return if (!TrustedProjects.isProjectTrusted(module.project) || ApplicationManager.getApplication().isUnitTestMode) {
|
||||
private suspend fun findExtension(module: Module): PyProjectSdkConfigurationExtension? = withContext(Dispatchers.Default) {
|
||||
if (!TrustedProjects.isProjectTrusted(module.project) || ApplicationManager.getApplication().isUnitTestMode) {
|
||||
null
|
||||
}
|
||||
else PyProjectSdkConfigurationExtension.EP_NAME.findFirstSafe {
|
||||
else PyProjectSdkConfigurationExtension.EP_NAME.extensionsIfPointIsRegistered.firstOrNull {
|
||||
it.getIntention(module) != null && (!ApplicationManager.getApplication().isHeadlessEnvironment || it.supportsHeadlessModel())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: PythonInterpreterService: detect and validate system python
|
||||
@Internal
|
||||
fun configureSdk(
|
||||
@ApiStatus.Internal
|
||||
suspend fun configureSdk(
|
||||
project: Project,
|
||||
module: Module,
|
||||
extension: PyProjectSdkConfigurationExtension?,
|
||||
indicator: ProgressIndicator,
|
||||
) {
|
||||
): Unit = reportRawProgress { indicator ->
|
||||
// please keep this method in sync with com.jetbrains.python.inspections.PyInterpreterInspection.Visitor.getSuitableSdkFix
|
||||
|
||||
indicator.isIndeterminate = true
|
||||
indicator.fraction(null)
|
||||
|
||||
val context = UserDataHolderBase()
|
||||
|
||||
if (indicator.isCanceled) return
|
||||
if (!coroutineContext.isActive) return@reportRawProgress
|
||||
|
||||
indicator.text = PyBundle.message("looking.for.inner.venvs")
|
||||
indicator.text(PyBundle.message("looking.for.inner.venvs"))
|
||||
LOGGER.debug("Looking for inner virtual environments")
|
||||
guardIndicator(indicator) {
|
||||
detectAssociatedEnvironments(module, emptyList(), context).filter { it.isLocatedInsideModule(module) }.takeIf { it.isNotEmpty() }
|
||||
}?.let {
|
||||
detectAssociatedEnvironments(module, emptyList(), context).filter { it.isLocatedInsideModule(module) }.takeIf { it.isNotEmpty() }?.let {
|
||||
runInEdt { it.forEach { module.excludeInnerVirtualEnv(it) } }
|
||||
}
|
||||
|
||||
if (!TrustedProjects.isProjectTrusted(project)) {
|
||||
// com.jetbrains.python.inspections.PyInterpreterInspection will ask for confirmation
|
||||
LOGGER.info("Python interpreter has not been configured since project is not trusted")
|
||||
return
|
||||
return@reportRawProgress
|
||||
}
|
||||
|
||||
val existingSdks = ProjectSdksModel().apply { reset(project) }.sdks.filter { it.sdkType is PythonSdkType }
|
||||
|
||||
if (indicator.isCanceled) return
|
||||
if (!coroutineContext.isActive) return@reportRawProgress
|
||||
|
||||
indicator.text = PyBundle.message("looking.for.previous.interpreter")
|
||||
indicator.text(PyBundle.message("looking.for.previous.interpreter"))
|
||||
LOGGER.debug("Looking for the previously used interpreter")
|
||||
guardIndicator(indicator) { mostPreferred(filterAssociatedSdks(module, existingSdks)) }?.let {
|
||||
mostPreferred(filterAssociatedSdks(module, existingSdks))?.let {
|
||||
LOGGER.debug { "The previously used interpreter: $it" }
|
||||
setReadyToUseSdk(project, module, it)
|
||||
return
|
||||
return@reportRawProgress
|
||||
}
|
||||
|
||||
if (indicator.isCanceled) return
|
||||
if (!coroutineContext.isActive) return@reportRawProgress
|
||||
|
||||
indicator.text = PyBundle.message("looking.for.related.venv")
|
||||
indicator.text(PyBundle.message("looking.for.related.venv"))
|
||||
LOGGER.debug("Looking for a virtual environment related to the project")
|
||||
guardIndicator(indicator) { detectAssociatedEnvironments(module, existingSdks, context).firstOrNull() }?.let {
|
||||
detectAssociatedEnvironments(module, existingSdks, context).firstOrNull()?.let {
|
||||
LOGGER.debug { "Detected virtual environment related to the project: $it" }
|
||||
val newSdk = it.setupAssociated(existingSdks, module.basePath, true).orLogException(LOGGER) ?: return
|
||||
|
||||
@@ -144,52 +144,52 @@ fun configureSdk(
|
||||
setReadyToUseSdk(project, module, newSdk)
|
||||
}
|
||||
|
||||
return
|
||||
return@reportRawProgress
|
||||
}
|
||||
|
||||
if (indicator.isCanceled) return
|
||||
if (!coroutineContext.isActive) return@reportRawProgress
|
||||
|
||||
if (extension != null) {
|
||||
indicator.text = ""
|
||||
indicator.text("")
|
||||
setSdkUsingExtension(module, extension) { extension.createAndAddSdkForConfigurator(module) }
|
||||
return
|
||||
return@reportRawProgress
|
||||
}
|
||||
|
||||
if (indicator.isCanceled) return
|
||||
if (!coroutineContext.isActive) return@reportRawProgress
|
||||
|
||||
if (PyCondaSdkCustomizer.instance.suggestSharedCondaEnvironments) {
|
||||
indicator.text = PyBundle.message("looking.for.shared.conda.environment")
|
||||
guardIndicator(indicator) { mostPreferred(filterSharedCondaEnvs(module, existingSdks)) }?.let {
|
||||
indicator.text(PyBundle.message("looking.for.shared.conda.environment"))
|
||||
mostPreferred(filterSharedCondaEnvs(module, existingSdks))?.let {
|
||||
setReadyToUseSdk(project, module, it)
|
||||
return
|
||||
return@reportRawProgress
|
||||
}
|
||||
|
||||
if (indicator.isCanceled) return
|
||||
if (!coroutineContext.isActive) return@reportRawProgress
|
||||
}
|
||||
|
||||
indicator.text = PyBundle.message("looking.for.default.interpreter")
|
||||
indicator.text(PyBundle.message("looking.for.default.interpreter"))
|
||||
LOGGER.debug("Looking for the default interpreter setting for a new project")
|
||||
guardIndicator(indicator) { getDefaultProjectSdk() }?.let {
|
||||
getDefaultProjectSdk()?.let {
|
||||
LOGGER.debug { "Default interpreter setting for a new project: $it" }
|
||||
setReadyToUseSdk(project, module, it)
|
||||
return
|
||||
return@reportRawProgress
|
||||
}
|
||||
|
||||
if (indicator.isCanceled) return
|
||||
if (!coroutineContext.isActive) return@reportRawProgress
|
||||
|
||||
indicator.text = PyBundle.message("looking.for.previous.system.interpreter")
|
||||
indicator.text(PyBundle.message("looking.for.previous.system.interpreter"))
|
||||
LOGGER.debug("Looking for the previously used system-wide interpreter")
|
||||
guardIndicator(indicator) { mostPreferred(filterSystemWideSdks(existingSdks)) }?.let {
|
||||
mostPreferred(filterSystemWideSdks(existingSdks))?.let {
|
||||
LOGGER.debug { "Previously used system-wide interpreter: $it" }
|
||||
setReadyToUseSdk(project, module, it)
|
||||
return
|
||||
return@reportRawProgress
|
||||
}
|
||||
|
||||
if (indicator.isCanceled) return
|
||||
if (!coroutineContext.isActive) return@reportRawProgress
|
||||
|
||||
indicator.text = PyBundle.message("looking.for.system.interpreter")
|
||||
indicator.text(PyBundle.message("looking.for.system.interpreter"))
|
||||
LOGGER.debug("Looking for a system-wide interpreter")
|
||||
guardIndicator(indicator) { detectSystemWideSdks(module, existingSdks, context).firstOrNull() }?.let {
|
||||
detectSystemWideSdks(module, existingSdks, context).firstOrNull()?.let {
|
||||
LOGGER.debug { "Detected system-wide interpreter: $it" }
|
||||
runInEdt {
|
||||
SdkConfigurationUtil.createAndAddSDK(it.homePath!!, PythonSdkType.getInstance())?.apply {
|
||||
|
||||
+25
-26
@@ -2,7 +2,6 @@
|
||||
package com.intellij.pycharm.community.ide.impl.configuration
|
||||
|
||||
import com.intellij.codeInspection.util.IntentionName
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
@@ -31,18 +30,24 @@ import com.intellij.webcore.packaging.PackageManagementService
|
||||
import com.intellij.webcore.packaging.PackagesNotificationPanel
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.configuration.PyConfigurableInterpreterList
|
||||
import com.jetbrains.python.getOrNull
|
||||
import com.jetbrains.python.onFailure
|
||||
import com.jetbrains.python.packaging.conda.environmentYml.CondaEnvironmentYmlSdkUtils
|
||||
import com.jetbrains.python.pathValidation.PlatformAndRoot
|
||||
import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory
|
||||
import com.jetbrains.python.sdk.*
|
||||
import com.jetbrains.python.sdk.PythonSdkUpdater
|
||||
import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.conda.PyCondaSdkCustomizer
|
||||
import com.jetbrains.python.sdk.conda.createCondaSdkAlongWithNewEnv
|
||||
import com.jetbrains.python.sdk.conda.execution.CondaExecutor
|
||||
import com.jetbrains.python.sdk.conda.suggestCondaPath
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import com.jetbrains.python.sdk.findAmongRoots
|
||||
import com.jetbrains.python.sdk.flavors.conda.CondaEnvSdkFlavor
|
||||
import com.jetbrains.python.sdk.flavors.conda.NewCondaEnvRequest
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaCommand
|
||||
import com.jetbrains.python.sdk.flavors.listCondaEnvironments
|
||||
import com.jetbrains.python.sdk.setAssociationToModuleAsync
|
||||
import com.jetbrains.python.util.ShowingMessageErrorSync
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import java.awt.BorderLayout
|
||||
import java.nio.file.Path
|
||||
@@ -56,23 +61,20 @@ private val LOGGER = Logger.getInstance(PyEnvironmentYmlSdkConfiguration::class.
|
||||
* TODO: Support remote target (ie \\wsl)
|
||||
*/
|
||||
internal class PyEnvironmentYmlSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
@RequiresBackgroundThread
|
||||
override fun createAndAddSdkForConfigurator(module: Module): Sdk? = createAndAddSdk(module, Source.CONFIGURATOR)
|
||||
override suspend fun createAndAddSdkForConfigurator(module: Module): Sdk? = createAndAddSdk(module, Source.CONFIGURATOR)
|
||||
|
||||
override fun getIntention(module: Module): @IntentionName String? = getEnvironmentYml(module)?.let {
|
||||
override suspend fun getIntention(module: Module): @IntentionName String? = getEnvironmentYml(module)?.let {
|
||||
PyCharmCommunityCustomizationBundle.message("sdk.create.condaenv.suggestion")
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
override fun createAndAddSdkForInspection(module: Module): Sdk? = createAndAddSdk(module, Source.INSPECTION)
|
||||
override suspend fun createAndAddSdkForInspection(module: Module): Sdk? = createAndAddSdk(module, Source.INSPECTION)
|
||||
|
||||
private fun getEnvironmentYml(module: Module) = listOf(
|
||||
CondaEnvironmentYmlSdkUtils.ENV_YAML_FILE_NAME,
|
||||
CondaEnvironmentYmlSdkUtils.ENV_YML_FILE_NAME,
|
||||
).firstNotNullOfOrNull { findAmongRoots(module, it) }
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun createAndAddSdk(module: Module, source: Source): Sdk? {
|
||||
private suspend fun createAndAddSdk(module: Module, source: Source): Sdk? {
|
||||
val targetConfig = PythonInterpreterTargetEnvironmentFactory.getTargetModuleResidesOn(module)
|
||||
if (targetConfig != null) {
|
||||
// Remote targets aren't supported yet
|
||||
@@ -112,7 +114,7 @@ internal class PyEnvironmentYmlSdkConfiguration : PyProjectSdkConfigurationExten
|
||||
return if (permitted) envData else null
|
||||
}
|
||||
|
||||
private fun createAndAddCondaEnv(module: Module, condaExecutable: String, environmentYml: String): Sdk? {
|
||||
private suspend fun createAndAddCondaEnv(module: Module, condaExecutable: String, environmentYml: String): Sdk? {
|
||||
ProgressManager.progress(PyBundle.message("python.sdk.creating.conda.environment.sentence"))
|
||||
LOGGER.debug("Creating conda environment")
|
||||
|
||||
@@ -138,15 +140,13 @@ internal class PyEnvironmentYmlSdkConfiguration : PyProjectSdkConfigurationExten
|
||||
return if (condaExecutable.isNullOrBlank()) InputData.NOT_FILLED else InputData.SPECIFIED
|
||||
}
|
||||
|
||||
private fun createCondaEnv(project: Project, condaExecutable: String, environmentYml: String): Sdk? {
|
||||
private suspend fun createCondaEnv(project: Project, condaExecutable: String, environmentYml: String): Sdk? {
|
||||
val condaEnvironmentsBefore = safelyListCondaEnvironments(project, condaExecutable) ?: return null
|
||||
|
||||
val sdk = runBlockingCancellable {
|
||||
val existingSdks = PyConfigurableInterpreterList.getInstance(project).model.sdks
|
||||
val newCondaEnvInfo = NewCondaEnvRequest.LocalEnvByLocalEnvironmentFile(Path.of(environmentYml))
|
||||
PyCondaCommand(condaExecutable, null)
|
||||
.createCondaSdkAlongWithNewEnv(newCondaEnvInfo, Dispatchers.EDT, existingSdks.toList(), project)
|
||||
}.getOr {
|
||||
val existingSdks = PyConfigurableInterpreterList.getInstance(project).model.sdks
|
||||
val newCondaEnvInfo = NewCondaEnvRequest.LocalEnvByLocalEnvironmentFile(Path.of(environmentYml))
|
||||
val sdk = PyCondaCommand(condaExecutable, null)
|
||||
.createCondaSdkAlongWithNewEnv(newCondaEnvInfo, Dispatchers.EDT, existingSdks.toList(), project).getOr {
|
||||
PySdkConfigurationCollector.logCondaEnv(project, CondaEnvResult.CREATION_FAILURE)
|
||||
LOGGER.warn("Exception during creating conda environment $it")
|
||||
PackageManagementService.ErrorDescription.fromMessage(it.error.message)?.let { description ->
|
||||
@@ -183,15 +183,14 @@ internal class PyEnvironmentYmlSdkConfiguration : PyProjectSdkConfigurationExten
|
||||
}
|
||||
|
||||
private fun safelyListCondaEnvironments(project: Project, condaExecutable: String): List<String>? {
|
||||
return try {
|
||||
listCondaEnvironments(condaExecutable)
|
||||
}
|
||||
catch (e: ExecutionException) {
|
||||
PySdkConfigurationCollector.logCondaEnv(project, CondaEnvResult.LISTING_FAILURE)
|
||||
LOGGER.warn("Exception during listing conda environments", e)
|
||||
showSdkExecutionException(null, e, PyCharmCommunityCustomizationBundle.message("sdk.detect.condaenv.exception.dialog.title"))
|
||||
null
|
||||
val listEnvs = runBlockingCancellable {
|
||||
CondaExecutor.listEnvs(Path.of(condaExecutable)).onFailure {
|
||||
PySdkConfigurationCollector.logCondaEnv(project, CondaEnvResult.LISTING_FAILURE)
|
||||
it.addMessage(PyCharmCommunityCustomizationBundle.message("sdk.detect.condaenv.exception.dialog.title"))
|
||||
ShowingMessageErrorSync.emit(it)
|
||||
}
|
||||
}
|
||||
return listEnvs.getOrNull()?.envs?.toList()
|
||||
}
|
||||
|
||||
private class Dialog(module: Module, condaBinary: VirtualFile?, environmentYml: VirtualFile) : DialogWrapper(module.project, false,
|
||||
|
||||
+7
-10
@@ -5,11 +5,11 @@ import com.intellij.codeInspection.util.IntentionName
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.platform.util.progress.reportRawProgress
|
||||
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
|
||||
import com.intellij.python.hatch.HatchVirtualEnvironment
|
||||
import com.intellij.python.hatch.cli.HatchEnvironment
|
||||
import com.intellij.python.hatch.getHatchService
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.getOrLogException
|
||||
import com.jetbrains.python.hatch.sdk.createSdk
|
||||
import com.jetbrains.python.orLogException
|
||||
@@ -21,12 +21,10 @@ internal class PyHatchSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
private val LOGGER = Logger.getInstance(PyHatchSdkConfiguration::class.java)
|
||||
}
|
||||
|
||||
override fun getIntention(module: Module): @IntentionName String? {
|
||||
val isReadyAndHaveOwnership = runWithModalBlockingOrInBackground(
|
||||
project = module.project,
|
||||
msg = PyCharmCommunityCustomizationBundle.message("sdk.set.up.hatch.project.analysis")
|
||||
) {
|
||||
val hatchService = module.getHatchService().getOr { return@runWithModalBlockingOrInBackground false }
|
||||
override suspend fun getIntention(module: Module): @IntentionName String? {
|
||||
val isReadyAndHaveOwnership = reportRawProgress {
|
||||
it.text(PyCharmCommunityCustomizationBundle.message("sdk.set.up.hatch.project.analysis"))
|
||||
val hatchService = module.getHatchService().getOr { return@reportRawProgress false }
|
||||
hatchService.isHatchManagedProject().getOrLogException(LOGGER) == true
|
||||
}
|
||||
|
||||
@@ -49,10 +47,9 @@ internal class PyHatchSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
val sdk = hatchVenv.createSdk(hatchService.getWorkingDirectoryPath(), module).orLogException(LOGGER)
|
||||
sdk
|
||||
}
|
||||
@RequiresBackgroundThread
|
||||
override fun createAndAddSdkForConfigurator(module: Module): Sdk? = createSdk(module)
|
||||
override suspend fun createAndAddSdkForConfigurator(module: Module): Sdk? = createSdk(module)
|
||||
|
||||
override fun createAndAddSdkForInspection(module: Module): Sdk? = createSdk(module)
|
||||
override suspend fun createAndAddSdkForInspection(module: Module): Sdk? = createSdk(module)
|
||||
|
||||
override fun supportsHeadlessModel(): Boolean = true
|
||||
}
|
||||
+3
-8
@@ -6,7 +6,6 @@ import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.progress.runBlockingCancellable
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
|
||||
@@ -20,7 +19,6 @@ import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationC
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.Source
|
||||
import com.intellij.ui.IdeBorderFactory
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
@@ -46,14 +44,11 @@ internal class PyPipfileSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
|
||||
private val LOGGER = Logger.getInstance(PyPipfileSdkConfiguration::class.java)
|
||||
|
||||
@RequiresBackgroundThread
|
||||
override fun createAndAddSdkForConfigurator(module: Module): Sdk? = runBlockingCancellable { createAndAddSDk(module, Source.CONFIGURATOR) }
|
||||
override suspend fun createAndAddSdkForConfigurator(module: Module): Sdk? = createAndAddSDk(module, Source.CONFIGURATOR)
|
||||
|
||||
@RequiresBackgroundThread
|
||||
override fun getIntention(module: Module): @IntentionName String? = findAmongRoots(module, PIP_FILE)?.let { PyCharmCommunityCustomizationBundle.message("sdk.create.pipenv.suggestion", it.name) }
|
||||
override suspend fun getIntention(module: Module): @IntentionName String? = findAmongRoots(module, PIP_FILE)?.let { PyCharmCommunityCustomizationBundle.message("sdk.create.pipenv.suggestion", it.name) }
|
||||
|
||||
@RequiresBackgroundThread
|
||||
override fun createAndAddSdkForInspection(module: Module): Sdk? = runBlockingCancellable { createAndAddSDk(module, Source.INSPECTION) }
|
||||
override suspend fun createAndAddSdkForInspection(module: Module): Sdk? = createAndAddSDk(module, Source.INSPECTION)
|
||||
|
||||
private suspend fun createAndAddSDk(module: Module, source: Source): Sdk? {
|
||||
val pipEnvExecutable = askForEnvData(module, source) ?: return null
|
||||
|
||||
+19
-20
@@ -6,19 +6,19 @@ import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.progress.runBlockingCancellable
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
|
||||
import com.intellij.openapi.ui.DialogWrapper
|
||||
import com.intellij.openapi.ui.ValidationInfo
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.intellij.platform.util.progress.reportRawProgress
|
||||
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
|
||||
import com.intellij.python.community.impl.poetry.poetryPath
|
||||
import com.intellij.ui.IdeBorderFactory
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
@@ -30,7 +30,6 @@ import com.jetbrains.python.sdk.findAmongRoots
|
||||
import com.jetbrains.python.sdk.poetry.*
|
||||
import com.jetbrains.python.sdk.poetry.ui.PyAddNewPoetryFromFilePanel
|
||||
import com.jetbrains.python.sdk.setAssociationToModule
|
||||
import com.jetbrains.python.util.runWithModalBlockingOrInBackground
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
@@ -45,26 +44,26 @@ internal class PyPoetrySdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
private val LOGGER = Logger.getInstance(PyPoetrySdkConfiguration::class.java)
|
||||
}
|
||||
|
||||
override fun getIntention(module: Module): String? =
|
||||
runWithModalBlockingOrInBackground(module.project, PyBundle.message("python.sdk.validating.environment")) {
|
||||
val toml = findAmongRoots(module, PY_PROJECT_TOML)
|
||||
if (toml == null) {
|
||||
return@runWithModalBlockingOrInBackground null
|
||||
}
|
||||
|
||||
val isPoetry = getPyProjectTomlForPoetry(toml) != null
|
||||
if (!isPoetry) {
|
||||
return@runWithModalBlockingOrInBackground null
|
||||
}
|
||||
|
||||
return@runWithModalBlockingOrInBackground PyCharmCommunityCustomizationBundle.message("sdk.set.up.poetry.environment", toml.name)
|
||||
@NlsSafe
|
||||
override suspend fun getIntention(module: Module): String? = reportRawProgress {
|
||||
it.text(PyBundle.message("python.sdk.validating.environment"))
|
||||
val toml = findAmongRoots(module, PY_PROJECT_TOML)
|
||||
if (toml == null) {
|
||||
return@reportRawProgress null
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
override fun createAndAddSdkForConfigurator(module: Module): Sdk? = runBlockingCancellable { createAndAddSDk(module, false) }
|
||||
val isPoetry = getPyProjectTomlForPoetry(toml) != null
|
||||
if (!isPoetry) {
|
||||
return@reportRawProgress null
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
override fun createAndAddSdkForInspection(module: Module): Sdk? = runBlockingCancellable { createAndAddSDk(module, true) }
|
||||
return@reportRawProgress PyCharmCommunityCustomizationBundle.message("sdk.set.up.poetry.environment", toml.name)
|
||||
}
|
||||
|
||||
|
||||
override suspend fun createAndAddSdkForConfigurator(module: Module): Sdk? = createAndAddSDk(module, false)
|
||||
|
||||
override suspend fun createAndAddSdkForInspection(module: Module): Sdk? = createAndAddSDk(module, true)
|
||||
|
||||
override fun supportsHeadlessModel(): Boolean = true
|
||||
|
||||
|
||||
+4
-7
@@ -25,9 +25,9 @@ import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBund
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.InputData
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.Source
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.VirtualEnvResult
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.ui.PyAddNewVirtualEnvFromFilePanel
|
||||
import com.intellij.ui.IdeBorderFactory
|
||||
import com.intellij.ui.components.JBLabel
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PySdkBundle
|
||||
@@ -35,7 +35,6 @@ import com.jetbrains.python.packaging.PyPackageManager
|
||||
import com.jetbrains.python.packaging.PyPackageUtil
|
||||
import com.jetbrains.python.packaging.PyTargetEnvironmentPackageManager
|
||||
import com.jetbrains.python.requirements.RequirementsFileType
|
||||
import com.intellij.pycharm.community.ide.impl.configuration.ui.PyAddNewVirtualEnvFromFilePanel
|
||||
import com.jetbrains.python.sdk.basePath
|
||||
import com.jetbrains.python.sdk.configuration.PyProjectSdkConfigurationExtension
|
||||
import com.jetbrains.python.sdk.configuration.createVirtualEnvAndSdkSynchronously
|
||||
@@ -50,14 +49,12 @@ import kotlin.io.path.Path
|
||||
private val LOGGER = fileLogger()
|
||||
|
||||
class PyRequirementsTxtOrSetupPySdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
@RequiresBackgroundThread
|
||||
override fun createAndAddSdkForConfigurator(module: Module): Sdk? = createAndAddSdk(module, Source.CONFIGURATOR)
|
||||
override suspend fun createAndAddSdkForConfigurator(module: Module): Sdk? = createAndAddSdk(module, Source.CONFIGURATOR)
|
||||
|
||||
override fun getIntention(module: Module): @IntentionName String? =
|
||||
override suspend fun getIntention(module: Module): @IntentionName String? =
|
||||
getRequirementsTxtOrSetupPy(module)?.let { PyCharmCommunityCustomizationBundle.message("sdk.create.venv.suggestion", it.name) }
|
||||
|
||||
@RequiresBackgroundThread
|
||||
override fun createAndAddSdkForInspection(module: Module): Sdk? = createAndAddSdk(module, Source.INSPECTION)
|
||||
override suspend fun createAndAddSdkForInspection(module: Module): Sdk? = createAndAddSdk(module, Source.INSPECTION)
|
||||
|
||||
private fun createAndAddSdk(module: Module, source: Source): Sdk? {
|
||||
val existingSdks = ProjectJdkTable.getInstance().allJdks.asList()
|
||||
|
||||
+8
-19
@@ -5,14 +5,12 @@ import com.intellij.codeInspection.util.IntentionName
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.progress.runBlockingCancellable
|
||||
import com.intellij.openapi.projectRoots.ProjectJdkTable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
|
||||
import com.intellij.python.pyproject.PY_PROJECT_TOML
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.jetbrains.python.errorProcessing.MessageError
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.onSuccess
|
||||
@@ -31,30 +29,21 @@ class PyUvSdkConfiguration : PyProjectSdkConfigurationExtension {
|
||||
private val LOGGER = Logger.getInstance(PyUvSdkConfiguration::class.java)
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
override fun getIntention(module: Module): @IntentionName String? {
|
||||
override suspend fun getIntention(module: Module): @IntentionName String? {
|
||||
return findAmongRoots(module, PY_PROJECT_TOML)?.let { toml ->
|
||||
getUvExecutable()?.let { PyCharmCommunityCustomizationBundle.message("sdk.set.up.uv.environment", toml.name) }
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
override fun createAndAddSdkForConfigurator(module: Module): Sdk? {
|
||||
return runBlockingCancellable {
|
||||
createUv(module).getOr {
|
||||
LOGGER.warn(it.error.message)
|
||||
return@runBlockingCancellable null
|
||||
}
|
||||
}
|
||||
override suspend fun createAndAddSdkForConfigurator(module: Module): Sdk? = createUv(module).getOr {
|
||||
LOGGER.warn(it.error.message)
|
||||
return null
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
override fun createAndAddSdkForInspection(module: Module): Sdk? {
|
||||
return runBlockingCancellable {
|
||||
createUv(module).getOr {
|
||||
LOGGER.warn(it.error.message)
|
||||
return@runBlockingCancellable null
|
||||
}
|
||||
override suspend fun createAndAddSdkForInspection(module: Module): Sdk? {
|
||||
return createUv(module).getOr {
|
||||
LOGGER.warn(it.error.message)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-3
@@ -1,7 +1,6 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.python.community.junit5Tests.framework.conda
|
||||
|
||||
import com.intellij.execution.processTools.getResultStdoutStr
|
||||
import com.jetbrains.python.getOrThrow
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.sdk.flavors.conda.NewCondaEnvRequest
|
||||
@@ -20,11 +19,10 @@ suspend fun createCondaEnv(
|
||||
existingEnv: PyCondaEnv,
|
||||
pathToCreateNewEnvIn: Path,
|
||||
): PyCondaEnv {
|
||||
val process = PyCondaEnv.createEnv(
|
||||
PyCondaEnv.createEnv(
|
||||
PyCondaCommand(existingEnv.fullCondaPathOnTarget, null),
|
||||
NewCondaEnvRequest.EmptyUnnamedEnv(LanguageLevel.PYTHON311, pathToCreateNewEnvIn.pathString)
|
||||
).getOrThrow()
|
||||
process.getResultStdoutStr().getOrThrow()
|
||||
|
||||
val env = PyCondaEnv(PyCondaEnvIdentity.UnnamedEnv(pathToCreateNewEnvIn.pathString, false), existingEnv.fullCondaPathOnTarget)
|
||||
return env
|
||||
|
||||
@@ -5,6 +5,10 @@ import com.intellij.openapi.diagnostic.Logger
|
||||
import com.jetbrains.python.Result.Failure
|
||||
import com.jetbrains.python.Result.Success
|
||||
import com.jetbrains.python.errorProcessing.MessageError
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.packaging.PyExecutionException
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.Nls
|
||||
|
||||
/**
|
||||
@@ -113,6 +117,23 @@ sealed class Result<out SUCC, out ERR> {
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UsagesOfObsoleteApi")
|
||||
@ApiStatus.Internal
|
||||
fun <T> pyRunCatching(body: () -> T): PyResult<T> {
|
||||
return try {
|
||||
PyResult.success(body())
|
||||
}
|
||||
catch (t: PyExecutionException) {
|
||||
PyResult.failure(t.pyError)
|
||||
}
|
||||
catch (t: CancellationException) {
|
||||
throw t
|
||||
}
|
||||
catch (t: IllegalArgumentException) {
|
||||
//Parse deserialization exceptions
|
||||
PyResult.localizedError(t.localizedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps success result to another one with same error
|
||||
|
||||
@@ -1192,6 +1192,7 @@ python.add.sdk.panel.name.system.interpreter=System interpreter
|
||||
python.add.sdk.panel.name.virtualenv.environment=Virtualenv environment
|
||||
|
||||
python.add.sdk.conda.executable.path.is.empty=Conda executable path is empty
|
||||
python.add.sdk.conda.executable.path.is.not.found=Conda executable is not found
|
||||
|
||||
python.requirements.version.label=Version in requirements:
|
||||
python.requirements.version.separator.no.version=Don't specify version
|
||||
|
||||
+15
-14
@@ -4,6 +4,7 @@ package com.jetbrains.python.sdk.configuration
|
||||
import com.intellij.codeInspection.util.IntentionName
|
||||
import com.intellij.openapi.extensions.ExtensionPointName
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.progress.runBlockingMaybeCancellable
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
@@ -23,8 +24,10 @@ interface PyProjectSdkConfigurationExtension {
|
||||
val EP_NAME = ExtensionPointName.create<PyProjectSdkConfigurationExtension>("Pythonid.projectSdkConfigurationExtension")
|
||||
|
||||
@JvmStatic
|
||||
fun findForModule(module: Module): Pair<@IntentionName String, PyProjectSdkConfigurationExtension>? =
|
||||
EP_NAME.computeSafeIfAny { ext -> ext.getIntention(module)?.let { Pair(it, ext) } }
|
||||
@RequiresBackgroundThread
|
||||
fun findForModule(module: Module): Pair<@IntentionName String, PyProjectSdkConfigurationExtension>? = runBlockingMaybeCancellable {
|
||||
EP_NAME.extensionsIfPointIsRegistered.firstNotNullOfOrNull { ext -> ext.getIntention(module)?.let { Pair(it, ext) } }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,8 +36,15 @@ interface PyProjectSdkConfigurationExtension {
|
||||
*
|
||||
* Rule of thumb is to explicitly ask a user if sdk creation is desired and allowed.
|
||||
*/
|
||||
@RequiresBackgroundThread
|
||||
fun createAndAddSdkForConfigurator(module: Module): Sdk?
|
||||
suspend fun createAndAddSdkForConfigurator(module: Module): Sdk?
|
||||
|
||||
/**
|
||||
* An implementation is responsible for interpreter setup and registration in IDE.
|
||||
* In case of failures `null` should be returned, the implementation is responsible for errors displaying.
|
||||
*
|
||||
* You're free here to create sdk immediately, without any user permission since quick fix is explicitly clicked.
|
||||
*/
|
||||
suspend fun createAndAddSdkForInspection(module: Module): Sdk?
|
||||
|
||||
/**
|
||||
* Called by sdk configurator and interpreter inspection
|
||||
@@ -51,16 +61,7 @@ interface PyProjectSdkConfigurationExtension {
|
||||
* Example: `Create a virtual environment using requirements.txt`.
|
||||
*/
|
||||
@IntentionName
|
||||
fun getIntention(module: Module): String?
|
||||
|
||||
/**
|
||||
* An implementation is responsible for interpreter setup and registration in IDE.
|
||||
* In case of failures `null` should be returned, the implementation is responsible for errors displaying.
|
||||
*
|
||||
* You're free here to create sdk immediately, without any user permission since quick fix is explicitly clicked.
|
||||
*/
|
||||
@RequiresBackgroundThread
|
||||
fun createAndAddSdkForInspection(module: Module): Sdk?
|
||||
suspend fun getIntention(module: Module): String?
|
||||
|
||||
/**
|
||||
* If headless supported implementation is responsible for interpreter setup and registration
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.jetbrains.python.sdk.flavors
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.intellij.execution.RunCanceledByUserException
|
||||
import com.intellij.execution.configurations.GeneralCommandLine
|
||||
import com.intellij.execution.process.CapturingProcessHandler
|
||||
import com.intellij.execution.process.ProcessOutput
|
||||
import com.intellij.openapi.progress.EmptyProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.jetbrains.python.PySdkBundle
|
||||
import com.jetbrains.python.packaging.IndicatedProcessOutputListener
|
||||
import com.jetbrains.python.packaging.PyCondaPackageService
|
||||
import com.jetbrains.python.packaging.PyExecutionException
|
||||
import com.jetbrains.python.packaging.getCondaBasePython
|
||||
import com.jetbrains.python.sdk.PySdkUtil
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
|
||||
@Deprecated("Use Sdk.configureBuilderToRunPythonOnTarget")
|
||||
@Throws(ExecutionException::class)
|
||||
fun runConda(condaExecutable: String, arguments: List<String>): ProcessOutput {
|
||||
return run(condaExecutable, arguments, readCondaEnv(condaExecutable))
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
|
||||
@Deprecated("Use Sdk.configureBuilderToRunPythonOnTarget")
|
||||
@Throws(ExecutionException::class)
|
||||
fun runConda(sdk: Sdk?, arguments: List<String>): ProcessOutput {
|
||||
val condaExecutable = findCondaExecutable(sdk)
|
||||
val environment = if (sdk != null) {
|
||||
PySdkUtil.activateVirtualEnv(sdk)
|
||||
}
|
||||
else {
|
||||
readCondaEnv(condaExecutable)
|
||||
}
|
||||
return run(condaExecutable, arguments, environment)
|
||||
}
|
||||
|
||||
|
||||
private fun run(executable: String, arguments: List<String>, env: Map<String, String>?): ProcessOutput {
|
||||
val commandLine = GeneralCommandLine(executable).withParameters(arguments).withEnvironment(env)
|
||||
val indicator = ProgressManager.getInstance().progressIndicator ?: EmptyProgressIndicator()
|
||||
val handler = CapturingProcessHandler(commandLine).apply {
|
||||
addProcessListener(IndicatedProcessOutputListener(indicator))
|
||||
}
|
||||
return handler.runProcessWithProgressIndicator(indicator).apply {
|
||||
if (isCancelled) throw RunCanceledByUserException()
|
||||
checkExitCode(executable, arguments)
|
||||
}
|
||||
}
|
||||
|
||||
private fun readCondaEnv(condaExecutable: String): Map<String, String>? {
|
||||
return getCondaBasePython(condaExecutable)?.let { PySdkUtil.activateVirtualEnv(it) }
|
||||
}
|
||||
|
||||
@Throws(ExecutionException::class)
|
||||
private fun findCondaExecutable(sdk: Sdk?): String {
|
||||
return PyCondaPackageService.getCondaExecutable(sdk?.homePath) ?: throw ExecutionException(
|
||||
PySdkBundle.message("python.sdk.flavor.cannot.find.conda"))
|
||||
}
|
||||
|
||||
@Throws(PyExecutionException::class)
|
||||
private fun ProcessOutput.checkExitCode(executable: String, arguments: List<String>) {
|
||||
if (exitCode != 0) {
|
||||
val message = if (StringUtil.isEmptyOrSpaces(stdout) && StringUtil.isEmptyOrSpaces(stderr))
|
||||
PySdkBundle.message("python.conda.permission.denied")
|
||||
else PySdkBundle.message("python.conda.non.zero.exit.code")
|
||||
throw PyExecutionException(message, executable, arguments, this)
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
|
||||
@Deprecated("Use PyCondaEnv")
|
||||
@Throws(ExecutionException::class, JsonSyntaxException::class)
|
||||
fun listCondaEnvironments(condaExecutable: String): List<String> {
|
||||
val output = runConda(condaExecutable, listOf("env", "list", "--json"))
|
||||
val text = output.stdout
|
||||
val envList = Gson().fromJson(text, CondaEnvironmentsList::class.java)
|
||||
return envList.envs
|
||||
}
|
||||
|
||||
private data class CondaEnvironmentsList(@SerializedName("envs") var envs: List<String>)
|
||||
+2
-4
@@ -1,7 +1,6 @@
|
||||
package com.intellij.python.community.testFramework.testEnv.conda
|
||||
|
||||
import com.intellij.execution.processTools.getResultStdout
|
||||
import com.intellij.execution.target.local.LocalTargetEnvironmentRequest
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.python.community.testFramework.testEnv.PythonType
|
||||
@@ -9,7 +8,6 @@ import com.jetbrains.python.PythonBinary
|
||||
import com.jetbrains.python.getOrThrow
|
||||
import com.jetbrains.python.packaging.PyCondaPackageService
|
||||
import com.jetbrains.python.packaging.findCondaExecutableRelativeToEnv
|
||||
import com.jetbrains.python.sdk.conda.TargetEnvironmentRequestCommandExecutor
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -52,8 +50,8 @@ data object TypeConda : PythonType<PyCondaEnv>("conda") {
|
||||
}
|
||||
|
||||
private suspend fun getCondaNames(condaPath: Path) =
|
||||
PyCondaEnv.getEnvs(TargetEnvironmentRequestCommandExecutor(LocalTargetEnvironmentRequest()),
|
||||
condaPath.toString()).getOrThrow()
|
||||
PyCondaEnv.getEnvs(
|
||||
condaPath.toString()).getOrThrow()
|
||||
.map { it.envIdentity.userReadableName }
|
||||
.toMutableSet()
|
||||
}
|
||||
@@ -138,7 +138,6 @@ public final class PyInterpreterInspection extends PyInspection {
|
||||
|
||||
if (fixData != null) {
|
||||
fixes.add(fixData.getQuickFix());
|
||||
// noinspection HardCodedStringLiteral
|
||||
registerProblemWithCommonFixes(node, fixData.getMessage(), module, sdk, fixes, pyCharm);
|
||||
return;
|
||||
}
|
||||
@@ -493,8 +492,7 @@ public final class PyInterpreterInspection extends PyInspection {
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PyProjectSdkConfiguration.INSTANCE.configureSdkUsingExtension(myModule, myExtension,
|
||||
() -> myExtension.createAndAddSdkForInspection(myModule));
|
||||
PyProjectSdkConfiguration.INSTANCE.configureSdkUsingExtension(myModule, myExtension);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
// 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.packaging.conda
|
||||
|
||||
import com.intellij.execution.ExecutionException
|
||||
import com.jetbrains.python.PySdkBundle
|
||||
import com.jetbrains.python.packaging.PyExecutionException
|
||||
import com.jetbrains.python.sdk.flavors.runConda
|
||||
import com.jetbrains.python.venvReader.VirtualEnvReader.Companion.Instance
|
||||
import java.io.File
|
||||
import java.nio.file.Path
|
||||
|
||||
object CondaEnvCreator {
|
||||
@Throws(ExecutionException::class)
|
||||
fun createVirtualEnv(
|
||||
condaExecutable: String?, destinationDir: String,
|
||||
version: String,
|
||||
): String {
|
||||
if (condaExecutable == null) {
|
||||
throw PyExecutionException(PySdkBundle.message("python.sdk.conda.dialog.cannot.find.conda"))
|
||||
}
|
||||
|
||||
val parameters = listOf("create", "-p", destinationDir, "-y", "python=" + version)
|
||||
|
||||
runConda(condaExecutable, parameters)
|
||||
val binary = Instance.findPythonInPythonRoot(Path.of(destinationDir))
|
||||
val binaryFallback = destinationDir + File.separator + "bin" + File.separator + "python"
|
||||
return binary?.toString() ?: binaryFallback
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ internal class CondaPackageCache : PythonPackageCache<String> {
|
||||
val targetConfig = sdk.targetEnvConfiguration
|
||||
val targetRequest = targetConfig?.createEnvironmentRequest(project) ?: LocalTargetEnvironmentRequest()
|
||||
val commandExecutor = TargetEnvironmentRequestCommandExecutor(targetRequest)
|
||||
val baseConda = PyCondaEnv.getEnvs(commandExecutor, pathOnTarget).getOrThrow()
|
||||
val baseConda = PyCondaEnv.getEnvs(pathOnTarget).getOrThrow()
|
||||
.first { it.envIdentity is PyCondaEnvIdentity.UnnamedEnv && it.envIdentity.isBase }
|
||||
|
||||
val helpersAware = PythonInterpreterTargetEnvironmentFactory.findPythonTargetInterpreter(sdk, project)
|
||||
|
||||
@@ -25,7 +25,7 @@ import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
|
||||
class CondaPackageManager(project: Project, sdk: Sdk) : PythonPackageManager(project, sdk) {
|
||||
private val condaPackageEngine = CondaPackageManagerEngine(project, sdk)
|
||||
private val condaPackageEngine = CondaPackageManagerEngine(sdk)
|
||||
private val condaRepositoryManger = CondaRepositoryManger(project, sdk)
|
||||
private val pipRepositoryManger = PipRepositoryManager(project)
|
||||
private val pipPackageEngine = PipPackageManagerEngine(project, sdk)
|
||||
@@ -33,12 +33,12 @@ class CondaPackageManager(project: Project, sdk: Sdk) : PythonPackageManager(pro
|
||||
override var repositoryManager: PythonRepositoryManager = CompositePythonRepositoryManager(project,
|
||||
listOf(condaRepositoryManger, pipRepositoryManger))
|
||||
|
||||
override fun getDependencyManager(): PythonDependenciesManager? {
|
||||
override fun getDependencyManager(): PythonDependenciesManager {
|
||||
return CondaEnvironmentYmlManager.getInstance(project, sdk)
|
||||
}
|
||||
|
||||
override suspend fun syncCommand(): PyResult<Unit> {
|
||||
val requirementsFile = getDependencyManager()?.getDependenciesFile()
|
||||
val requirementsFile = getDependencyManager().getDependenciesFile()
|
||||
?: return PyResult.localizedError(PyBundle.message("python.sdk.conda.requirements.file.not.found"))
|
||||
return updateEnv(requirementsFile)
|
||||
}
|
||||
|
||||
@@ -1,147 +1,62 @@
|
||||
// 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.packaging.conda
|
||||
|
||||
import com.intellij.execution.target.TargetProgressIndicator
|
||||
import com.intellij.execution.target.TargetedCommandLineBuilder
|
||||
import com.intellij.execution.target.local.LocalTargetEnvironmentRequest
|
||||
import com.intellij.execution.target.value.targetPath
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.toNioPathOrNull
|
||||
import com.intellij.platform.eel.provider.asEelPath
|
||||
import com.jetbrains.python.PyBundle.message
|
||||
import com.jetbrains.python.Result.Companion.success
|
||||
import com.jetbrains.python.errorProcessing.ExecError
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.errorProcessing.asExecutionFailed
|
||||
import com.jetbrains.python.errorProcessing.failure
|
||||
import com.jetbrains.python.packaging.PyExecutionException
|
||||
import com.jetbrains.python.packaging.common.PythonOutdatedPackage
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import com.jetbrains.python.packaging.common.PythonRepositoryPackageSpecification
|
||||
import com.jetbrains.python.packaging.management.PythonPackageInstallRequest
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManagerEngine
|
||||
import com.jetbrains.python.packaging.management.PythonPackageManagerRunner
|
||||
import com.jetbrains.python.sdk.conda.execution.CondaExecutor
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaFlavorData
|
||||
import com.jetbrains.python.sdk.getOrCreateAdditionalData
|
||||
import com.jetbrains.python.sdk.targetEnvConfiguration
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
|
||||
internal class CondaPackageManagerEngine(
|
||||
private val project: Project,
|
||||
private val sdk: Sdk,
|
||||
) : PythonPackageManagerEngine {
|
||||
internal class CondaPackageManagerEngine(private val sdk: Sdk) : PythonPackageManagerEngine {
|
||||
suspend fun updateFromEnvironmentFile(envFile: VirtualFile): PyResult<Unit> {
|
||||
return runConda("env", listOf("update", "--file", envFile.name, "--prune"),
|
||||
addCondaEnv = false, workingPath = envFile.parent?.toNioPathOrNull())
|
||||
.mapSuccess { }
|
||||
val env = getEnvData()
|
||||
return CondaExecutor.updateFromEnvironmentFile(env.condaPath, envFile.path)
|
||||
}
|
||||
|
||||
suspend fun exportToEnvironmentFile(): PyResult<String> {
|
||||
return runConda("export", listOf("--from-history"))
|
||||
val env = getEnvData()
|
||||
return CondaExecutor.exportEnvironmentFile(env.condaPath, env.envIdentity)
|
||||
}
|
||||
|
||||
override suspend fun loadOutdatedPackagesCommand(): PyResult<List<PythonOutdatedPackage>> {
|
||||
val jsonPyResult = runConda("update", listOf("--dry-run", "--all", "--json")
|
||||
).getOr { return it }
|
||||
|
||||
val parsed = withContext(Dispatchers.Default) {
|
||||
CondaParseUtils.parseOutdatedOutputs(jsonPyResult)
|
||||
}
|
||||
return PyResult.success(parsed)
|
||||
val env = getEnvData()
|
||||
return CondaExecutor.listOutdatedPackages(env.condaPath, env.envIdentity)
|
||||
}
|
||||
|
||||
override suspend fun installPackageCommand(installRequest: PythonPackageInstallRequest, options: List<String>): PyResult<Unit> {
|
||||
val installationArgs = installRequest.buildInstallationArguments().getOr { return it }
|
||||
val result = runConda("install", installationArgs + "-y" + options)
|
||||
return result.mapSuccess { }
|
||||
val env = getEnvData()
|
||||
return CondaExecutor.installPackages(env.condaPath, env.envIdentity, installationArgs, options)
|
||||
}
|
||||
|
||||
override suspend fun updatePackageCommand(vararg specifications: PythonRepositoryPackageSpecification): PyResult<Unit> {
|
||||
val packages = specifications.map { it.name }
|
||||
val result = runConda("install", packages + listOf("-y")
|
||||
)
|
||||
return result.mapSuccess { }
|
||||
val env = getEnvData()
|
||||
return CondaExecutor.installPackages(env.condaPath, env.envIdentity, packages, emptyList())
|
||||
}
|
||||
|
||||
override suspend fun uninstallPackageCommand(vararg pythonPackages: String): PyResult<Unit> {
|
||||
if (pythonPackages.isEmpty())
|
||||
return PyResult.success(Unit)
|
||||
|
||||
val result = runConda("uninstall", pythonPackages.toList() + listOf("-y")
|
||||
)
|
||||
return result.mapSuccess { }
|
||||
val env = getEnvData()
|
||||
return CondaExecutor.uninstallPackages(env.condaPath, env.envIdentity, pythonPackages.toList())
|
||||
}
|
||||
|
||||
override suspend fun loadPackagesCommand(): PyResult<List<PythonPackage>> {
|
||||
val result = runConda("list", emptyList())
|
||||
return result.mapSuccess {
|
||||
parseCondaPackageList(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseCondaPackageList(text: String): List<CondaPackage> {
|
||||
return text.lineSequence()
|
||||
.filterNot { it.startsWith("#") }
|
||||
.map { line -> line.split(listLineParser) }
|
||||
.filterNot { it.size < 2 }
|
||||
//TODO: fix
|
||||
.map { CondaPackage(it[0], it[1], editableMode = false, installedWithPip = (it.size >= 4 && it[3] == "pypi")) }
|
||||
.sortedWith(compareBy(CondaPackage::name))
|
||||
.toList()
|
||||
val env = getEnvData()
|
||||
return CondaExecutor.listPackages(env.condaPath, env.envIdentity)
|
||||
}
|
||||
|
||||
|
||||
private suspend fun runConda(
|
||||
operation: String, arguments: List<String>,
|
||||
addCondaEnv: Boolean = true,
|
||||
workingPath: Path? = null,
|
||||
): PyResult<String> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val targetConfig = sdk.targetEnvConfiguration
|
||||
val targetReq = targetConfig?.createEnvironmentRequest(project) ?: LocalTargetEnvironmentRequest()
|
||||
val commandLineBuilder = TargetedCommandLineBuilder(targetReq)
|
||||
val targetEnv = targetReq.prepareEnvironment(TargetProgressIndicator.EMPTY)
|
||||
val env = (sdk.getOrCreateAdditionalData().flavorAndData.data as PyCondaFlavorData).env
|
||||
|
||||
commandLineBuilder.setExePath(env.fullCondaPathOnTarget)
|
||||
commandLineBuilder.addParameter(operation)
|
||||
if (addCondaEnv) {
|
||||
env.addCondaEnvironmentToTargetBuilder(commandLineBuilder)
|
||||
}
|
||||
|
||||
arguments.forEach(commandLineBuilder::addParameter)
|
||||
|
||||
if (workingPath != null) {
|
||||
val workingDir = targetPath(workingPath)
|
||||
val appliedWorkingDir = workingDir.apply(targetEnv)
|
||||
commandLineBuilder.setWorkingDirectory(appliedWorkingDir)
|
||||
}
|
||||
|
||||
val targetedCommandLine = commandLineBuilder.build()
|
||||
val process = targetEnv.createProcess(targetedCommandLine)
|
||||
val commandLine = targetedCommandLine.collectCommandsSynchronously()
|
||||
val commandLineString = StringUtil.join(commandLine, " ")
|
||||
|
||||
val result = PythonPackageManagerRunner.runProcess(process, commandLineString)
|
||||
|
||||
result.checkSuccess(thisLogger())
|
||||
if (result.isTimeout) throw PyExecutionException(message("conda.packaging.exception.timeout"), operation, arguments, result)
|
||||
if (result.exitCode != 0) {
|
||||
val error = ExecError(Path(env.fullCondaPathOnTarget).asEelPath(), (listOf(operation) + arguments).toTypedArray(), result.asExecutionFailed())
|
||||
failure(error)
|
||||
}
|
||||
else {
|
||||
success(result.stdout)
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun getEnvData(): PyCondaEnv = (sdk.getOrCreateAdditionalData().flavorAndData.data as PyCondaFlavorData).env
|
||||
|
||||
private fun PythonPackageInstallRequest.buildInstallationArguments(): PyResult<List<String>> = when (this) {
|
||||
is PythonPackageInstallRequest.ByLocation -> PyResult.localizedError("CondaManager does not support installing from location uri")
|
||||
@@ -160,6 +75,6 @@ internal class CondaPackageManagerEngine(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val listLineParser = "\\s+".toRegex()
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,17 +6,18 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.platform.util.coroutines.childScope
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@Service(Service.Level.PROJECT)
|
||||
@ApiStatus.Internal
|
||||
internal class PyPackageCoroutine(val project: Project, val coroutineScope: CoroutineScope) {
|
||||
val ioScope = coroutineScope.childScope("Jupyter IO scope", context = Dispatchers.IO)
|
||||
class PyPackageCoroutine(val project: Project, val coroutineScope: CoroutineScope) {
|
||||
val ioScope: CoroutineScope = coroutineScope.childScope("Jupyter IO scope", context = Dispatchers.IO)
|
||||
|
||||
companion object {
|
||||
fun launch(project: Project, context: CoroutineContext = Dispatchers.Main, body: suspend CoroutineScope.() -> Unit) =
|
||||
fun launch(project: Project, context: CoroutineContext = Dispatchers.Main, body: suspend CoroutineScope.() -> Unit): Job =
|
||||
project.service<PyPackageCoroutine>().coroutineScope.launch(context, block = body)
|
||||
|
||||
fun getIoScope(project: Project): CoroutineScope = project.service<PyPackageCoroutine>().ioScope
|
||||
|
||||
@@ -26,10 +26,10 @@ import kotlin.time.Duration.Companion.minutes
|
||||
* @return A [Result] object containing the output of the command execution.
|
||||
*/
|
||||
@Internal
|
||||
suspend fun runExecutableWithProgress(executable: Path, workDir: Path?, timeout: Duration = 10.minutes, vararg args: String): PyExecResult<String> {
|
||||
suspend fun runExecutableWithProgress(executable: Path, workDir: Path?, timeout: Duration = 10.minutes, env: Map<String,String> = emptyMap(), vararg args: String): PyExecResult<String> {
|
||||
val ansiDecoder = AnsiEscapeDecoder()
|
||||
reportRawProgress { reporter ->
|
||||
return ExecService().execGetStdout(executable, args.toList(), ExecOptions(workingDirectory = workDir, timeout = timeout), procListener = {
|
||||
return ExecService().execGetStdout(executable, args.toList(), ExecOptions(workingDirectory = workDir, timeout = timeout, env = env), procListener = {
|
||||
when (it) {
|
||||
is ProcessEvent.ProcessStarted, is ProcessEvent.ProcessEnded -> Unit
|
||||
is ProcessEvent.ProcessOutput -> {
|
||||
|
||||
@@ -151,10 +151,9 @@ abstract class PythonAddInterpreterModel(
|
||||
* Returns error or `null` if no error
|
||||
*/
|
||||
suspend fun detectCondaEnvironments(): PyResult<Unit> = withContext(Dispatchers.IO) {
|
||||
val commandExecutor = targetEnvironmentConfiguration.toExecutor()
|
||||
val fullCondaPathOnTarget = state.condaExecutable.get()
|
||||
if (fullCondaPathOnTarget.isBlank()) return@withContext PyResult.localizedError(message("python.sdk.conda.no.exec"))
|
||||
val environments = PyCondaEnv.getEnvs(commandExecutor, fullCondaPathOnTarget).getOr { return@withContext it }
|
||||
val environments = PyCondaEnv.getEnvs(fullCondaPathOnTarget).getOr { return@withContext it }
|
||||
val baseConda = environments.find { env -> env.envIdentity.let { it is PyCondaEnvIdentity.UnnamedEnv && it.isBase } }
|
||||
|
||||
withContext(Dispatchers.EDT) {
|
||||
|
||||
@@ -127,12 +127,7 @@ suspend fun PyCondaCommand.createCondaSdkAlongWithNewEnv(
|
||||
project: Project,
|
||||
reporter: RawProgressReporter? = null,
|
||||
): PyResult<Sdk> {
|
||||
val process = PyCondaEnv.createEnv(this, newCondaEnvInfo).getOr { return it }
|
||||
|
||||
ProcessHandlerReader(process).runProcessAndGetError(uiContext, reporter)?.let {
|
||||
return PyResult.localizedError(it)
|
||||
}
|
||||
|
||||
PyCondaEnv.createEnv(this, newCondaEnvInfo).getOr { return it }
|
||||
val sdk = createCondaSdkFromExistingEnv(newCondaEnvInfo.toIdentity(), existingSdks, project)
|
||||
saveLocalPythonCondaPath(Path.of(this@createCondaSdkAlongWithNewEnv.fullCondaPathOnTarget))
|
||||
|
||||
|
||||
+23
-3
@@ -1,13 +1,31 @@
|
||||
// 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.packaging.conda
|
||||
package com.jetbrains.python.sdk.conda.execution
|
||||
|
||||
import com.jetbrains.python.packaging.common.PythonOutdatedPackage
|
||||
import com.jetbrains.python.packaging.conda.CondaPackage
|
||||
import com.jetbrains.python.sdk.conda.execution.models.CondaEnvInfo
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
internal object CondaParseUtils {
|
||||
internal object CondaExecutionParser {
|
||||
private val json = kotlinx.serialization.json.Json { ignoreUnknownKeys = true }
|
||||
fun parseCondaPackageList(text: String): List<CondaPackage> {
|
||||
return text.lineSequence()
|
||||
.filterNot { it.startsWith("#") }
|
||||
.map { line -> line.split(listLineParser) }
|
||||
.filterNot { it.size < 2 }
|
||||
//TODO: fix
|
||||
.map { CondaPackage(it[0], it[1], editableMode = false, installedWithPip = (it.size >= 4 && it[3] == "pypi")) }
|
||||
.sortedWith(compareBy(CondaPackage::name))
|
||||
.toList()
|
||||
}
|
||||
|
||||
|
||||
fun parseListEnvironmentsOutput(jsonContent: String): CondaEnvInfo {
|
||||
return json.decodeFromString<CondaEnvInfo>(jsonContent)
|
||||
}
|
||||
|
||||
fun parseOutdatedOutputs(jsonContent: String): List<PythonOutdatedPackage> {
|
||||
val json = kotlinx.serialization.json.Json { ignoreUnknownKeys = true }
|
||||
val condaOutput = json.decodeFromString<CondaOutput>(jsonContent)
|
||||
val prevVersions = condaOutput.actions.unlink.associateBy { it.name }
|
||||
val newVersions = condaOutput.actions.link.associateBy { it.name }
|
||||
@@ -33,4 +51,6 @@ internal object CondaParseUtils {
|
||||
|
||||
@Serializable
|
||||
private data class CondaPackageInfo(val name: String, val version: String)
|
||||
|
||||
private val listLineParser = "\\s+".toRegex()
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
// 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.conda.execution
|
||||
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.platform.eel.isWindows
|
||||
import com.intellij.platform.eel.provider.getEelDescriptor
|
||||
import com.intellij.util.EnvReader
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.packaging.common.PythonOutdatedPackage
|
||||
import com.jetbrains.python.packaging.common.PythonPackage
|
||||
import com.jetbrains.python.pyRunCatching
|
||||
import com.jetbrains.python.sdk.conda.execution.models.CondaEnvInfo
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity
|
||||
import com.jetbrains.python.sdk.runExecutableWithProgress
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.*
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
@ApiStatus.Internal
|
||||
object CondaExecutor {
|
||||
suspend fun createNamedEnv(condaPath: Path, envName: String, pythonVersion: String): PyResult<Unit> {
|
||||
val args = listOf("create", "-y", "-n", envName, "python=${pythonVersion}")
|
||||
return runConda(condaPath, args, null).mapSuccess { }
|
||||
}
|
||||
|
||||
suspend fun createUnnamedEnv(condaPath: Path, envPrefix: String, pythonVersion: String): PyResult<Unit> {
|
||||
val args = listOf("create", "-y", "-p", envPrefix, "python=${pythonVersion}")
|
||||
return runConda(condaPath, args, null).mapSuccess { }
|
||||
}
|
||||
|
||||
suspend fun createFileEnv(condaPath: Path, environmentYaml: Path): PyResult<Unit> {
|
||||
val args = listOf("env", "create", "-y", "-f", environmentYaml.pathString)
|
||||
return runConda(condaPath, args, null).mapSuccess { }
|
||||
}
|
||||
|
||||
suspend fun updateFromEnvironmentFile(condaPath: Path, envYmlPath: String): PyResult<Unit> {
|
||||
val args = listOf("env", "update", "--file", envYmlPath, "--prune")
|
||||
return runConda(condaPath, args, null).mapSuccess { }
|
||||
}
|
||||
|
||||
suspend fun listEnvs(condaPath: Path): PyResult<CondaEnvInfo> {
|
||||
val args = listOf("env", "list", "--json")
|
||||
val json = runConda(condaPath, args, null).getOr { return it }
|
||||
return pyRunCatching {
|
||||
CondaExecutionParser.parseListEnvironmentsOutput(json)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun exportEnvironmentFile(condaPath: Path, envIdentity: PyCondaEnvIdentity): PyResult<String> {
|
||||
return runConda(condaPath, listOf("export") + listOf("--from-history"), envIdentity)
|
||||
}
|
||||
|
||||
suspend fun listPackages(condaPath: Path, envIdentity: PyCondaEnvIdentity): PyResult<List<PythonPackage>> {
|
||||
return runConda(condaPath, listOf("list"), envIdentity).mapSuccess {
|
||||
CondaExecutionParser.parseCondaPackageList(it)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun installPackages(condaPath: Path, envIdentity: PyCondaEnvIdentity, packages: List<String>, options: List<String>): PyResult<Unit> {
|
||||
return runConda(condaPath, listOf("install") + packages + listOf("-y") + options, envIdentity).mapSuccess { }
|
||||
|
||||
}
|
||||
|
||||
suspend fun uninstallPackages(condaPath: Path, envIdentity: PyCondaEnvIdentity, packages: List<String>): PyResult<Unit> {
|
||||
return runConda(condaPath, listOf("uninstall") + packages + "-y", envIdentity).mapSuccess { }
|
||||
}
|
||||
|
||||
|
||||
suspend fun listOutdatedPackages(condaPath: Path, envIdentity: PyCondaEnvIdentity): PyResult<List<PythonOutdatedPackage>> {
|
||||
val jsonPyResult = runConda(condaPath, listOf("update", "--dry-run", "--all", "--json"), envIdentity).getOr {
|
||||
return it
|
||||
}
|
||||
return pyRunCatching {
|
||||
CondaExecutionParser.parseOutdatedOutputs(jsonPyResult)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun runConda(condaPath: Path, args: List<String>, condaEnvIdentity: PyCondaEnvIdentity?, timeout: Duration = 15.minutes): PyResult<String> {
|
||||
val condaEnv = when (condaEnvIdentity) {
|
||||
is PyCondaEnvIdentity.UnnamedEnv -> {
|
||||
if (condaEnvIdentity.isBase)
|
||||
listOf()
|
||||
else
|
||||
listOf("-p", condaEnvIdentity.envPath)
|
||||
}
|
||||
is PyCondaEnvIdentity.NamedEnv -> {
|
||||
listOf("-n", condaEnvIdentity.envName)
|
||||
}
|
||||
null -> emptyList()
|
||||
}
|
||||
|
||||
val envs = getFixedEnvs(condaPath).getOr {
|
||||
return it
|
||||
}
|
||||
|
||||
val runArgs = (condaEnv + args).toTypedArray()
|
||||
return runExecutableWithProgress(condaPath, null, timeout, env = envs, *runArgs)
|
||||
}
|
||||
|
||||
private fun getFixedEnvs(condaPath: Path): PyResult<Map<String, String>> {
|
||||
val osFamily = condaPath.getEelDescriptor().osFamily
|
||||
if (!osFamily.isWindows) return PyResult.success(emptyMap())
|
||||
if (!condaPath.exists()) {
|
||||
return PyResult.localizedError(PyBundle.message("python.add.sdk.conda.executable.path.is.not.found"))
|
||||
}
|
||||
val activateBat = condaPath.resolveSibling("activate.bat")
|
||||
if (!activateBat.isExecutable()) {
|
||||
thisLogger().warn("$activateBat doesn't exist or can't be read")
|
||||
return PyResult.success(emptyMap())
|
||||
}
|
||||
try {
|
||||
val envs = EnvReader().readBatEnv(activateBat, emptyList())
|
||||
val transformed = transformEnvVars(envs, condaPath)
|
||||
return PyResult.success(transformed)
|
||||
}
|
||||
catch (e: IOException) {
|
||||
thisLogger().warn("Can't read env vars", e)
|
||||
return PyResult.success(emptyMap())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Special fix for conda PATH.
|
||||
* conda itself runs base python under the hood. When run non-base env, only non-base env gets activated, so conda
|
||||
* first runs non-activated base python. It loads ``sitecustomize.py`` and it leads to mkl loading that throws warning
|
||||
* due to path issue (DLL not found).
|
||||
* We add this folder to conda path
|
||||
*/
|
||||
private fun transformEnvVars(envs: Map<String, String>, condaPathOnTarget: Path): Map<String, String> {
|
||||
val extraPath = condaPathOnTarget.parent?.parent?.resolve("Library")?.resolve("Bin")
|
||||
if (extraPath == null || !extraPath.exists() || !extraPath.isDirectory()) {
|
||||
thisLogger().warn("$extraPath doesn't exist")
|
||||
}
|
||||
thisLogger().info("Patching envs")
|
||||
return envs.map { (key, value) ->
|
||||
val fixedVal = if (key.equals("Path", ignoreCase = true) && extraPath != null) {
|
||||
value + ";" + extraPath.absolutePathString()
|
||||
}
|
||||
else value
|
||||
|
||||
key to fixedVal.toString()
|
||||
}.toMap()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// 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.conda.execution.models
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Internal
|
||||
@Serializable
|
||||
data class CondaEnvInfo(
|
||||
@SerialName("envs")
|
||||
val envs: List<String>,
|
||||
@SerialName("envs_dirs")
|
||||
val envsDirs: List<String>,
|
||||
@SerialName("conda_prefix")
|
||||
val condaPrefix: String,
|
||||
)
|
||||
@@ -10,21 +10,20 @@ import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.progress.Task.Backgroundable
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.isNotificationSilentMode
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.util.use
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import com.intellij.platform.ide.progress.withBackgroundProgress
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PySdkBundle
|
||||
import com.jetbrains.python.PythonPluginDisposable
|
||||
import com.jetbrains.python.inspections.PyInspectionExtension
|
||||
import com.jetbrains.python.inspections.requirement.RunningPackagingTasksListener
|
||||
import com.jetbrains.python.packaging.utils.PyPackageCoroutine
|
||||
import com.jetbrains.python.projectModel.uv.UvProjectModelService
|
||||
import com.jetbrains.python.psi.PyFile
|
||||
import com.jetbrains.python.sdk.PySdkPopupFactory
|
||||
@@ -35,21 +34,22 @@ object PyProjectSdkConfiguration {
|
||||
|
||||
private val LOGGER = Logger.getInstance(PyProjectSdkConfiguration::class.java)
|
||||
|
||||
fun configureSdkUsingExtension(module: Module, extension: PyProjectSdkConfigurationExtension, supplier: () -> Sdk?) {
|
||||
fun configureSdkUsingExtension(module: Module, extension: PyProjectSdkConfigurationExtension) {
|
||||
val lifetime = suppressTipAndInspectionsFor(module, extension)
|
||||
|
||||
ProgressManager.getInstance().run(
|
||||
object : Backgroundable(module.project, PySdkBundle.message("python.configuring.interpreter.progress"), false) {
|
||||
override fun run(indicator: ProgressIndicator) {
|
||||
indicator.isIndeterminate = true
|
||||
lifetime.use { setSdkUsingExtension(module, extension, supplier) }
|
||||
val project = module.project
|
||||
PyPackageCoroutine.launch(project) {
|
||||
withBackgroundProgress(project, PySdkBundle.message("python.configuring.interpreter.progress"), false) {
|
||||
lifetime.use {
|
||||
setSdkUsingExtension(module, extension) {
|
||||
extension.createAndAddSdkForInspection(module)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresBackgroundThread
|
||||
fun setSdkUsingExtension(module: Module, extension: PyProjectSdkConfigurationExtension, supplier: () -> Sdk?) {
|
||||
suspend fun setSdkUsingExtension(module: Module, extension: PyProjectSdkConfigurationExtension, supplier: suspend () -> Sdk?) {
|
||||
ProgressManager.progress("")
|
||||
LOGGER.debug("Configuring sdk with ${extension.javaClass.canonicalName} extension")
|
||||
|
||||
@@ -60,7 +60,7 @@ object PyProjectSdkConfiguration {
|
||||
val ws = UvProjectModelService.findWorkspace(module)
|
||||
if (ws != null) {
|
||||
for (wsModule in ws.members + ws.root) {
|
||||
setReadyToUseSdk(wsModule.project, wsModule, sdk)
|
||||
setReadyToUseSdk(wsModule.project, wsModule, sdk)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -173,7 +173,7 @@ private class PyInterpreterInspectionSuppressor : PyInspectionExtension() {
|
||||
}
|
||||
}
|
||||
|
||||
private class PyPackageRequirementsInspectionSuppressor(module: Module): Disposable {
|
||||
private class PyPackageRequirementsInspectionSuppressor(module: Module) : Disposable {
|
||||
|
||||
private val listener = RunningPackagingTasksListener(module)
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import com.jetbrains.python.pathValidation.PlatformAndRoot.Companion.getPlatform
|
||||
import com.jetbrains.python.pathValidation.ValidationRequest
|
||||
import com.jetbrains.python.pathValidation.validateExecutableFile
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.Path
|
||||
|
||||
/**
|
||||
* Encapsulates conda binary command to simplify target request creation
|
||||
@@ -22,6 +24,7 @@ class PyCondaCommand(
|
||||
internal val project: Project? = null,
|
||||
internal val indicator: TargetProgressIndicator = TargetProgressIndicator.EMPTY
|
||||
) {
|
||||
fun getCondaPath(): Path = Path(fullCondaPathOnTarget)
|
||||
|
||||
@RequiresBackgroundThread
|
||||
private fun createRequest(): PyResult<TargetEnvironmentRequest> {
|
||||
|
||||
@@ -4,26 +4,24 @@ package com.jetbrains.python.sdk.flavors.conda
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode
|
||||
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
|
||||
import com.google.gson.Gson
|
||||
import com.intellij.execution.target.FullPathOnTarget
|
||||
import com.intellij.execution.target.TargetEnvironmentConfiguration
|
||||
import com.intellij.execution.target.TargetedCommandLineBuilder
|
||||
import com.intellij.execution.target.createProcessWithResult
|
||||
import com.intellij.openapi.projectRoots.Sdk
|
||||
import com.jetbrains.python.errorProcessing.PyResult
|
||||
import com.jetbrains.python.errorProcessing.asPythonResult
|
||||
import com.jetbrains.python.psi.LanguageLevel
|
||||
import com.jetbrains.python.sdk.conda.TargetCommandExecutor
|
||||
import com.jetbrains.python.sdk.conda.createCondaSdkFromExistingEnv
|
||||
import com.jetbrains.python.sdk.conda.execution.CondaExecutor
|
||||
import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv.Companion.getEnvs
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.future.await
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.name
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
/**
|
||||
* TODO: Once we get rid of [TargetCommandExecutor] and have access to [com.intellij.execution.target.TargetEnvironmentConfiguration] use it validate conda binary in [getEnvs]
|
||||
@@ -35,78 +33,54 @@ data class PyCondaEnv(
|
||||
val envIdentity: PyCondaEnvIdentity,
|
||||
val fullCondaPathOnTarget: FullPathOnTarget,
|
||||
) {
|
||||
|
||||
val condaPath: Path
|
||||
get() = Path(fullCondaPathOnTarget)
|
||||
|
||||
companion object {
|
||||
|
||||
|
||||
/**
|
||||
* @return unparsed output of conda info --envs --json
|
||||
*/
|
||||
private suspend fun getEnvsInfo(command: TargetCommandExecutor, fullCondaPathOnTarget: FullPathOnTarget): PyResult<String> {
|
||||
val output = command.execute(listOf(fullCondaPathOnTarget, "info", "--envs", "--json")).await()
|
||||
return if (output.exitCode == 0) PyResult.success(output.stdout) else PyResult.localizedError(output.stderr)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of conda's envs_dirs directories
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
suspend fun getEnvsDirs(
|
||||
command: TargetCommandExecutor,
|
||||
fullCondaPathOnTarget: FullPathOnTarget,
|
||||
): PyResult<Collection<String>> = withContext(Dispatchers.IO) {
|
||||
val json = getEnvsInfo(command, fullCondaPathOnTarget).getOr { return@withContext it }
|
||||
runCatching { // External command may return junk
|
||||
val info = Gson().fromJson(json, CondaInfoJson::class.java)
|
||||
info.envs_dirs
|
||||
}.asPythonResult()
|
||||
): PyResult<Collection<String>> {
|
||||
val info = CondaExecutor.listEnvs(Path(fullCondaPathOnTarget)).getOr { return it }
|
||||
return PyResult.success(info.envsDirs)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list of conda environments
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
suspend fun getEnvs(
|
||||
command: TargetCommandExecutor,
|
||||
fullCondaPathOnTarget: FullPathOnTarget,
|
||||
): PyResult<List<PyCondaEnv>> = withContext(Dispatchers.IO) {
|
||||
val json = getEnvsInfo(command, fullCondaPathOnTarget).getOr { return@withContext it }
|
||||
return@withContext kotlin.runCatching { // External command may return junk
|
||||
val info = Gson().fromJson(json, CondaInfoJson::class.java)
|
||||
val fileSeparator = command.targetPlatform.await().platform.fileSeparator
|
||||
info.envs.distinctBy { it.trim().lowercase(Locale.getDefault()) }.map { envPath ->
|
||||
// Env name is the basename for envs inside of default location
|
||||
// envPath should be direct child of envs_dirs to be a NamedEnv
|
||||
val envName = if (info.envs_dirs.any {
|
||||
if (command.local) Path.of(it) == Path.of(envPath).parent
|
||||
else envPath.startsWith(it)
|
||||
}) envPath.split(fileSeparator).last()
|
||||
else null
|
||||
val base = envPath.equals(info.conda_prefix, ignoreCase = true)
|
||||
PyCondaEnv(envName?.let { PyCondaEnvIdentity.NamedEnv(it) } ?: PyCondaEnvIdentity.UnnamedEnv(envPath, base),
|
||||
fullCondaPathOnTarget)
|
||||
|
||||
suspend fun getEnvs(condaPath: String): PyResult<List<PyCondaEnv>> {
|
||||
val info = CondaExecutor.listEnvs(Path(condaPath)).getOr { return it }
|
||||
val envs = info.envs.distinctBy { it.trim().lowercase(Locale.getDefault()) }
|
||||
val identities = envs.map { envPath ->
|
||||
// Env name is the basename for envs inside of default location
|
||||
// envPath should be direct child of envs_dirs to be a NamedEnv
|
||||
val isEnvName = info.envsDirs.any {
|
||||
Path.of(it) == Path.of(envPath).parent
|
||||
}
|
||||
}.asPythonResult()
|
||||
val envName = if (isEnvName)
|
||||
Path.of(envPath).name
|
||||
else
|
||||
null
|
||||
val base = envPath.equals(info.condaPrefix, ignoreCase = true)
|
||||
val identity = if (envName != null) {
|
||||
PyCondaEnvIdentity.NamedEnv(envName)
|
||||
}
|
||||
else {
|
||||
PyCondaEnvIdentity.UnnamedEnv(envPath, base)
|
||||
}
|
||||
PyCondaEnv(identity, condaPath)
|
||||
}
|
||||
|
||||
return PyResult.success(identities)
|
||||
}
|
||||
|
||||
suspend fun createEnv(command: PyCondaCommand, newCondaEnvInfo: NewCondaEnvRequest): PyResult<Process> {
|
||||
val (_, env, commandLineBuilder) = withContext(Dispatchers.IO) {
|
||||
command.createRequestEnvAndCommandLine()
|
||||
}.getOr { return it }
|
||||
|
||||
|
||||
val commandLine = commandLineBuilder.apply {
|
||||
//conda create -y -n myenv python=3.9
|
||||
//addParameters("create", "-y", "-n", envName, *newCondaEnvInfo.createEnvArguments)
|
||||
addParameters(*newCondaEnvInfo.createEnvArguments)
|
||||
}.build()
|
||||
|
||||
val processResult = env.createProcessWithResult(commandLine)
|
||||
return processResult.asPythonResult()
|
||||
suspend fun createEnv(command: PyCondaCommand, newCondaEnvInfo: NewCondaEnvRequest): PyResult<Unit> {
|
||||
return newCondaEnvInfo.create(command.getCondaPath())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
suspend fun createSdkFromThisEnv(targetConfig: TargetEnvironmentConfiguration?, existingSdk: List<Sdk>): Sdk =
|
||||
@@ -135,43 +109,10 @@ data class PyCondaEnv(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add conda prefix to [targetedCommandLineBuilder] without specifying the 'run' command
|
||||
*/
|
||||
fun addCondaEnvironmentToTargetBuilder(targetedCommandLineBuilder: TargetedCommandLineBuilder) {
|
||||
targetedCommandLineBuilder.apply {
|
||||
when (val identity = this@PyCondaEnv.envIdentity) {
|
||||
is PyCondaEnvIdentity.UnnamedEnv -> {
|
||||
addParameter("-p")
|
||||
addParameter(identity.envPath) // TODO: Escape. Shouldn't target have something like "addEscaped"?
|
||||
}
|
||||
is PyCondaEnvIdentity.NamedEnv -> {
|
||||
addParameter("-n")
|
||||
addParameter(identity.envName)
|
||||
}
|
||||
}
|
||||
targetedCommandLineBuilder.fixCondaPathEnvIfNeeded(fullCondaPathOnTarget)
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = "$envIdentity@$fullCondaPathOnTarget"
|
||||
}
|
||||
|
||||
|
||||
private class CondaInfoJson {
|
||||
|
||||
lateinit var envs: Collection<String>
|
||||
private set
|
||||
|
||||
@Suppress("PropertyName") // JSON conda format
|
||||
lateinit var envs_dirs: Collection<String>
|
||||
private set
|
||||
|
||||
@Suppress("PropertyName") // JSON conda format
|
||||
lateinit var conda_prefix: String
|
||||
private set
|
||||
}
|
||||
|
||||
/**
|
||||
* Request to create new conda environment.
|
||||
* Conda binary must be run with [createEnvArguments]
|
||||
@@ -179,22 +120,30 @@ private class CondaInfoJson {
|
||||
sealed class NewCondaEnvRequest {
|
||||
abstract val envName: @NonNls String
|
||||
|
||||
abstract val createEnvArguments: Array<String>
|
||||
@ApiStatus.Internal
|
||||
abstract suspend fun create(condaPath: Path): PyResult<Unit>
|
||||
|
||||
/**
|
||||
* Create empty environment with [langLevel]
|
||||
*/
|
||||
class EmptyNamedEnv(langLevel: LanguageLevel, @NonNls override val envName: String) : NewCondaEnvRequest() {
|
||||
override val createEnvArguments: Array<String> = arrayOf("create", "-y", "-n", envName, "python=${langLevel.toPythonVersion()}")
|
||||
class EmptyNamedEnv(private val langLevel: LanguageLevel, @get:NonNls override val envName: String) : NewCondaEnvRequest() {
|
||||
|
||||
@ApiStatus.Internal
|
||||
override suspend fun create(condaPath: Path): PyResult<Unit> {
|
||||
return CondaExecutor.createNamedEnv(condaPath, envName, langLevel.toPythonVersion())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create empty environment with [langlevel] in a specific directory
|
||||
* Create empty environment with [langLevel] in a specific directory
|
||||
*/
|
||||
class EmptyUnnamedEnv(langLevel: LanguageLevel, private val envPrefix: String) : NewCondaEnvRequest() {
|
||||
class EmptyUnnamedEnv(private val langLevel: LanguageLevel, private val envPrefix: String) : NewCondaEnvRequest() {
|
||||
override val envName: String get() = envPrefix
|
||||
|
||||
override val createEnvArguments: Array<String> = arrayOf("create", "-y", "-p", envPrefix, "python=${langLevel.toPythonVersion()}")
|
||||
@ApiStatus.Internal
|
||||
override suspend fun create(condaPath: Path): PyResult<Unit> {
|
||||
return CondaExecutor.createUnnamedEnv(condaPath, envPrefix, langLevel.toPythonVersion())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,10 +158,13 @@ sealed class NewCondaEnvRequest {
|
||||
}
|
||||
|
||||
private val lazyName = lazy {
|
||||
ObjectMapper(YAMLFactory()).readValue(environmentYaml.toFile(), ObjectNode::class.java).get("name").asText()
|
||||
ObjectMapper(YAMLFactory()).readValue(environmentYaml.pathString, ObjectNode::class.java).get("name").asText()
|
||||
}
|
||||
override val envName: String get() = lazyName.value
|
||||
|
||||
override val createEnvArguments: Array<String> = arrayOf("env", "create", "-y", "-f", environmentYaml.toFile().path)
|
||||
@ApiStatus.Internal
|
||||
override suspend fun create(condaPath: Path): PyResult<Unit> {
|
||||
return CondaExecutor.createFileEnv(condaPath, environmentYaml)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ import kotlin.time.Duration.Companion.minutes
|
||||
@Internal
|
||||
suspend fun runPipEnv(dirPath: Path?, vararg args: String): PyResult<String> {
|
||||
val executable = getPipEnvExecutable().getOr { return it }
|
||||
return runExecutableWithProgress(executable, dirPath, 10.minutes, *args)
|
||||
return runExecutableWithProgress(executable, dirPath, 10.minutes, args = args)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,7 +99,7 @@ suspend fun setupPipEnvSdkUnderProgress(
|
||||
setUpPipEnv(projectPath, python, installPackages)
|
||||
}.getOr { return it }
|
||||
|
||||
return createSdk(pythonExecutablePath, existingSdks, projectPath, suggestedSdkName(projectPath),PyPipEnvSdkAdditionalData())
|
||||
return createSdk(pythonExecutablePath, existingSdks, projectPath, suggestedSdkName(projectPath), PyPipEnvSdkAdditionalData())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,7 +55,7 @@ private val VERSION_2 = "2.0.0".toVersion()
|
||||
@Internal
|
||||
suspend fun runPoetry(projectPath: Path?, vararg args: String): PyResult<String> {
|
||||
val executable = getPoetryExecutable().getOr { return it }
|
||||
return runExecutableWithProgress(executable, projectPath, 10.minutes, *args)
|
||||
return runExecutableWithProgress(executable, projectPath, 10.minutes, args = args)
|
||||
}
|
||||
|
||||
|
||||
@@ -98,7 +98,8 @@ suspend fun validatePoetryExecutable(poetryExecutable: Path?): ValidationInfo? =
|
||||
*/
|
||||
@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.associatedModulePath?.let { Path.of(it) }
|
||||
?: return PyResult.localizedError(poetryNotFoundException) // Choose a correct sdk
|
||||
runPoetry(projectPath, "env", "use", sdk.homePath!!)
|
||||
return runPoetry(projectPath, *args)
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ private fun validateUvExecutable(uvPath: Path?): ValidationInfo? {
|
||||
}
|
||||
|
||||
private suspend fun runUv(uv: Path, workingDir: Path, vararg args: String): PyExecResult<String> {
|
||||
return runExecutableWithProgress(uv, workingDir, 10.minutes, *args)
|
||||
return runExecutableWithProgress(uv, workingDir, 10.minutes, args = args)
|
||||
}
|
||||
|
||||
private class UvCliImpl(val dispatcher: CoroutineDispatcher, uvPath: Path?) : UvCli {
|
||||
|
||||
@@ -147,7 +147,7 @@ class PyAddCondaPanelModel(val targetConfiguration: TargetEnvironmentConfigurati
|
||||
reporter: RawProgressReporter? = null): PyResult<List<PyCondaEnv>> = withContext(uiContext) {
|
||||
val path = condaPathTextBoxRwProp.get()
|
||||
reporter?.text(PyBundle.message("python.sdk.conda.getting.list.envs"))
|
||||
val envs = PyCondaEnv.getEnvs(targetCommandExecutor, path.trim())
|
||||
val envs = PyCondaEnv.getEnvs(path.trim())
|
||||
.onFailure {
|
||||
condaEnvModel.removeAllElements()
|
||||
showCondaActionsPanelRoProp.set(false)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
package com.jetbrains.env.python.conda
|
||||
|
||||
import com.intellij.execution.processTools.getResultStdoutStr
|
||||
import com.intellij.execution.processTools.mapFlat
|
||||
import com.intellij.execution.target.TargetProgressIndicator
|
||||
import com.intellij.execution.target.TargetedCommandLineBuilder
|
||||
import com.intellij.execution.target.local.LocalTargetEnvironmentRequest
|
||||
@@ -32,8 +31,7 @@ internal class CondaYamlFileRule(private val condaRule: LocalCondaRule,
|
||||
val command = PyCondaCommand(fullPathOnTarget, null, null)
|
||||
val condaEnvRequest = NewCondaEnvRequest.EmptyNamedEnv(languageLevel, envName)
|
||||
runBlocking {
|
||||
val process = PyCondaEnv.createEnv(command, condaEnvRequest).getOrThrow()
|
||||
Result.success(process).mapFlat { it.getResultStdoutStr() }.getOrThrow()
|
||||
PyCondaEnv.createEnv(command, condaEnvRequest).getOrThrow()
|
||||
}
|
||||
val targetReq = LocalTargetEnvironmentRequest()
|
||||
val builder = TargetedCommandLineBuilder(targetReq).apply {
|
||||
|
||||
@@ -102,7 +102,7 @@ class PyAddCondaPanelModelTest {
|
||||
|
||||
// Create env
|
||||
PyCondaEnv.createEnv(condaRule.condaCommand,
|
||||
NewCondaEnvRequest.EmptyNamedEnv(LanguageLevel.PYTHON38, name)).asKotlinResult().map { it.getResultStdoutStr() }.getOrThrow()
|
||||
NewCondaEnvRequest.EmptyNamedEnv(LanguageLevel.PYTHON38, name)).getOrThrow()
|
||||
|
||||
val model = PyAddCondaPanelModel(null, emptyList(), projectRule.project)
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ internal class PyCondaSdkTest {
|
||||
|
||||
private suspend fun createCondaEnv(): PyCondaEnv {
|
||||
val name = "condaForTests"
|
||||
PyCondaEnv.createEnv(condaRule.condaCommand, NewCondaEnvRequest.EmptyNamedEnv(LanguageLevel.PYTHON38, name)).getOrThrow().waitFor()
|
||||
PyCondaEnv.createEnv(condaRule.condaCommand, NewCondaEnvRequest.EmptyNamedEnv(LanguageLevel.PYTHON38, name)).getOrThrow()
|
||||
return PyCondaEnv(PyCondaEnvIdentity.NamedEnv(name), condaRule.condaPathOnTarget)
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ internal class PyCondaSdkTest {
|
||||
""".trimIndent())
|
||||
|
||||
Assume.assumeTrue("Windows only", SystemInfoRt.isWindows)
|
||||
val condaEnvs = PyCondaEnv.getEnvs(condaRule.commandExecutor, condaRule.condaPathOnTarget).getOrThrow()
|
||||
val condaEnvs = PyCondaEnv.getEnvs(condaRule.condaPathOnTarget).getOrThrow()
|
||||
val baseEnv = condaEnvs.first { (it.envIdentity as? PyCondaEnvIdentity.UnnamedEnv)?.isBase == true }
|
||||
val nonBaseEnv = condaEnvs.firstOrNull { it.envIdentity is PyCondaEnvIdentity.NamedEnv } ?: createCondaEnv()
|
||||
|
||||
@@ -116,7 +116,7 @@ internal class PyCondaSdkTest {
|
||||
|
||||
@Test
|
||||
fun testExecuteCommandOnSdk(): Unit = runTest(timeout = 20.seconds) {
|
||||
val condaEnv = PyCondaEnv.getEnvs(condaRule.commandExecutor, condaRule.condaPathOnTarget).getOrThrow().first()
|
||||
val condaEnv = PyCondaEnv.getEnvs(condaRule.condaPathOnTarget).getOrThrow().first()
|
||||
val sdk = condaRule.condaCommand.createCondaSdkFromExistingEnv(condaEnv.envIdentity, emptyList(), projectRule.project)
|
||||
val request = LocalTargetEnvironmentRequest()
|
||||
|
||||
@@ -139,7 +139,7 @@ internal class PyCondaSdkTest {
|
||||
|
||||
@Test
|
||||
fun testCreateFromExisting() = runTest {
|
||||
val env = PyCondaEnv.getEnvs(condaRule.commandExecutor, condaRule.condaPathOnTarget).getOrThrow().first()
|
||||
val env = PyCondaEnv.getEnvs(condaRule.condaPathOnTarget).getOrThrow().first()
|
||||
val sdk = condaRule.condaCommand.createCondaSdkFromExistingEnv(env.envIdentity, emptyList(), projectRule.project)
|
||||
Assert.assertEquals(sdk.getOrCreateAdditionalData().flavor, CondaEnvSdkFlavor.getInstance())
|
||||
Assert.assertTrue(env.toString(), getPythonVersion(sdk, LocalTargetEnvironmentRequest())?.isNotBlank() == true)
|
||||
|
||||
+10
-10
@@ -64,7 +64,7 @@ internal class PyCondaTest {
|
||||
|
||||
@Test
|
||||
fun testBasePython(): Unit = runTest {
|
||||
val baseConda = PyCondaEnv.getEnvs(condaRule.commandExecutor, condaRule.condaPathOnTarget).getOrThrow()
|
||||
val baseConda = PyCondaEnv.getEnvs(condaRule.condaPathOnTarget).getOrThrow()
|
||||
.first { (it.envIdentity as? PyCondaEnvIdentity.UnnamedEnv)?.isBase == true }
|
||||
val targetRequest = LocalTargetEnvironmentRequest()
|
||||
val commandLineBuilder = TargetedCommandLineBuilder(targetRequest)
|
||||
@@ -87,8 +87,8 @@ internal class PyCondaTest {
|
||||
@Test
|
||||
fun testCondaCreateByYaml() = runTest(timeout = 60.seconds) {
|
||||
PyCondaEnv.createEnv(condaRule.condaCommand,
|
||||
LocalEnvByLocalEnvironmentFile(yamlRule.yamlFilePath)).asKotlinResult().mapFlat { it.getResultStdoutStr() }.getOrThrow()
|
||||
val condaEnv = PyCondaEnv.getEnvs(condaRule.commandExecutor, condaRule.condaPathOnTarget)
|
||||
LocalEnvByLocalEnvironmentFile(yamlRule.yamlFilePath)).getOrThrow()
|
||||
val condaEnv = PyCondaEnv.getEnvs(condaRule.condaPathOnTarget)
|
||||
.getOrThrow().first { (it.envIdentity as? PyCondaEnvIdentity.NamedEnv)?.envName == yamlRule.envName }
|
||||
|
||||
// Python version contains word "Python", LanguageLevel doesn't expect it
|
||||
@@ -100,14 +100,14 @@ internal class PyCondaTest {
|
||||
fun testCondaCreateEnv(): Unit = runTest(timeout = 20.seconds) {
|
||||
val envName = "myNewEnvForTests"
|
||||
PyCondaEnv.createEnv(condaRule.condaCommand,
|
||||
EmptyNamedEnv(LanguageLevel.PYTHON39, envName)).asKotlinResult().mapFlat { it.getResultStdout() }
|
||||
PyCondaEnv.getEnvs(condaRule.commandExecutor, condaRule.condaPathOnTarget)
|
||||
EmptyNamedEnv(LanguageLevel.PYTHON39, envName)).getOrThrow()
|
||||
PyCondaEnv.getEnvs(condaRule.condaPathOnTarget)
|
||||
.getOrThrow().first { (it.envIdentity as? PyCondaEnvIdentity.NamedEnv)?.envName == envName }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCondaListEnvs(): Unit = runTest {
|
||||
val condaEnvs = PyCondaEnv.getEnvs(condaRule.commandExecutor, condaRule.condaPathOnTarget).getOrThrow()
|
||||
val condaEnvs = PyCondaEnv.getEnvs(condaRule.condaPathOnTarget).getOrThrow()
|
||||
Assert.assertTrue("No environments returned", condaEnvs.isNotEmpty())
|
||||
|
||||
var baseFound = false
|
||||
@@ -130,20 +130,20 @@ internal class PyCondaTest {
|
||||
|
||||
@Test
|
||||
fun testCondaListUnnamedEnvs(): Unit = runTest(timeout = 90.seconds) {
|
||||
val envsDirs = Path.of(PyCondaEnv.getEnvsDirs(condaRule.commandExecutor, condaRule.condaPathOnTarget).getOrThrow().first())
|
||||
val envsDirs = Path.of(PyCondaEnv.getEnvsDirs(condaRule.condaPathOnTarget).getOrThrow().first())
|
||||
val childDir = envsDirs.resolve("child")
|
||||
val childEnvPrefix = childDir.resolve("childEnv").toString()
|
||||
val siblingDir = envsDirs.resolveSibling("${envsDirs.fileName}Sibling")
|
||||
val siblingEnvPrefix = siblingDir.resolve("siblingEnv").toString()
|
||||
|
||||
PyCondaEnv.createEnv(condaRule.condaCommand,
|
||||
NewCondaEnvRequest.EmptyUnnamedEnv(LanguageLevel.PYTHON39, childEnvPrefix)).asKotlinResult().mapFlat { it.getResultStdout() }
|
||||
NewCondaEnvRequest.EmptyUnnamedEnv(LanguageLevel.PYTHON39, childEnvPrefix)).getOrThrow()
|
||||
PyCondaEnv.createEnv(condaRule.condaCommand,
|
||||
NewCondaEnvRequest.EmptyUnnamedEnv(LanguageLevel.PYTHON39, siblingEnvPrefix)).asKotlinResult().mapFlat { it.getResultStdout() }
|
||||
NewCondaEnvRequest.EmptyUnnamedEnv(LanguageLevel.PYTHON39, siblingEnvPrefix)).getOrThrow()
|
||||
|
||||
// Important to check that envIdentity is UnnamedEnv as this is testing to make sure that
|
||||
// getEnvs doesn't mistakenly return a NamedEnv for an environment that isn't a direct child of envsDirs
|
||||
val envs = PyCondaEnv.getEnvs(condaRule.commandExecutor, condaRule.condaPathOnTarget).getOrThrow()
|
||||
val envs = PyCondaEnv.getEnvs(condaRule.condaPathOnTarget).getOrThrow()
|
||||
.map { it.envIdentity }
|
||||
.filterIsInstance<PyCondaEnvIdentity.UnnamedEnv>()
|
||||
Assert.assertTrue("No child $childEnvPrefix in $envs", envs.any { it.envPath == childEnvPrefix })
|
||||
|
||||
Reference in New Issue
Block a user