PY-87232 Move SDK configurators away from ide customizations module

This was done for two reasons. To support these configurators in Python
plugin, not only PyCharm. And to make it easier to split tools by plugins.

GitOrigin-RevId: 9954c0c70be4e0d87405e88a0547ba4984db3dc1
This commit is contained in:
Alexey Katsman
2026-01-28 17:51:44 +01:00
committed by intellij-monorepo-bot
parent 199b3f0851
commit 71cb91d1b6
32 changed files with 163 additions and 285 deletions

View File

@@ -58,7 +58,6 @@ public final class PythonIcons {
/** 16x16 */ public static final @NotNull Icon PythonTests = load("icons/com/jetbrains/pythonCore/python/expui/pythonTests.svg", "icons/com/jetbrains/pythonCore/python/pythonTests.svg", 1252990498, 0);
/** 16x16 */ public static final @NotNull Icon ReferencedFile = load("icons/com/jetbrains/pythonCore/python/referencedFile.svg", 701028925, 2);
/** 16x16 */ public static final @NotNull Icon RemoteInterpreter = load("icons/com/jetbrains/pythonCore/python/expui/remoteInterpreter.svg", "icons/com/jetbrains/pythonCore/python/RemoteInterpreter.svg", 60369309, 0);
/** 16x16 */ public static final @NotNull Icon Virtualenv = load("icons/com/jetbrains/pythonCore/python/expui/virtualEnv.svg", "icons/com/jetbrains/pythonCore/python/virtualenv.svg", 758230626, 0);
/** @deprecated to be removed (use AllIcons.Debugger.AttacthToProcess) */
@SuppressWarnings("unused")

View File

@@ -25,7 +25,6 @@
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<python.common.toolToIconMapper implementation="com.intellij.pycharm.community.ide.impl.configuration.PyReqToolIdToIconMapper"/>
<projectService serviceInterface="com.intellij.psi.search.ProjectScopeBuilder"
serviceImplementation="com.intellij.pycharm.community.ide.impl.PyProjectScopeBuilder"
overrides="true"/>
@@ -140,33 +139,11 @@
<ignoredFileProvider implementation="com.intellij.pycharm.community.ide.impl.configuration.PyTemporarilyIgnoredFileProvider"/>
<statistics.counterUsagesCollector
implementationClass="com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector"/>
<codeInsight.codeVision.settings.defaults implementation="com.intellij.pycharm.community.ide.impl.PyCharmCodeVisionSettingsDefaults"/>
<startPagePromoter
implementation="com.intellij.pycharm.community.ide.impl.promo.WelcomeToUnifiedWelcomeScreenBanner"/>
</extensions>
<extensions defaultExtensionNs="Pythonid">
<projectSdkConfigurationExtension
implementation="com.intellij.pycharm.community.ide.impl.configuration.PyRequirementsTxtOrSetupPySdkConfiguration"
id="requirementsTxtOrSetupPy" order="before uv"/>
<projectSdkConfigurationExtension
implementation="com.intellij.pycharm.community.ide.impl.conda.PyEnvironmentYmlSdkConfiguration"
id="environmentYml"/>
<projectSdkConfigurationExtension implementation="com.intellij.pycharm.community.ide.impl.configuration.PyPipfileSdkConfiguration"
id="pipfile" order="before requirementsTxtOrSetupPy"/>
<projectSdkConfigurationExtension implementation="com.intellij.pycharm.community.ide.impl.configuration.PyPoetrySdkConfiguration"
id="poetry"/>"/>
<projectSdkConfigurationExtension implementation="com.intellij.pycharm.community.ide.impl.configuration.PyHatchSdkConfiguration"
id="hatch" order="after poetry"/>
<projectSdkConfigurationExtension implementation="com.intellij.pycharm.community.ide.impl.configuration.PyUvSdkConfiguration"
id="uv" order="before venv"/>
<projectSdkConfigurationExtension implementation="com.intellij.pycharm.community.ide.impl.configuration.PyVenvSdkConfiguration"
id="venv" order="last"/>
</extensions>
<applicationListeners>
<listener class="com.intellij.pycharm.community.ide.impl.welcomeScreen.PyWelcomeScreenNewUsersTracker"
topic="com.intellij.ide.AppLifecycleListener"/>

View File

@@ -34,31 +34,10 @@ feature.remoteSsh.sync=Synchronize code, data, and other project files, keeping
temporarily.ignored.file.provider.description=Temporarily ignored files
sdk.use.existing.venv=Use existing virtual environment {0}
sdk.create.venv.suggestion=Create a virtual environment using {0}
sdk.create.venv.suggestion.no.arg=Create a virtual environment
sdk.create.venv.permission=File {0} contains project dependencies. Would you like to create a virtual environment using it?
sdk.create.condaenv.suggestion=Create a conda environment using environment.yml
sdk.create.pipenv.suggestion=Create a pipenv environment using {0}
sdk.set.up.poetry.environment=Set up Poetry environment
sdk.progress.text.setting.up.poetry.environment=Setting up Poetry environment
notification.group.pro.advertiser=PyCharm recommended
sdk.could.not.find.valid.hatch.environment=Could not find a valid Hatch environment
sdk.set.up.hatch.environment=Set up Hatch 'default' environment
sdk.set.up.hatch.project.analysis=Hatch project analysis
sdk.set.up.uv.environment=Set up a uv {0} environment
sdk.cannot.use.existing.conda.environment=Cannot use existing Conda environment
sdk.remote.target.are.not.supported.for.conda.environment=Remote target are not supported for Conda environment
sdk.cannot.create.conda.environment.yml.not.found=Cannot create Conda environment, environment.yml does not exist
new.project.python.group.name=Python
new.project.other.group.name=Other

View File

@@ -1,13 +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.intellij.pycharm.community.ide.impl.configuration
import com.intellij.python.common.tools.ToolId
import com.intellij.python.common.tools.spi.ToolIdToIconMapper
import com.jetbrains.python.icons.PythonIcons
import javax.swing.Icon
internal class PyReqToolIdToIconMapper : ToolIdToIconMapper {
override val id: ToolId = PY_REQ_TOOL_ID
override val icon: Icon = PythonIcons.Python.Virtualenv
override val clazz: Class<*> = PythonIcons::class.java
}

View File

@@ -719,6 +719,9 @@
<registryKey defaultValue="true" description="Show value tooltip" key="python.debugger.show.value.tooltip"/>
<registryKey defaultValue="3" description="Timeout to guess the best suitable SDK when adding new interpreter (seconds)" key="python.guess.sdk.timeout.seconds"/>
<statistics.counterUsagesCollector
implementationClass="com.jetbrains.python.sdk.configuration.PySdkConfigurationCollector"/>
</extensions>
<extensionPoints>
@@ -961,6 +964,23 @@
<runConfigurationEditorExtension implementation="com.jetbrains.python.run.PyRunConfigurationTargetOptions"/>
<projectSdkConfigurationExtension
implementation="com.jetbrains.python.sdk.configuration.PyRequirementsTxtOrSetupPySdkConfiguration"
id="requirementsTxtOrSetupPy" order="before uv"/>
<projectSdkConfigurationExtension
implementation="com.jetbrains.python.sdk.configuration.PyEnvironmentYmlSdkConfiguration"
id="environmentYml"/>
<projectSdkConfigurationExtension implementation="com.jetbrains.python.sdk.configuration.PyPipfileSdkConfiguration"
id="pipfile" order="before requirementsTxtOrSetupPy"/>
<projectSdkConfigurationExtension implementation="com.jetbrains.python.sdk.configuration.PyPoetrySdkConfiguration"
id="poetry"/>"/>
<projectSdkConfigurationExtension implementation="com.jetbrains.python.sdk.configuration.PyHatchSdkConfiguration"
id="hatch" order="after poetry"/>
<projectSdkConfigurationExtension implementation="com.jetbrains.python.sdk.configuration.PyUvSdkConfiguration"
id="uv" order="before venv"/>
<projectSdkConfigurationExtension implementation="com.jetbrains.python.sdk.configuration.PyVenvSdkConfiguration"
id="venv" order="last"/>
</extensions>
<actions resource-bundle="messages.PyBundle">

View File

@@ -1792,3 +1792,23 @@ pycharm.statistics.feedback.ux.survey.title=Help us make PyCharm better
pycharm.statistics.feedback.ux.survey.content=Share your experience in a survey
progress.title.checking.test.framework=Looking for an Installed Test Framework
python.sdk.broken.configuration=Unsupported configuration detected in the Python SDK {0}, please recreate it.
sdk.create.condaenv.suggestion=Create a conda environment using environment.yml
sdk.remote.target.are.not.supported.for.conda.environment=Remote target are not supported for Conda environment
sdk.cannot.create.conda.environment.yml.not.found=Cannot create Conda environment, environment.yml does not exist
sdk.cannot.use.existing.conda.environment=Cannot use existing Conda environment
sdk.set.up.hatch.project.analysis=Hatch project analysis
sdk.set.up.hatch.environment=Set up Hatch 'default' environment
sdk.could.not.find.valid.hatch.environment=Could not find a valid Hatch environment
sdk.create.pipenv.suggestion=Create a pipenv environment using {0}
sdk.set.up.poetry.environment=Set up Poetry environment
sdk.progress.text.setting.up.poetry.environment=Setting up Poetry environment
sdk.create.venv.suggestion=Create a virtual environment using {0}
sdk.use.existing.venv=Use existing virtual environment {0}
sdk.create.venv.suggestion.no.arg=Create a virtual environment
sdk.set.up.uv.environment=Set up a uv {0} environment

View File

@@ -243,7 +243,7 @@ private suspend fun CreateSdkInfo.createAndSetToModule(module: Module) {
logger.trace { "Failed to create sdk for ${module.name} : ${r.error}" }
}
is Result.Success -> {
val sdk = r.result!! // It can't be null: this is an old buggy API that will be fixed soon
val sdk = r.result
module.pythonSdk = sdk
logger.trace { "SDK creation result for ${module.name} : $sdk" }
}

View File

@@ -31,6 +31,7 @@ jvm_library(
"//platform/core-api:core",
"//python/python-exec-service/execService.python",
"//platform/eel-provider",
"//python/common",
]
)

