diff --git a/python/src/com/jetbrains/python/packaging/conda/CondaPackageManagerEngine.kt b/python/src/com/jetbrains/python/packaging/conda/CondaPackageManagerEngine.kt index 1847b913ed92..6dfc0b8d6316 100644 --- a/python/src/com/jetbrains/python/packaging/conda/CondaPackageManagerEngine.kt +++ b/python/src/com/jetbrains/python/packaging/conda/CondaPackageManagerEngine.kt @@ -18,7 +18,7 @@ import com.jetbrains.python.sdk.getOrCreateAdditionalData internal class CondaPackageManagerEngine(private val sdk: Sdk) : PythonPackageManagerEngine { suspend fun updateFromEnvironmentFile(envFile: VirtualFile): PyResult { val env = getEnvData() - return CondaExecutor.updateFromEnvironmentFile(env.condaPath, envFile.path) + return CondaExecutor.updateFromEnvironmentFile(env.condaPath, envFile.path, env.envIdentity) } suspend fun exportToEnvironmentFile(): PyResult { @@ -74,8 +74,4 @@ internal class CondaPackageManagerEngine(private val sdk: Sdk) : PythonPackageMa PyResult.success(quoted) } } - - companion object { - - } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/conda/environmentYml/format/CondaEnvironmentYmlParser.kt b/python/src/com/jetbrains/python/packaging/conda/environmentYml/format/CondaEnvironmentYmlParser.kt index 42f01566b50d..349f4a61662e 100644 --- a/python/src/com/jetbrains/python/packaging/conda/environmentYml/format/CondaEnvironmentYmlParser.kt +++ b/python/src/com/jetbrains/python/packaging/conda/environmentYml/format/CondaEnvironmentYmlParser.kt @@ -15,6 +15,14 @@ import org.jetbrains.annotations.ApiStatus @ApiStatus.Internal object CondaEnvironmentYmlParser { + fun readNameFromFile(file: VirtualFile): String? { + val text = FileDocumentManager.getInstance().getDocument(file)?.text ?: return null + val yaml = Yaml(configuration = YamlConfiguration(strictMode = false)) + val environment: YamlMap = yaml.parseToYamlNode(text).yamlMap + + return environment.get("name")?.yamlScalar?.content + } + fun fromFile(file: VirtualFile): List? { val pyRequirements = runCatching { readDeps(file) }.onFailure { thisLogger().info("Cannot parse deps from ${file.readText()}", it) diff --git a/python/src/com/jetbrains/python/sdk/conda/PyAddCondaTools.kt b/python/src/com/jetbrains/python/sdk/conda/PyAddCondaTools.kt index 00e7a503d8ad..c10890784667 100644 --- a/python/src/com/jetbrains/python/sdk/conda/PyAddCondaTools.kt +++ b/python/src/com/jetbrains/python/sdk/conda/PyAddCondaTools.kt @@ -18,18 +18,20 @@ import com.intellij.openapi.projectRoots.ProjectJdkTable import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil import com.intellij.platform.util.progress.RawProgressReporter +import com.jetbrains.python.conda.loadLocalPythonCondaPath +import com.jetbrains.python.conda.saveLocalPythonCondaPath import com.jetbrains.python.errorProcessing.PyResult import com.jetbrains.python.errorProcessing.asPythonResult import com.jetbrains.python.getOrThrow +import com.jetbrains.python.onFailure import com.jetbrains.python.psi.LanguageLevel import com.jetbrains.python.sdk.PythonSdkAdditionalData import com.jetbrains.python.sdk.PythonSdkType -import com.jetbrains.python.conda.loadLocalPythonCondaPath -import com.jetbrains.python.conda.saveLocalPythonCondaPath import com.jetbrains.python.sdk.flavors.PyFlavorAndData import com.jetbrains.python.sdk.flavors.conda.* import com.jetbrains.python.sdk.getOrCreateAdditionalData import com.jetbrains.python.target.PyTargetAwareAdditionalData +import com.jetbrains.python.util.ShowingMessageErrorSync import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.future.await import kotlinx.coroutines.withContext @@ -48,8 +50,9 @@ internal val condaSupportedLanguages: List .asReversed() .filter { it < LanguageLevel.PYTHON313 } -val condaLatestSupportedLanguage: LanguageLevel @ApiStatus.Internal get() = - condaSupportedLanguages.maxWith(LanguageLevel.VERSION_COMPARATOR) +val condaLatestSupportedLanguage: LanguageLevel + @ApiStatus.Internal get() = + condaSupportedLanguages.maxWith(LanguageLevel.VERSION_COMPARATOR) /** * See [com.jetbrains.env.conda.PyCondaSdkTest] @@ -73,7 +76,9 @@ suspend fun PyCondaCommand.createCondaSdkFromExistingEnv( sdkModificator.sdkAdditionalData = additionalData // homePath is not required by conda, but used by lots of tools all over the code and required by CondaPathFix // Because homePath is not set yet, CondaPathFix does not work - sdkModificator.homePath = getCondaPythonBinaryPath(project, condaEnv, targetConfig).getOrThrow() + sdkModificator.homePath = getCondaPythonBinaryPath(project, condaEnv, targetConfig).onFailure { + ShowingMessageErrorSync.emit(it) + }.getOrThrow() edtWriteAction { sdkModificator.commitChanges() } diff --git a/python/src/com/jetbrains/python/sdk/conda/execution/CondaExecutionParser.kt b/python/src/com/jetbrains/python/sdk/conda/execution/CondaExecutionParser.kt index 403c65c348af..7294ba1cdb46 100644 --- a/python/src/com/jetbrains/python/sdk/conda/execution/CondaExecutionParser.kt +++ b/python/src/com/jetbrains/python/sdk/conda/execution/CondaExecutionParser.kt @@ -6,16 +6,22 @@ import com.jetbrains.python.packaging.conda.CondaPackage import com.jetbrains.python.sdk.conda.execution.models.CondaEnvInfo import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive internal object CondaExecutionParser { private val json = kotlinx.serialization.json.Json { ignoreUnknownKeys = true } fun parseCondaPackageList(text: String): List { - 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")) } + val parsed = json.parseToJsonElement(text).jsonArray + return parsed.map { + val jsonObject = it.jsonObject + val name = jsonObject["name"]?.jsonPrimitive?.content ?: "" + val version = jsonObject["version"]?.jsonPrimitive?.content ?: "" + val channel = jsonObject["channel"]?.jsonPrimitive?.content ?: "" + val isPypi = channel == "pypi" + CondaPackage(name, version, editableMode = false, installedWithPip = isPypi) + } .sortedWith(compareBy(CondaPackage::name)) .toList() } @@ -51,6 +57,4 @@ internal object CondaExecutionParser { @Serializable private data class CondaPackageInfo(val name: String, val version: String) - - private val listLineParser = "\\s+".toRegex() } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/conda/execution/CondaExecutor.kt b/python/src/com/jetbrains/python/sdk/conda/execution/CondaExecutor.kt index a4059c9e542a..6577bf1cc361 100644 --- a/python/src/com/jetbrains/python/sdk/conda/execution/CondaExecutor.kt +++ b/python/src/com/jetbrains/python/sdk/conda/execution/CondaExecutor.kt @@ -37,9 +37,9 @@ object CondaExecutor { return runConda(condaPath, args, null).mapSuccess { } } - suspend fun updateFromEnvironmentFile(condaPath: Path, envYmlPath: String): PyResult { + suspend fun updateFromEnvironmentFile(condaPath: Path, envYmlPath: String, envIdentity: PyCondaEnvIdentity): PyResult { val args = listOf("env", "update", "--file", envYmlPath, "--prune") - return runConda(condaPath, args, null).mapSuccess { } + return runConda(condaPath, args, envIdentity).mapSuccess { } } suspend fun listEnvs(condaPath: Path): PyResult { @@ -55,7 +55,7 @@ object CondaExecutor { } suspend fun listPackages(condaPath: Path, envIdentity: PyCondaEnvIdentity): PyResult> { - return runConda(condaPath, listOf("list"), envIdentity).mapSuccess { + return runConda(condaPath, listOf("list", "--json"), envIdentity).mapSuccess { CondaExecutionParser.parseCondaPackageList(it) } } @@ -97,7 +97,7 @@ object CondaExecutor { return it } - val runArgs = (condaEnv + args).toTypedArray() + val runArgs = (args + condaEnv).toTypedArray() return runExecutableWithProgress(condaPath, null, timeout, env = envs, *runArgs) } @@ -142,7 +142,7 @@ object CondaExecutor { } else value - key to fixedVal.toString() + key to fixedVal }.toMap() } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/flavors/conda/PyCondaEnv.kt b/python/src/com/jetbrains/python/sdk/flavors/conda/PyCondaEnv.kt index 16fb6fbd6629..fc16a8678448 100644 --- a/python/src/com/jetbrains/python/sdk/flavors/conda/PyCondaEnv.kt +++ b/python/src/com/jetbrains/python/sdk/flavors/conda/PyCondaEnv.kt @@ -1,14 +1,14 @@ // 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.flavors.conda -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.databind.node.ObjectNode -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory import com.intellij.execution.target.FullPathOnTarget import com.intellij.execution.target.TargetEnvironmentConfiguration import com.intellij.execution.target.TargetedCommandLineBuilder +import com.intellij.openapi.application.runReadAction import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.vfs.VirtualFileManager import com.jetbrains.python.errorProcessing.PyResult +import com.jetbrains.python.packaging.conda.environmentYml.format.CondaEnvironmentYmlParser import com.jetbrains.python.psi.LanguageLevel import com.jetbrains.python.sdk.conda.TargetCommandExecutor import com.jetbrains.python.sdk.conda.createCondaSdkFromExistingEnv @@ -21,10 +21,9 @@ 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] + * TODO: Once we get rid of [TargetCommandExecutor] and have access to [TargetEnvironmentConfiguration] use it validate conda binary in [getEnvs] * @see `PyCondaTest` */ @ApiStatus.Internal @@ -115,7 +114,6 @@ data class PyCondaEnv( /** * Request to create new conda environment. - * Conda binary must be run with [createEnvArguments] */ sealed class NewCondaEnvRequest { abstract val envName: @NonNls String @@ -157,14 +155,16 @@ sealed class NewCondaEnvRequest { assert(environmentYaml.exists()) { "$environmentYaml doesn't exist" } } - private val lazyName = lazy { - ObjectMapper(YAMLFactory()).readValue(environmentYaml.pathString, ObjectNode::class.java).get("name").asText() - } - override val envName: String get() = lazyName.value + override val envName: String = readEnvName() @ApiStatus.Internal override suspend fun create(condaPath: Path): PyResult { return CondaExecutor.createFileEnv(condaPath, environmentYaml) } + + private fun readEnvName(): String = runReadAction { + val virtualFile = VirtualFileManager.getInstance().findFileByNioPath(environmentYaml) ?: return@runReadAction "default" + CondaEnvironmentYmlParser.readNameFromFile(virtualFile) ?: "default" + } } } \ No newline at end of file