PY-91412 do not quote conda install specs

Specs go to conda as argv without a shell, so the stray leading double-quote
reached conda literally; conda 26.x rejects it with InvalidMatchSpec. Pass
specs verbatim and add unit + conda env-test coverage.


(cherry picked from commit efc80ccf64198ee0b925e18688663988d27d1890)

IJ-MR-216983

GitOrigin-RevId: 6fc61239d57bc5cdeb46fc22c78ac3fa79758855
This commit is contained in:
Vitaly Legchilkin
2026-08-05 12:25:20 +00:00
committed by intellij-monorepo-bot
parent dea3b12973
commit d2d75ac7bd
2 changed files with 41 additions and 14 deletions
@@ -35,7 +35,7 @@ internal class CondaPackageManagerEngine(private val sdk: Sdk) : PythonPackageMa
}
override suspend fun installPackageCommand(installRequest: PythonPackageInstallRequest, options: List<String>): PyResult<Unit> {
val installationArgs = installRequest.buildInstallationArguments().getOr { return it }
val installationArgs = installRequest.buildCondaInstallationArguments().getOr { return it }
val env = getEnvData()
return CondaExecutor.installPackages(sdk.getCondaBinToExecute(), env.envIdentity, installationArgs, options)
}
@@ -61,20 +61,18 @@ internal class CondaPackageManagerEngine(private val sdk: Sdk) : PythonPackageMa
private fun getEnvData(): PyCondaEnv = (sdk.pySdkAdditionalData.flavorAndData.data as PyCondaFlavorData).env
}
private fun PythonPackageInstallRequest.buildInstallationArguments(): PyResult<List<String>> = when (this) {
is PythonPackageInstallRequest.ByLocation -> PyResult.localizedError(PyBundle.message("python.packaging.conda.does.not.support.location.uri"))
is PythonPackageInstallRequest.ByRepositoryPythonPackageSpecifications -> {
val condaSpecs = specifications.filter { it.repository is CondaPackageRepository }
val specs = condaSpecs.map { it.nameWithVersionSpec }
internal fun PythonPackageInstallRequest.buildCondaInstallationArguments(): PyResult<List<String>> = when (this) {
is PythonPackageInstallRequest.ByLocation -> PyResult.localizedError(PyBundle.message("python.packaging.conda.does.not.support.location.uri"))
is PythonPackageInstallRequest.ByRepositoryPythonPackageSpecifications -> {
val condaSpecs = specifications.filter { it.repository is CondaPackageRepository }
val specs = condaSpecs.map { it.nameWithVersionSpec }
//https://docs.conda.io/projects/conda/en/latest/user-guide/concepts/pkg-specs.html#package-match-specifications
//When using the command line, put double quotes around any package version specification that
// contains the space character or any of the following characters: <, >, *, or |.
//Ido not know why we need put single prefix quota but it does not work in EEL with suffix quota
val quoted = specs.map { "\"$it" }
PyResult.success(quoted)
}
// Each spec is passed to conda as a single argv element (no shell), so it must not be quoted.
// conda's docs recommend double quotes only for shell command lines to protect characters like <, >, *, |.
// Wrapping the spec in a quote here glues a literal '"' onto the package name, which conda 26.x rejects
// with InvalidMatchSpec (PY-91412).
PyResult.success(specs)
}
}
@@ -0,0 +1,29 @@
// 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.testFramework.junit5.TestApplication
import com.jetbrains.python.getOrThrow
import com.jetbrains.python.packaging.common.PythonRepositoryPackageSpecification
import com.jetbrains.python.packaging.management.PythonPackageInstallRequest
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
@TestApplication
class CondaInstallationArgumentsTest {
// PY-91412: specs are passed to conda as individual argv elements (no shell), so they must reach conda verbatim.
// A stray leading double-quote used to be glued onto every spec, which conda 26.x rejects with InvalidMatchSpec.
@Test
fun testCondaSpecsAreNotQuoted() {
val specs = listOf(
PythonRepositoryPackageSpecification(CondaPackageRepository, "seaborn"),
PythonRepositoryPackageSpecification(CondaPackageRepository, "seaborn", "0.13.2"),
)
val request = PythonPackageInstallRequest.ByRepositoryPythonPackageSpecifications(specs)
val args = request.buildCondaInstallationArguments().getOrThrow()
assertEquals(specs.map { it.nameWithVersionSpec }, args)
assertTrue(args.none { it.contains('"') }, "Conda specs must not contain quote characters: $args")
}
}