View File

@@ -29,5 +29,6 @@
<orderEntry type="module" module-name="intellij.platform.lang.impl" scope="TEST" />
<orderEntry type="module" module-name="intellij.platform.lang.core" scope="TEST" />
<orderEntry type="module" module-name="intellij.python.community.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.python.common" />
</component>
</module>

View File

@@ -0,0 +1,17 @@
{
"images": {
"intellij": {
"python": {
"community": {
"impl": {
"venv": {
"expui": {
"virtualEnv.svg": "images/intellij/python/community/impl/venv/virtualenv.svg"
}
}
}
}
}
}
}
}

View File

@@ -4,5 +4,11 @@
<module name="intellij.python.community.execService"/>
<module name="intellij.python.community.execService.python"/>
<module name="intellij.python.sdk"/>
<module name="intellij.python.common"/>
</dependencies>
<extensions defaultExtensionNs="com.intellij">
<iconMapper mappingFile="PythonVenvIconMappings.json"/>
<python.common.toolToIconMapper implementation="com.intellij.python.community.impl.venv.PyReqToolIdToIconMapper"/>
</extensions>
</idea-plugin>

View File

@@ -0,0 +1,13 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.community.impl.venv
import com.intellij.python.common.tools.ToolId
import com.intellij.python.common.tools.spi.ToolIdToIconMapper
import com.intellij.python.community.impl.venv.icons.PythonCommunityImplVenvIcons
import javax.swing.Icon
internal class PyReqToolIdToIconMapper : ToolIdToIconMapper {
override val id: ToolId = PY_REQ_TOOL_ID
override val icon: Icon = PythonCommunityImplVenvIcons.Virtualenv
override val clazz: Class<*> = PythonCommunityImplVenvIcons::class.java
}

View File

@@ -0,0 +1,19 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.community.impl.venv.icons;
import com.intellij.ui.IconManager;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
/**
* NOTE THIS FILE IS AUTO-GENERATED
* DO NOT EDIT IT BY HAND, run "Generate icon classes" configuration instead
*/
@org.jetbrains.annotations.ApiStatus.Internal
public final class PythonCommunityImplVenvIcons {
private static @NotNull Icon load(@NotNull String expUIPath, @NotNull String path, int cacheKey, int flags) {
return IconManager.getInstance().loadRasterizedIcon(path, expUIPath, PythonCommunityImplVenvIcons.class.getClassLoader(), cacheKey, flags);
}
/** 16x16 */ public static final @NotNull Icon Virtualenv = load("images/intellij/python/community/impl/venv/expui/virtualEnv.svg", "images/intellij/python/community/impl/venv/virtualenv.svg", 758230626, 0);
}

View File

@@ -0,0 +1,8 @@
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.community.impl.venv
import com.intellij.python.common.tools.ToolId
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Internal
val PY_REQ_TOOL_ID: ToolId = ToolId("requirements.txt")

View File

@@ -182,9 +182,16 @@ private class ToolDetectionService(project: Project, val coroutineScope: Corouti
module
)
private suspend fun detectBestToolForModule(module: Module): CreateSdkInfoWithTool? =
when (val i = module.getModuleInfo()) {
is ModuleCreateInfo.CreateSdkInfoWrapper -> CreateSdkInfoWithTool(i.createSdkInfo, i.toolId)
is ModuleCreateInfo.SameAs -> detectBestToolForModule(i.parentModule)
null -> null
}
private fun detectBestToolAsync(module: Module): CachedValueProvider.Result<Deferred<CreateSdkInfoWithTool?>> {
val result = coroutineScope.async {
(module.getModuleInfo() as? ModuleCreateInfo.CreateSdkInfoWrapper)?.let { CreateSdkInfoWithTool(it.createSdkInfo, it.toolId) }
detectBestToolForModule(module)
}
result.invokeOnCompletion { getOrCreateModificationTracker(module).incModificationCount() }
return CachedValueProvider.Result.create(result, getOrCreateModificationTracker(module))

View File

@@ -35,6 +35,7 @@ import com.intellij.python.community.impl.poetry.common.POETRY_TOOL_ID
import com.intellij.python.community.impl.poetry.common.icons.PythonCommunityImplPoetryCommonIcons
import com.intellij.python.community.impl.uv.common.UV_TOOL_ID
import com.intellij.python.community.impl.uv.common.icons.PythonCommunityImplUVCommonIcons
import com.intellij.python.community.impl.venv.icons.PythonCommunityImplVenvIcons
import com.intellij.python.hatch.icons.PythonHatchIcons
import com.intellij.python.hatch.impl.HATCH_TOOL_ID
import com.intellij.ui.dsl.builder.Align
@@ -166,7 +167,7 @@ enum class PythonSupportedEnvironmentManagers(
val icon: Icon,
val isFSSupported: (FileSystem<*>) -> Boolean = { (it as? FileSystem.Eel)?.eelApi == localEel },
) {
VIRTUALENV(VENV_TOOL_ID, "sdk.create.custom.virtualenv", PythonIcons.Python.Virtualenv, { true }),
VIRTUALENV(VENV_TOOL_ID, "sdk.create.custom.virtualenv", PythonCommunityImplVenvIcons.Virtualenv, { true }),
CONDA(CONDA_TOOL_ID, "sdk.create.custom.conda", PythonIcons.Python.Anaconda, { true }),
POETRY(POETRY_TOOL_ID, "sdk.create.custom.poetry", PythonCommunityImplPoetryCommonIcons.Poetry),
PIPENV(PIPENV_TOOL_ID, "sdk.create.custom.pipenv", PIPENV_ICON),

View File

@@ -1,5 +1,5 @@
// 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.pycharm.community.ide.impl.conda
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.sdk.configuration
import com.intellij.openapi.application.EDT
import com.intellij.openapi.diagnostic.thisLogger
@@ -12,10 +12,6 @@ import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.ide.progress.withBackgroundProgress
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.CondaEnvResult
import com.intellij.pycharm.community.ide.impl.findEnvOrNull
import com.intellij.python.common.tools.ToolId
import com.intellij.python.community.execService.BinOnEel
import com.intellij.util.FileName
@@ -39,7 +35,7 @@ import com.jetbrains.python.sdk.conda.createCondaSdkAlongWithNewEnv
import com.jetbrains.python.sdk.conda.createCondaSdkFromExistingEnv
import com.jetbrains.python.sdk.conda.execution.CondaExecutor
import com.jetbrains.python.sdk.conda.suggestCondaPath
import com.jetbrains.python.sdk.configuration.*
import com.jetbrains.python.sdk.configuration.PySdkConfigurationCollector.CondaEnvResult
import com.jetbrains.python.sdk.findAmongRoots
import com.jetbrains.python.sdk.flavors.conda.NewCondaEnvRequest
import com.jetbrains.python.sdk.flavors.conda.PyCondaCommand
@@ -76,7 +72,7 @@ internal class PyEnvironmentYmlSdkConfiguration : PyProjectSdkConfigurationExten
suggestCondaPath()?.let { LocalFileSystem.getInstance().findFileByPath(it) }
}
val canManage = condaPath != null
val intentionName = PyCharmCommunityCustomizationBundle.message("sdk.create.condaenv.suggestion")
val intentionName = PyBundle.message("sdk.create.condaenv.suggestion")
val envNotFound = EnvCheckerResult.EnvNotFound(intentionName)
when {
@@ -100,7 +96,7 @@ internal class PyEnvironmentYmlSdkConfiguration : PyProjectSdkConfigurationExten
val targetConfig = PythonInterpreterTargetEnvironmentFactory.getTargetModuleResidesOn(module)
if (targetConfig != null) {
// Remote targets aren't supported yet
return PyResult.localizedError(PyCharmCommunityCustomizationBundle.message("sdk.remote.target.are.not.supported.for.conda.environment"))
return PyResult.localizedError(PyBundle.message("sdk.remote.target.are.not.supported.for.conda.environment"))
}
// Again: only local conda is supported for now
@@ -121,13 +117,13 @@ internal class PyEnvironmentYmlSdkConfiguration : PyProjectSdkConfigurationExten
}
else {
val environmentYml = getEnvironmentYml(module)
?: return PyResult.localizedError(PyCharmCommunityCustomizationBundle.message("sdk.cannot.create.conda.environment.yml.not.found"))
?: return PyResult.localizedError(PyBundle.message("sdk.cannot.create.conda.environment.yml.not.found"))
createCondaEnv(module.project, condaExecutable, environmentYml).also {
PySdkConfigurationCollector.logCondaEnv(module.project, CondaEnvResult.CREATED)
}
}.getOr { return it }
val shared = PyCondaSdkCustomizer.instance.sharedEnvironmentsByDefault
val shared = PyCondaSdkCustomizer.Companion.instance.sharedEnvironmentsByDefault
val basePath = module.baseDir?.path
withContext(Dispatchers.EDT) {
@@ -148,7 +144,7 @@ internal class PyEnvironmentYmlSdkConfiguration : PyProjectSdkConfigurationExten
val project = module.project
return PyResult.success(PyCondaCommand(condaExecutable, null).createCondaSdkFromExistingEnv(
getCondaEnvIdentity(module, condaExecutable)
?: return PyResult.localizedError(PyCharmCommunityCustomizationBundle.message("sdk.cannot.use.existing.conda.environment")),
?: return PyResult.localizedError(PyBundle.message("sdk.cannot.use.existing.conda.environment")),
PyConfigurableInterpreterList.getInstance(project).model.sdks.toList(),
project
))

View File

@@ -1,23 +1,22 @@
// 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.pycharm.community.ide.impl.configuration
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.sdk.configuration
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.common.tools.ToolId
import com.intellij.python.hatch.HatchVirtualEnvironment
import com.intellij.python.hatch.PythonVirtualEnvironment
import com.intellij.python.hatch.cli.HatchEnvironment
import com.intellij.python.hatch.getHatchService
import com.intellij.python.hatch.impl.HATCH_TOOL_ID
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.hatch.sdk.createSdk
import com.jetbrains.python.onSuccess
import com.jetbrains.python.orLogException
import com.jetbrains.python.sdk.configuration.*
import com.jetbrains.python.sdk.service.PySdkService.Companion.pySdkService
import com.jetbrains.python.sdk.setAssociationToModule
import com.jetbrains.python.util.runWithModalBlockingOrInBackground
@@ -43,10 +42,10 @@ internal class PyHatchSdkConfiguration : PyProjectTomlConfigurationExtension {
private suspend fun checkManageableEnv(
module: Module, checkExistence: CheckExistence, checkToml: CheckToml,
): EnvCheckerResult = reportRawProgress {
it.text(PyCharmCommunityCustomizationBundle.message("sdk.set.up.hatch.project.analysis"))
it.text(PyBundle.message("sdk.set.up.hatch.project.analysis"))
val hatchService = module.getHatchService().getOr { return EnvCheckerResult.CannotConfigure }
val canManage = if (checkToml) hatchService.isHatchManagedProject() else true
val intentionName = PyCharmCommunityCustomizationBundle.message("sdk.set.up.hatch.environment")
val intentionName = PyBundle.message("sdk.set.up.hatch.environment")
val envNotFound = EnvCheckerResult.EnvNotFound(intentionName)
when {
@@ -70,7 +69,7 @@ internal class PyHatchSdkConfiguration : PyProjectTomlConfigurationExtension {
*/
private fun createSdk(module: Module, envExists: EnvExists): PyResult<Sdk> = runWithModalBlockingOrInBackground(
project = module.project,
msg = PyCharmCommunityCustomizationBundle.message("sdk.set.up.hatch.environment")
msg = PyBundle.message("sdk.set.up.hatch.environment")
) {
val hatchService = module.getHatchService().getOr { return@runWithModalBlockingOrInBackground it }
@@ -81,7 +80,7 @@ internal class PyHatchSdkConfiguration : PyProjectTomlConfigurationExtension {
when (defaultEnv) {
is PythonVirtualEnvironment.Existing -> defaultEnv
is PythonVirtualEnvironment.NotExisting, null -> return@runWithModalBlockingOrInBackground PyResult.localizedError(
PyCharmCommunityCustomizationBundle.message("sdk.could.not.find.valid.hatch.environment"))
PyBundle.message("sdk.could.not.find.valid.hatch.environment"))
}
}
else {

View File

@@ -1,5 +1,5 @@
// 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.pycharm.community.ide.impl.configuration
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.sdk.configuration
import com.intellij.ide.util.PropertiesComponent
import com.intellij.openapi.application.EDT
@@ -10,9 +10,6 @@ import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
import com.intellij.openapi.util.io.toNioPathOrNull
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.platform.ide.progress.withBackgroundProgress
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.PipEnvResult
import com.intellij.pycharm.community.ide.impl.findEnvOrNull
import com.intellij.python.common.tools.ToolId
import com.intellij.python.community.execService.ZeroCodeStdoutParserTransformer
import com.intellij.python.community.impl.pipenv.pipenvPath
@@ -21,7 +18,7 @@ import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.sdk.PythonSdkType
import com.jetbrains.python.sdk.baseDir
import com.jetbrains.python.sdk.configuration.*
import com.jetbrains.python.sdk.configuration.PySdkConfigurationCollector.PipEnvResult
import com.jetbrains.python.sdk.findAmongRoots
import com.jetbrains.python.sdk.impl.PySdkBundle
import com.jetbrains.python.sdk.impl.resolvePythonBinary
@@ -55,7 +52,7 @@ internal class PyPipfileSdkConfiguration : PyProjectSdkConfigurationExtension {
val pipfile = findAmongRoots(module, PipEnvFileHelper.PIP_FILE)?.name ?: return@withBackgroundProgress EnvCheckerResult.CannotConfigure
val pipEnvExecutable = getPipEnvExecutable() ?: return@withBackgroundProgress EnvCheckerResult.CannotConfigure
val canManage = pipEnvExecutable.isExecutable()
val intentionName = PyCharmCommunityCustomizationBundle.message("sdk.create.pipenv.suggestion", pipfile)
val intentionName = PyBundle.message("sdk.create.pipenv.suggestion", pipfile)
val envNotFound = EnvCheckerResult.EnvNotFound(intentionName)
when {

View File

@@ -1,5 +1,5 @@
// 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.pycharm.community.ide.impl.configuration
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.sdk.configuration
import com.intellij.openapi.application.EDT
import com.intellij.openapi.diagnostic.Logger
@@ -10,8 +10,6 @@ import com.intellij.openapi.util.io.toNioPathOrNull
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.pycharm.community.ide.impl.findEnvOrNull
import com.intellij.python.common.tools.ToolId
import com.intellij.python.community.impl.poetry.common.POETRY_TOOL_ID
import com.intellij.python.pyproject.PyProjectToml
@@ -22,7 +20,6 @@ import com.jetbrains.python.poetry.findPoetryLock
import com.jetbrains.python.poetry.getPyProjectTomlForPoetry
import com.jetbrains.python.sdk.PythonSdkType
import com.jetbrains.python.sdk.baseDir
import com.jetbrains.python.sdk.configuration.*
import com.jetbrains.python.sdk.impl.PySdkBundle
import com.jetbrains.python.sdk.impl.resolvePythonBinary
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
@@ -67,7 +64,7 @@ internal class PyPoetrySdkConfiguration : PyProjectTomlConfigurationExtension {
else true
val canManage = isPoetryProject && getPoetryExecutable() != null
val intentionName = PyCharmCommunityCustomizationBundle.message("sdk.set.up.poetry.environment")
val intentionName = PyBundle.message("sdk.set.up.poetry.environment")
val envNotFound = EnvCheckerResult.EnvNotFound(intentionName)
when {
@@ -85,7 +82,7 @@ internal class PyPoetrySdkConfiguration : PyProjectTomlConfigurationExtension {
}
private suspend fun createPoetry(module: Module): PyResult<Sdk> =
withBackgroundProgress(module.project, PyCharmCommunityCustomizationBundle.message("sdk.progress.text.setting.up.poetry.environment")) {
withBackgroundProgress(module.project, PyBundle.message("sdk.progress.text.setting.up.poetry.environment")) {
LOGGER.debug("Creating poetry environment")
val basePath = module.baseDir?.path?.let { Path.of(it) }

View File

@@ -50,7 +50,7 @@ object PyProjectSdkConfiguration {
val sdk = createSdkInfoWithTool.createSdkInfo.getSdkCreator(module).createSdk().getOr {
ShowingMessageErrorSync.emit(it.error, module.project)
return@withContext true
} ?: return@withContext false
}
setReadyToUseSdk(module.project, module, sdk)
thisLogger().debug("Successfully configured sdk using ${createSdkInfoWithTool.toolId}")

View File

@@ -3,121 +3,10 @@
package com.jetbrains.python.sdk.configuration
import com.intellij.execution.ExecutionException
import com.intellij.execution.target.TargetEnvironmentConfiguration
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.writeAction
import com.intellij.openapi.module.Module
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.modules
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.util.io.FileUtil
import com.intellij.platform.ide.progress.ModalTaskOwner
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
import com.intellij.remote.RemoteSdkException
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.jetbrains.python.Result
import com.jetbrains.python.packaging.PyPackageManagers
import com.jetbrains.python.packaging.PyTargetEnvCreationManager
import com.jetbrains.python.run.PythonInterpreterTargetEnvironmentFactory
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.flavors.PyFlavorAndData
import com.jetbrains.python.sdk.flavors.PyFlavorData
import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor
import com.jetbrains.python.sdk.impl.PySdkBundle
import com.jetbrains.python.target.PyTargetAwareAdditionalData
import com.jetbrains.python.target.getInterpreterVersion
import com.jetbrains.python.target.ui.TargetPanelExtension
import com.jetbrains.python.ui.pyModalBlocking
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jetbrains.annotations.ApiStatus
/**
* Use [com.jetbrains.python.projectCreation.createVenvAndSdk] unless you need the Targets API.
*
* If you need venv only, please use [com.intellij.python.community.impl.venv.createVenv]: it is cleaner and suspend.
*/
@ApiStatus.Internal
@RequiresEdt
fun createVirtualEnvAndSdkSynchronously(
baseSdk: Sdk,
existingSdks: List<Sdk>,
venvRoot: String,
projectBasePath: String?,
project: Project?,
module: Module?,
context: UserDataHolder = UserDataHolderBase(),
inheritSitePackages: Boolean = false,
makeShared: Boolean = false,
targetPanelExtension: TargetPanelExtension? = null,
): Sdk {
val targetEnvironmentConfiguration = baseSdk.targetEnvConfiguration
val installedSdk: Sdk = if (targetEnvironmentConfiguration == null) {
installSdkIfNeeded(baseSdk, module, existingSdks, context).getOrThrow()
}
else {
baseSdk
}
val projectPath = projectBasePath ?: module?.baseDir?.path ?: project?.basePath
val task = object : Task.WithResult<String, ExecutionException>(project, PySdkBundle.message("python.creating.venv.title"), false) {
override fun compute(indicator: ProgressIndicator): String {
indicator.isIndeterminate = true
val sdk = if (installedSdk is Disposable && Disposer.isDisposed(installedSdk)) {
ProjectJdkTable.getInstance().findJdk(installedSdk.name)!!
}
else {
installedSdk
}
try {
return PyTargetEnvCreationManager(sdk).createVirtualEnv(venvRoot, inheritSitePackages)
}
finally {
PyPackageManagers.getInstance().clearCache(sdk)
}
}
}
val associatedPath = if (!makeShared) projectPath else null
val venvSdk = targetEnvironmentConfiguration.let {
if (it == null) {
// here is the local machine case
createSdkByGenerateTask(task, existingSdks, installedSdk, associatedPath, null)
}
else {
val homePath = ProgressManager.getInstance().run(task)
runWithModalProgressBlocking(ModalTaskOwner.guess(), "...") {
createSdkForTarget(project, it, homePath, existingSdks, targetPanelExtension)
}
}
}
if (!makeShared) {
when {
module != null -> pyModalBlocking { venvSdk.setAssociationToModule(module) }
projectPath != null -> pyModalBlocking { venvSdk.setAssociationToPath(projectPath) }
}
}
project?.excludeInnerVirtualEnv(venvSdk)
if (targetEnvironmentConfiguration == null) {
// The method `onVirtualEnvCreated(..)` stores preferred base path to virtual envs. Storing here the base path from the non-local
// target (e.g. a path from SSH machine or a Docker image) ends up with a meaningless default for the local machine.
// If we would like to store preferred paths for non-local targets we need to use some key to identify the exact target.
PySdkSettings.instance.onVirtualEnvCreated(installedSdk.homePath, FileUtil.toSystemIndependentName(venvRoot), projectPath)
}
return venvSdk
}
import com.jetbrains.python.sdk.PyDetectedSdk
import com.jetbrains.python.sdk.PySdkSettings
fun findPreferredVirtualEnvBaseSdk(existingBaseSdks: List<Sdk>): Sdk? {
val preferredSdkPath = PySdkSettings.instance.preferredVirtualEnvBaseSdk.takeIf(FileUtil::exists)
@@ -129,50 +18,3 @@ fun findPreferredVirtualEnvBaseSdk(existingBaseSdks: List<Sdk>): Sdk? {
}
}
internal suspend fun createSdkForTarget(
project: Project?,
environmentConfiguration: TargetEnvironmentConfiguration,
interpreterPath: String,
existingSdks: Collection<Sdk>,
targetPanelExtension: TargetPanelExtension?,
sdkName: String? = null,
): Sdk = withContext(Dispatchers.IO) {
// TODO [targets] Should flavor be more flexible?
val data = PyTargetAwareAdditionalData(PyFlavorAndData(PyFlavorData.Empty, VirtualEnvSdkFlavor.getInstance())).also {
it.interpreterPath = interpreterPath
it.targetEnvironmentConfiguration = environmentConfiguration
targetPanelExtension?.applyToAdditionalData(it)
}
val sdkVersion = when (val r = data.getInterpreterVersion()) {
is Result.Success -> r.result.toPythonVersion()
is Result.Failure -> throw RemoteSdkException(r.error.message) // TODO: Return error instead to show it to user
}
val name = if (!sdkName.isNullOrEmpty()) {
sdkName
}
else {
PythonInterpreterTargetEnvironmentFactory.findDefaultSdkName(project, data, sdkVersion)
}
val sdk = SdkConfigurationUtil.createSdk(existingSdks, interpreterPath, PythonSdkType.getInstance(), data, name)
if (PythonInterpreterTargetEnvironmentFactory.by(environmentConfiguration)?.needAssociateWithModule() == true) {
// FIXME: multi module project support
project?.modules?.firstOrNull()?.let {
sdk.setAssociationToModuleAsync(it)
}
}
sdk.sdkModificator.let { modifiableSdk ->
modifiableSdk.versionString = sdkVersion
writeAction {
modifiableSdk.commitChanges()
}
}
// FIXME: should we persist it?
data.isValid = true
return@withContext sdk
}

View File

@@ -1,12 +1,12 @@
// 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.pycharm.community.ide.impl.configuration
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.sdk.configuration
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
import com.intellij.pycharm.community.ide.impl.configuration.PySdkConfigurationCollector.VirtualEnvResult
import com.intellij.python.common.tools.ToolId
import com.intellij.python.community.impl.venv.PY_REQ_TOOL_ID
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.packaging.PyPackageUtil
@@ -15,9 +15,7 @@ import com.jetbrains.python.packaging.requirementsTxt.PythonRequirementTxtSdkUti
import com.jetbrains.python.packaging.setupPy.SetupPyManager
import com.jetbrains.python.projectCreation.createVenvAndSdk
import com.jetbrains.python.sdk.ModuleOrProject
import com.jetbrains.python.sdk.configuration.*
internal val PY_REQ_TOOL_ID = ToolId("requirements.txt")
import com.jetbrains.python.sdk.configuration.PySdkConfigurationCollector.VirtualEnvResult
internal class PyRequirementsTxtOrSetupPySdkConfiguration : PyProjectSdkConfigurationExtension {
@@ -31,7 +29,7 @@ internal class PyRequirementsTxtOrSetupPySdkConfiguration : PyProjectSdkConfigur
private fun checkManageableEnv(module: Module): EnvCheckerResult {
val configFile = getRequirementsTxtOrSetupPy(module) ?: return EnvCheckerResult.CannotConfigure
return EnvCheckerResult.EnvNotFound(PyCharmCommunityCustomizationBundle.message("sdk.create.venv.suggestion", configFile.name))
return EnvCheckerResult.EnvNotFound(PyBundle.message("sdk.create.venv.suggestion", configFile.name))
}
private suspend fun createAndAddSdk(module: Module): PyResult<Sdk> {

View File

@@ -1,5 +1,5 @@
// 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.configuration
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.sdk.configuration
import com.intellij.internal.statistic.eventLog.EventLogGroup
import com.intellij.internal.statistic.eventLog.events.EventFields

View File

@@ -1,21 +1,19 @@
// 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.pycharm.community.ide.impl.configuration
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.sdk.configuration
import com.intellij.openapi.application.EDT
import com.intellij.openapi.diagnostic.fileLogger
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
import com.intellij.pycharm.community.ide.impl.findEnvOrNull
import com.intellij.python.common.tools.ToolId
import com.intellij.python.community.impl.uv.common.UV_TOOL_ID
import com.intellij.python.pyproject.model.api.SuggestedSdk
import com.intellij.python.pyproject.model.api.suggestSdk
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.onSuccess
import com.jetbrains.python.sdk.baseDir
import com.jetbrains.python.sdk.configuration.*
import com.jetbrains.python.sdk.persist
import com.jetbrains.python.sdk.pyvenvContains
import com.jetbrains.python.sdk.service.PySdkService.Companion.pySdkService
@@ -61,7 +59,7 @@ internal class PyUvSdkConfiguration : PyProjectTomlConfigurationExtension {
): EnvCheckerResult {
getUvExecutable() ?: return EnvCheckerResult.CannotConfigure
val intentionName = PyCharmCommunityCustomizationBundle.message("sdk.set.up.uv.environment", module.name)
val intentionName = PyBundle.message("sdk.set.up.uv.environment", module.name)
val envFound = if (checkExistence) getUvEnv(venvsInModule)?.findEnvOrNull(intentionName) else null

View File

@@ -1,12 +1,10 @@
// 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.pycharm.community.ide.impl.configuration
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.sdk.configuration
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.vfs.refreshAndFindVirtualFile
import com.intellij.platform.ide.progress.withBackgroundProgress
import com.intellij.pycharm.community.ide.impl.PyCharmCommunityCustomizationBundle
import com.intellij.pycharm.community.ide.impl.findEnvOrNull
import com.intellij.python.common.tools.ToolId
import com.jetbrains.python.PyBundle
import com.jetbrains.python.PythonBinary
@@ -14,7 +12,6 @@ import com.jetbrains.python.errorProcessing.MessageError
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.projectCreation.createVenvAndSdk
import com.jetbrains.python.sdk.*
import com.jetbrains.python.sdk.configuration.*
import com.jetbrains.python.sdk.flavors.PyFlavorAndData
import com.jetbrains.python.sdk.flavors.PyFlavorData
import com.jetbrains.python.sdk.flavors.VirtualEnvSdkFlavor
@@ -41,8 +38,8 @@ internal class PyVenvSdkConfiguration : PyProjectSdkConfigurationExtension {
): EnvCheckerResult = withBackgroundProgress(module.project, PyBundle.message("python.sdk.validating.environment")) {
withContext(Dispatchers.IO) {
getVirtualEnv(venvsInModule)?.let {
it.findEnvOrNull(PyCharmCommunityCustomizationBundle.message("sdk.use.existing.venv", it.resolvePythonHome().name))
} ?: EnvCheckerResult.EnvNotFound(PyCharmCommunityCustomizationBundle.message("sdk.create.venv.suggestion.no.arg"))
it.findEnvOrNull(PyBundle.message("sdk.use.existing.venv", it.resolvePythonHome().name))
} ?: EnvCheckerResult.EnvNotFound(PyBundle.message("sdk.create.venv.suggestion.no.arg"))
}
}

View File

@@ -1,5 +1,5 @@
// 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.pycharm.community.ide.impl
// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.sdk.configuration
import com.intellij.codeInspection.util.IntentionName
import com.intellij.openapi.diagnostic.fileLogger
@@ -10,7 +10,6 @@ import com.jetbrains.python.PythonInfo
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.orLogException
import com.jetbrains.python.sdk.asBinToExecute
import com.jetbrains.python.sdk.configuration.EnvCheckerResult
internal suspend fun PythonBinary.findEnvOrNull(@IntentionName intentionName: String): EnvCheckerResult.EnvFound? =
validatePythonAndGetInfo().findEnvOrNull(intentionName)

View File

@@ -6,13 +6,12 @@ import com.intellij.openapi.module.Module;
import com.intellij.openapi.util.UserDataHolder;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.python.community.impl.venv.icons.PythonCommunityImplVenvIcons;
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.icons.PythonIcons;
import com.jetbrains.python.sdk.BasePySdkExtKt;
import com.jetbrains.python.sdk.PySdkExtKt;
import com.jetbrains.python.sdk.legacy.PythonSdkUtil;
import com.jetbrains.python.venvReader.VirtualEnvReader;
import com.jetbrains.python.venvReader.VirtualEnvReaderKt;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@@ -86,6 +85,6 @@ public final class VirtualEnvSdkFlavor extends CPythonSdkFlavor<PyFlavorData.Emp
@Override
public @NotNull Icon getIcon() {
return PythonIcons.Python.Virtualenv;
return PythonCommunityImplVenvIcons.Virtualenv;
}
}