diff --git a/python/conda/gen/com/intellij/python/community/impl/conda/icons/PythonCommunityImplCondaIcons.java b/python/conda/gen/com/intellij/python/community/impl/conda/icons/PythonCommunityImplCondaIcons.java index f3e4b0a18579..47b602fdd683 100644 --- a/python/conda/gen/com/intellij/python/community/impl/conda/icons/PythonCommunityImplCondaIcons.java +++ b/python/conda/gen/com/intellij/python/community/impl/conda/icons/PythonCommunityImplCondaIcons.java @@ -16,4 +16,5 @@ public final class PythonCommunityImplCondaIcons { return IconManager.getInstance().loadRasterizedIcon(path, PythonCommunityImplCondaIcons.class.getClassLoader(), cacheKey, flags); } /** 16x16 */ public static final @NotNull Icon Anaconda = load("icons/com/intellij/python/community/impl/conda/expui/anaconda.svg", 1479605221, 2); + /** 16x16 */ public static final @NotNull Icon Yaml = load("icons/com/intellij/python/community/impl/conda/expui/yaml.svg", -1755535915, 2); } diff --git a/python/conda/resources/icons/com/intellij/python/community/impl/conda/expui/yaml.svg b/python/conda/resources/icons/com/intellij/python/community/impl/conda/expui/yaml.svg new file mode 100644 index 000000000000..9d4031ee225d --- /dev/null +++ b/python/conda/resources/icons/com/intellij/python/community/impl/conda/expui/yaml.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/python/conda/resources/icons/com/intellij/python/community/impl/conda/expui/yaml_dark.svg b/python/conda/resources/icons/com/intellij/python/community/impl/conda/expui/yaml_dark.svg new file mode 100644 index 000000000000..b173b95d99f7 --- /dev/null +++ b/python/conda/resources/icons/com/intellij/python/community/impl/conda/expui/yaml_dark.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/python/conda/resources/intellij.python.community.impl.conda.xml b/python/conda/resources/intellij.python.community.impl.conda.xml index ed7bc6263bb6..12027743b6fc 100644 --- a/python/conda/resources/intellij.python.community.impl.conda.xml +++ b/python/conda/resources/intellij.python.community.impl.conda.xml @@ -1,8 +1,11 @@ + + + diff --git a/python/conda/resources/messages/PyCondaBundle.properties b/python/conda/resources/messages/PyCondaBundle.properties new file mode 100644 index 000000000000..fd4d7613e872 --- /dev/null +++ b/python/conda/resources/messages/PyCondaBundle.properties @@ -0,0 +1 @@ +command.name.add.package.to.conda.environments.yml=Add a package to conda environments.yml diff --git a/python/conda/src/com/intellij/python/community/impl/conda/PyCondaBundle.kt b/python/conda/src/com/intellij/python/community/impl/conda/PyCondaBundle.kt new file mode 100644 index 000000000000..c8fbb1379a7d --- /dev/null +++ b/python/conda/src/com/intellij/python/community/impl/conda/PyCondaBundle.kt @@ -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.conda + +import com.intellij.DynamicBundle +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.Nls +import org.jetbrains.annotations.PropertyKey + +@ApiStatus.Internal +object PyCondaBundle { + private const val BUNDLE = "messages.PyCondaBundle" + + private val INSTANCE = DynamicBundle(PyCondaBundle::class.java, BUNDLE) + + @JvmStatic + fun message(key: @PropertyKey(resourceBundle = BUNDLE) String, vararg params: Any): @Nls String { + return INSTANCE.getMessage(key, *params) + } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/conda/environmentYml/CondaEnvironmentYmlSdkUtils.kt b/python/conda/src/com/intellij/python/community/impl/conda/environmentYml/CondaEnvironmentYmlFile.kt similarity index 53% rename from python/src/com/jetbrains/python/packaging/conda/environmentYml/CondaEnvironmentYmlSdkUtils.kt rename to python/conda/src/com/intellij/python/community/impl/conda/environmentYml/CondaEnvironmentYmlFile.kt index e7592c58506a..d5284fe74415 100644 --- a/python/src/com/jetbrains/python/packaging/conda/environmentYml/CondaEnvironmentYmlSdkUtils.kt +++ b/python/conda/src/com/intellij/python/community/impl/conda/environmentYml/CondaEnvironmentYmlFile.kt @@ -1,10 +1,25 @@ -// 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.environmentYml +// 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.conda.environmentYml import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.vfs.VirtualFile +import com.intellij.python.community.impl.conda.icons.PythonCommunityImplCondaIcons +import com.jetbrains.python.requirements.PyDependenciesFile import com.jetbrains.python.sdk.associatedModuleDir import org.jetbrains.annotations.ApiStatus +import javax.swing.Icon + + +@ApiStatus.Internal +data class CondaEnvironmentYmlFile(override val virtualFile: VirtualFile) : PyDependenciesFile { + override val icon: Icon + get() = PythonCommunityImplCondaIcons.Yaml +} + +@ApiStatus.Internal +fun Sdk.findCondaEnvironmentYmlFile(): CondaEnvironmentYmlFile? { + return CondaEnvironmentYmlSdkUtils.findFile(this)?.let { CondaEnvironmentYmlFile(it) } +} /** * Migrate from the module persistent path to sdk path diff --git a/python/python-pyproject/gen/com/intellij/python/pyproject/icons/PythonPyprojectIcons.java b/python/python-pyproject/gen/com/intellij/python/pyproject/icons/PythonPyprojectIcons.java index 1db923f53010..ca840cd957a2 100644 --- a/python/python-pyproject/gen/com/intellij/python/pyproject/icons/PythonPyprojectIcons.java +++ b/python/python-pyproject/gen/com/intellij/python/pyproject/icons/PythonPyprojectIcons.java @@ -24,4 +24,6 @@ public final class PythonPyprojectIcons { /** 16x16 */ public static final @NotNull Icon PyProjectModule = load("icons/com/intellij/python/pyproject/expui/model/pyProjectModule.svg", 623489112, 2); } + + /** 16x16 */ public static final @NotNull Icon Toml = load("icons/com/intellij/python/pyproject/expui/toml.svg", 945785471, 2); } diff --git a/python/python-pyproject/resources/icons/com/intellij/python/pyproject/expui/toml.svg b/python/python-pyproject/resources/icons/com/intellij/python/pyproject/expui/toml.svg new file mode 100644 index 000000000000..aafa75a4d4fc --- /dev/null +++ b/python/python-pyproject/resources/icons/com/intellij/python/pyproject/expui/toml.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/python/python-pyproject/resources/icons/com/intellij/python/pyproject/expui/toml_dark.svg b/python/python-pyproject/resources/icons/com/intellij/python/pyproject/expui/toml_dark.svg new file mode 100644 index 000000000000..7886becc2154 --- /dev/null +++ b/python/python-pyproject/resources/icons/com/intellij/python/pyproject/expui/toml_dark.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/python/python-pyproject/src/com/intellij/python/pyproject/PyProjectToml.kt b/python/python-pyproject/src/com/intellij/python/pyproject/PyProjectToml.kt index 0fa142a7d01d..3ef0709494e4 100644 --- a/python/python-pyproject/src/com/intellij/python/pyproject/PyProjectToml.kt +++ b/python/python-pyproject/src/com/intellij/python/pyproject/PyProjectToml.kt @@ -259,7 +259,9 @@ data class PyProjectToml( * Attempts to find the `pyproject.toml` file in the provided module. * Returns null if not found. */ - suspend fun findFile(module: Module): VirtualFile? = findAmongRoots(module, PY_PROJECT_TOML) + suspend fun findPyProjectTomlFile(module: Module): PyProjectTomlFile? { + return findAmongRoots(module, PY_PROJECT_TOML)?.let { PyProjectTomlFile(it) } + } suspend fun findInRoot(moduleBasePath: Path): Path? = withContext(Dispatchers.IO) { moduleBasePath.resolve(PY_PROJECT_TOML).takeIf { it.isRegularFile() } diff --git a/python/python-pyproject/src/com/intellij/python/pyproject/PyProjectTomlFile.kt b/python/python-pyproject/src/com/intellij/python/pyproject/PyProjectTomlFile.kt new file mode 100644 index 000000000000..6528d0c0953b --- /dev/null +++ b/python/python-pyproject/src/com/intellij/python/pyproject/PyProjectTomlFile.kt @@ -0,0 +1,11 @@ +package com.intellij.python.pyproject + +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.python.pyproject.icons.PythonPyprojectIcons +import com.jetbrains.python.requirements.PyDependenciesFile +import javax.swing.Icon + + +data class PyProjectTomlFile(override val virtualFile: VirtualFile) : PyDependenciesFile { + override val icon: Icon = PythonPyprojectIcons.Toml +} diff --git a/python/python-requirements/gen/com/intellij/python/requirements/icons/PythonRequirementsIcons.java b/python/python-requirements/gen/com/intellij/python/requirements/icons/PythonRequirementsIcons.java new file mode 100644 index 000000000000..09e154831099 --- /dev/null +++ b/python/python-requirements/gen/com/intellij/python/requirements/icons/PythonRequirementsIcons.java @@ -0,0 +1,18 @@ +// 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.requirements.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 + */ +public final class PythonRequirementsIcons { + private static @NotNull Icon load(@NotNull String path, int cacheKey, int flags) { + return IconManager.getInstance().loadRasterizedIcon(path, PythonRequirementsIcons.class.getClassLoader(), cacheKey, flags); + } + /** 16x16 */ public static final @NotNull Icon Requirements = load("icons/com/intellij/python/requirements/expui/requirements.svg", 2026575763, 2); +} diff --git a/python/python-requirements/gen/com/intellij/python/requirements/icons/package-info.java b/python/python-requirements/gen/com/intellij/python/requirements/icons/package-info.java new file mode 100644 index 000000000000..67b63770a092 --- /dev/null +++ b/python/python-requirements/gen/com/intellij/python/requirements/icons/package-info.java @@ -0,0 +1,4 @@ +@ApiStatus.Internal +package com.intellij.python.requirements.icons; + +import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/python/python-requirements/resources/icons/com/intellij/python/requirements/expui/requirements.svg b/python/python-requirements/resources/icons/com/intellij/python/requirements/expui/requirements.svg new file mode 100644 index 000000000000..c88f94d9e825 --- /dev/null +++ b/python/python-requirements/resources/icons/com/intellij/python/requirements/expui/requirements.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/python/python-requirements/resources/icons/com/intellij/python/requirements/expui/requirements_dark.svg b/python/python-requirements/resources/icons/com/intellij/python/requirements/expui/requirements_dark.svg new file mode 100644 index 000000000000..374d9fcaef4e --- /dev/null +++ b/python/python-requirements/resources/icons/com/intellij/python/requirements/expui/requirements_dark.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/python/python-requirements/src/com/jetbrains/python/requirements/PyDependenciesFile.kt b/python/python-requirements/src/com/jetbrains/python/requirements/PyDependenciesFile.kt new file mode 100644 index 000000000000..2a8de0ce6063 --- /dev/null +++ b/python/python-requirements/src/com/jetbrains/python/requirements/PyDependenciesFile.kt @@ -0,0 +1,23 @@ +package com.jetbrains.python.requirements + +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.python.requirements.icons.PythonRequirementsIcons +import org.jetbrains.annotations.ApiStatus +import javax.swing.Icon + +@ApiStatus.Internal +interface PyDependenciesFile { + val virtualFile: VirtualFile + val icon: Icon +} + + +@ApiStatus.Internal +data class SetupPyFile(override val virtualFile: VirtualFile) : PyDependenciesFile { + override val icon: Icon = PythonRequirementsIcons.Requirements +} + +@ApiStatus.Internal +data class RequirementsTxtFile(override val virtualFile: VirtualFile) : PyDependenciesFile { + override val icon: Icon = PythonRequirementsIcons.Requirements +} \ No newline at end of file diff --git a/python/python-sdk/gen/com/intellij/python/sdk/icons/package-info.java b/python/python-sdk/gen/com/intellij/python/sdk/icons/package-info.java new file mode 100644 index 000000000000..c92681e92087 --- /dev/null +++ b/python/python-sdk/gen/com/intellij/python/sdk/icons/package-info.java @@ -0,0 +1,4 @@ +@ApiStatus.Internal +package com.intellij.python.sdk.icons; + +import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/python/python-sdk/src/com/jetbrains/python/sdk/SdkExt.kt b/python/python-sdk/src/com/jetbrains/python/sdk/SdkExt.kt index 30a88af4b4fd..32f9f3790465 100644 --- a/python/python-sdk/src/com/jetbrains/python/sdk/SdkExt.kt +++ b/python/python-sdk/src/com/jetbrains/python/sdk/SdkExt.kt @@ -4,15 +4,21 @@ package com.jetbrains.python.sdk import com.intellij.execution.target.TargetBasedSdkAdditionalData import com.intellij.execution.target.TargetEnvironmentConfiguration import com.intellij.openapi.application.writeAction +import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.module.Module import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.util.Key +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.vfs.VirtualFileManager import com.jetbrains.python.sdk.flavors.PythonSdkFlavor import com.jetbrains.python.sdk.impl.buildPresentationInfo import com.jetbrains.python.sdk.legacy.PythonSdkUtil.isPythonSdk import com.jetbrains.python.target.PyTargetAwareAdditionalData import org.jetbrains.annotations.ApiStatus.Internal +import java.nio.file.InvalidPathException import java.nio.file.Path +import kotlin.io.path.Path +import com.intellij.openapi.vfs.ex.temp.TempFileSystem @get:Internal val BASE_DIR: Key = Key.create("PYTHON_PROJECT_BASE_PATH") @@ -112,4 +118,36 @@ val Sdk.targetEnvConfiguration: TargetEnvironmentConfiguration? @Internal -fun Sdk.pyInterpreterPresentation(customName: String? = null): PythonInterpreterPresentation = buildPresentationInfo(customName) \ No newline at end of file +fun Sdk.pyInterpreterPresentation(customName: String? = null): PythonInterpreterPresentation = buildPresentationInfo(customName) + + +private val SDK_ERROR_REPORTED = Key.create("pySdkErrorReported") + +@get:Internal +val Sdk.associatedModuleDir: VirtualFile? + get() { + val nioPath = associatedModuleNioPath ?: return null + return VirtualFileManager.getInstance().findFileByNioPath(nioPath) ?: TempFileSystem.getInstance().findFileByNioFile(nioPath) + } + +@get:Internal +val Sdk.associatedModuleNioPath: Path? + get() = + try { + associatedModulePath?.let { Path(it) } + } + catch (e: InvalidPathException) { + if (getUserData(SDK_ERROR_REPORTED) != true) { + thisLogger().warn("Can't convert ${associatedModulePath} to path", e) + putUserData(SDK_ERROR_REPORTED, true) + } + null + } + +@get:Internal +val Sdk.associatedModulePath: String? + // TODO: Support .project associations + get() = associatedPathFromAdditionalData /*?: associatedPathFromDotProject*/ + +private val Sdk.associatedPathFromAdditionalData: String? + get() = (sdkAdditionalData as? PythonSdkAdditionalData)?.associatedModulePath diff --git a/python/src/com/jetbrains/python/conda/sdk/configuration/PyEnvironmentYmlSdkConfiguration.kt b/python/src/com/jetbrains/python/conda/sdk/configuration/PyEnvironmentYmlSdkConfiguration.kt index c750b827cfb7..b39a1bab96be 100644 --- a/python/src/com/jetbrains/python/conda/sdk/configuration/PyEnvironmentYmlSdkConfiguration.kt +++ b/python/src/com/jetbrains/python/conda/sdk/configuration/PyEnvironmentYmlSdkConfiguration.kt @@ -22,8 +22,8 @@ import com.jetbrains.python.configuration.PyConfigurableInterpreterList import com.jetbrains.python.errorProcessing.PyResult import com.jetbrains.python.getOrNull import com.jetbrains.python.onSuccess -import com.jetbrains.python.packaging.conda.environmentYml.CondaEnvironmentYmlSdkUtils -import com.jetbrains.python.packaging.conda.environmentYml.format.CondaEnvironmentYmlParser +import com.intellij.python.community.impl.conda.environmentYml.CondaEnvironmentYmlSdkUtils +import com.intellij.python.community.impl.conda.environmentYml.format.CondaEnvironmentYmlParser import com.jetbrains.python.pathValidation.PlatformAndRoot import com.jetbrains.python.pathValidation.ValidationRequest import com.jetbrains.python.pathValidation.validateExecutableFile diff --git a/python/src/com/jetbrains/python/hatch/packaging/HatchPackageManager.kt b/python/src/com/jetbrains/python/hatch/packaging/HatchPackageManager.kt index 9798461d865c..374ea71d6916 100644 --- a/python/src/com/jetbrains/python/hatch/packaging/HatchPackageManager.kt +++ b/python/src/com/jetbrains/python/hatch/packaging/HatchPackageManager.kt @@ -3,9 +3,9 @@ package com.jetbrains.python.hatch.packaging import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk -import com.intellij.openapi.vfs.VirtualFile import com.intellij.python.hatch.HatchService import com.intellij.python.hatch.getHatchService +import com.intellij.python.pyproject.PyProjectTomlFile import com.jetbrains.python.errorProcessing.PyResult import com.jetbrains.python.hatch.sdk.HatchSdkAdditionalData import com.jetbrains.python.hatch.sdk.isHatch @@ -33,7 +33,7 @@ internal class HatchPackageManager(project: Project, sdk: Sdk) : PipPythonPackag return workingDirectory.getHatchService(hatchEnvironmentName = data.hatchEnvironmentName) } - override fun getDependencyFile(): VirtualFile? { + override fun getDependencyFile(): PyProjectTomlFile? { val data = sdk.sdkAdditionalData as? HatchSdkAdditionalData ?: return null val workingDirectory = data.hatchWorkingDirectory ?: return null return resolvePyProjectToml(workingDirectory) diff --git a/python/src/com/jetbrains/python/packaging/conda/CondaPackageManager.kt b/python/src/com/jetbrains/python/packaging/conda/CondaPackageManager.kt index aea19c8e1bf2..f2bad7863046 100644 --- a/python/src/com/jetbrains/python/packaging/conda/CondaPackageManager.kt +++ b/python/src/com/jetbrains/python/packaging/conda/CondaPackageManager.kt @@ -17,10 +17,11 @@ 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.common.toPythonPackages +import com.intellij.python.community.impl.conda.environmentYml.CondaEnvironmentYmlFile import com.jetbrains.python.packaging.management.PyWorkspaceMember -import com.jetbrains.python.packaging.conda.environmentYml.CondaEnvironmentYmlSdkUtils -import com.jetbrains.python.packaging.conda.environmentYml.format.CondaEnvironmentYmlParser -import com.jetbrains.python.packaging.conda.environmentYml.format.EnvironmentYmlModifier +import com.intellij.python.community.impl.conda.environmentYml.findCondaEnvironmentYmlFile +import com.intellij.python.community.impl.conda.environmentYml.format.CondaEnvironmentYmlParser +import com.intellij.python.community.impl.conda.environmentYml.format.EnvironmentYmlModifier import com.jetbrains.python.packaging.management.PythonPackageInstallRequest import com.jetbrains.python.packaging.management.PythonPackageManager import com.jetbrains.python.packaging.management.PythonPackageManagerEngine @@ -39,7 +40,7 @@ class CondaPackageManager(project: Project, sdk: Sdk) : PythonPackageManager(pro override suspend fun syncLockedCommand(): PyResult { val requirementsFile = getDependencyFile() ?: return PyResult.localizedError(PyBundle.message("python.sdk.conda.requirements.file.not.found")) - return updateEnv(requirementsFile) + return updateEnv(requirementsFile.virtualFile) } private suspend fun updateEnv(envFile: VirtualFile): PyResult { @@ -151,16 +152,14 @@ class CondaPackageManager(project: Project, sdk: Sdk) : PythonPackageManager(pro override suspend fun listDeclaredPackages(): PyResult>? { val envFile = getDependencyFile() ?: return null - val requirements = CondaEnvironmentYmlParser.fromFile(envFile) ?: return null + val requirements = CondaEnvironmentYmlParser.fromFile(envFile.virtualFile) ?: return null return PyResult.success(requirements.toPythonPackages()) } - override fun getDependencyFile(): VirtualFile? { - return CondaEnvironmentYmlSdkUtils.findFile(sdk) - } + override fun getDependencyFile(): CondaEnvironmentYmlFile? = sdk.findCondaEnvironmentYmlFile() override suspend fun addDependencyImpl(requirement: PyRequirement): Boolean { val envFile = getDependencyFile() ?: return false - return EnvironmentYmlModifier.addRequirement(project, envFile, requirement.presentableText) + return EnvironmentYmlModifier.addRequirement(project, envFile.virtualFile, requirement.presentableText) } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/conda/actions/CondaPackageManagerAction.kt b/python/src/com/jetbrains/python/packaging/conda/actions/CondaPackageManagerAction.kt index a14328c0efb7..174305fa80f2 100644 --- a/python/src/com/jetbrains/python/packaging/conda/actions/CondaPackageManagerAction.kt +++ b/python/src/com/jetbrains/python/packaging/conda/actions/CondaPackageManagerAction.kt @@ -5,8 +5,8 @@ import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.PlatformDataKeys import com.jetbrains.python.errorProcessing.PyResult import com.jetbrains.python.packaging.conda.CondaPackageManager -import com.jetbrains.python.packaging.conda.environmentYml.CondaEnvironmentYmlSdkUtils.ENV_YAML_FILE_NAME -import com.jetbrains.python.packaging.conda.environmentYml.CondaEnvironmentYmlSdkUtils.ENV_YML_FILE_NAME +import com.intellij.python.community.impl.conda.environmentYml.CondaEnvironmentYmlSdkUtils.ENV_YAML_FILE_NAME +import com.intellij.python.community.impl.conda.environmentYml.CondaEnvironmentYmlSdkUtils.ENV_YML_FILE_NAME import com.jetbrains.python.packaging.management.PythonPackageManagerAction import com.jetbrains.python.packaging.management.getPythonPackageManager import kotlin.text.Regex.Companion.escape diff --git a/python/src/com/jetbrains/python/packaging/management/PythonPackageManager.kt b/python/src/com/jetbrains/python/packaging/management/PythonPackageManager.kt index b88f42eb1c36..977df8a3a11c 100644 --- a/python/src/com/jetbrains/python/packaging/management/PythonPackageManager.kt +++ b/python/src/com/jetbrains/python/packaging/management/PythonPackageManager.kt @@ -8,7 +8,6 @@ import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ModalityState import com.intellij.openapi.application.asContextElement -import com.intellij.openapi.application.readAction import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.module.Module @@ -19,6 +18,7 @@ import com.intellij.openapi.util.Key import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFileManager import com.intellij.python.pyproject.PY_PROJECT_TOML +import com.intellij.python.pyproject.PyProjectTomlFile import com.intellij.serviceContainer.AlreadyDisposedException import com.intellij.util.cancelOnDispose import com.intellij.util.concurrency.annotations.RequiresBackgroundThread @@ -40,6 +40,7 @@ import com.jetbrains.python.packaging.packageRequirements.DependencyTreeProvider import com.jetbrains.python.packaging.packageRequirements.FlatPackageStructureNode import com.jetbrains.python.packaging.packageRequirements.PackageStructureNode import com.jetbrains.python.packaging.utils.PyPackageCoroutine +import com.jetbrains.python.requirements.PyDependenciesFile import com.jetbrains.python.sdk.PythonSdkType import com.jetbrains.python.sdk.isReadOnly import com.jetbrains.python.sdk.readOnlyErrorMessage @@ -348,6 +349,7 @@ abstract class PythonPackageManager @ApiStatus.Internal constructor( @ApiStatus.Internal suspend fun listDeclaredPackagesCached(): PyResult>? { val stamps = getDependencyFiles() + .map { it.virtualFile } .sortedBy { it.path } .map { it to it.modificationStamp } if (stamps.isEmpty()) return null @@ -360,7 +362,7 @@ abstract class PythonPackageManager @ApiStatus.Internal constructor( */ @ApiStatus.Internal @RequiresBackgroundThread - open fun getDependencyFile(): VirtualFile? = null + open fun getDependencyFile(): PyDependenciesFile? = null /** * Returns every file whose modification should invalidate the declared-packages cache. @@ -370,7 +372,7 @@ abstract class PythonPackageManager @ApiStatus.Internal constructor( * file invalidate the cached declared-packages list. */ @ApiStatus.Internal - protected open suspend fun getDependencyFiles(): List = listOfNotNull(getDependencyFile()) + protected open suspend fun getDependencyFiles(): List = listOfNotNull(getDependencyFile()) /** * Adds a dependency to the project's dependency declaration file. @@ -470,13 +472,10 @@ fun PythonPackageManager.listDeclaredPackagesAsync(): List? = run */ @ApiStatus.Internal @RequiresBackgroundThread -internal fun resolvePyProjectToml(workingDirectory: Path): VirtualFile? { +internal fun resolvePyProjectToml(workingDirectory: Path): PyProjectTomlFile? { val pyprojectPath = workingDirectory.resolve(PY_PROJECT_TOML) - return runBlockingMaybeCancellable { - readAction { - VirtualFileManager.getInstance().refreshAndFindFileByNioPath(pyprojectPath) - } - } + val virtualFile = VirtualFileManager.getInstance().findFileByNioPath(pyprojectPath) ?: return null + return PyProjectTomlFile(virtualFile) } @ApiStatus.Internal diff --git a/python/src/com/jetbrains/python/packaging/pip/PipPythonPackageManager.kt b/python/src/com/jetbrains/python/packaging/pip/PipPythonPackageManager.kt index 54606ad38461..2c79f33918a8 100644 --- a/python/src/com/jetbrains/python/packaging/pip/PipPythonPackageManager.kt +++ b/python/src/com/jetbrains/python/packaging/pip/PipPythonPackageManager.kt @@ -26,7 +26,9 @@ import com.jetbrains.python.packaging.management.PythonRepositoryManager import com.jetbrains.python.packaging.management.hasInstalledPackage import com.jetbrains.python.packaging.requirementsTxt.RequirementsTxtManipulationHelper import com.jetbrains.python.psi.LanguageLevel +import com.jetbrains.python.requirements.PyDependenciesFile import com.jetbrains.python.sdk.PythonSdkAdditionalData +import com.jetbrains.python.requirements.RequirementsTxtFile import com.jetbrains.python.sdk.associatedModuleDir import com.jetbrains.python.sdk.legacy.PythonSdkUtil import com.jetbrains.python.statistics.version @@ -52,7 +54,7 @@ open class PipPythonPackageManager(project: Project, sdk: Sdk) : PythonPackageMa override suspend fun syncLockedCommand(): PyResult { val requirementsFile = getDependencyFile() return if (requirementsFile != null) { - engine.syncRequirementsTxt(requirementsFile) + engine.syncRequirementsTxt(requirementsFile.virtualFile) } else { engine.syncProject() @@ -77,21 +79,22 @@ open class PipPythonPackageManager(project: Project, sdk: Sdk) : PythonPackageMa override suspend fun listDeclaredPackages(): PyResult>? { val requirementsFile = getDependencyFile() ?: return null val requirements = readAction { - PyRequirementParser.fromFile(requirementsFile) + PyRequirementParser.fromFile(requirementsFile.virtualFile) } return PyResult.success(requirements.mapNotNull { it.toPythonPackage() }) } - override fun getDependencyFile(): VirtualFile? { + override fun getDependencyFile(): PyDependenciesFile? { val data = sdk.sdkAdditionalData as? PythonSdkAdditionalData ?: return null val requirementsPath = data.requiredTxtPath ?: Path.of(PythonSdkAdditionalData.REQUIREMENT_TXT_DEFAULT) - return sdk.associatedModuleDir?.findFileByRelativePath(requirementsPath.toString()) + val requirementsTxtFile = sdk.associatedModuleDir?.findFileByRelativePath(requirementsPath.toString()) + return requirementsTxtFile?.let { RequirementsTxtFile(it) } } override suspend fun addDependencyImpl(requirement: PyRequirement): Boolean { val requirementsFile = getDependencyFile() ?: return false return withContext(Dispatchers.EDT) { - RequirementsTxtManipulationHelper.addToRequirementsTxt(project, requirementsFile, requirement.presentableText) + RequirementsTxtManipulationHelper.addToRequirementsTxt(project, requirementsFile.virtualFile, requirement.presentableText) } } } diff --git a/python/src/com/jetbrains/python/packaging/pipenv/PipEnvPackageManager.kt b/python/src/com/jetbrains/python/packaging/pipenv/PipEnvPackageManager.kt index 601b69df2969..10f749bb9762 100644 --- a/python/src/com/jetbrains/python/packaging/pipenv/PipEnvPackageManager.kt +++ b/python/src/com/jetbrains/python/packaging/pipenv/PipEnvPackageManager.kt @@ -16,7 +16,8 @@ import com.jetbrains.python.packaging.management.PythonPackageManager import com.jetbrains.python.packaging.management.PythonRepositoryManager import com.jetbrains.python.packaging.pip.PipRepositoryManager import com.jetbrains.python.sdk.associatedModulePath -import com.jetbrains.python.sdk.pipenv.PipEnvFileHelper +import com.jetbrains.python.sdk.pipenv.PipFileLockFile +import com.jetbrains.python.sdk.pipenv.findPipFileLockFile import com.jetbrains.python.sdk.pipenv.runPipEnv import com.jetbrains.python.sdk.pipenv.PipEnvParser as SdkPipEnvParser import org.jetbrains.annotations.ApiStatus @@ -81,11 +82,9 @@ class PipEnvPackageManager(project: Project, sdk: Sdk) : PythonPackageManager(pr override suspend fun listDeclaredPackages(): PyResult>? { val pipFileLock = getDependencyFile() ?: return null - val requirements = SdkPipEnvParser.getPipFileLockRequirements(pipFileLock) ?: return null + val requirements = SdkPipEnvParser.getPipFileLockRequirements(pipFileLock.virtualFile) ?: return null return PyResult.success(requirements.toPythonPackages()) } - override fun getDependencyFile(): com.intellij.openapi.vfs.VirtualFile? { - return PipEnvFileHelper.getPipFileLock(sdk) - } + override fun getDependencyFile(): PipFileLockFile? = sdk.findPipFileLockFile() } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/packaging/pipenv/actions/PipEnvPackageManagerAction.kt b/python/src/com/jetbrains/python/packaging/pipenv/actions/PipEnvPackageManagerAction.kt index 73c014f56add..ac4bcca56ac5 100644 --- a/python/src/com/jetbrains/python/packaging/pipenv/actions/PipEnvPackageManagerAction.kt +++ b/python/src/com/jetbrains/python/packaging/pipenv/actions/PipEnvPackageManagerAction.kt @@ -6,11 +6,12 @@ import com.jetbrains.python.errorProcessing.PyResult import com.jetbrains.python.packaging.management.PythonPackageManagerAction import com.jetbrains.python.packaging.management.getPythonPackageManager import com.jetbrains.python.packaging.pipenv.PipEnvPackageManager -import com.jetbrains.python.sdk.pipenv.PipEnvFileHelper +import com.jetbrains.python.sdk.pipenv.PIP_FILE +import com.jetbrains.python.sdk.pipenv.PIP_FILE_LOCK import kotlin.text.Regex.Companion.escape internal sealed class PipEnvPackageManagerAction : PythonPackageManagerAction() { - override val fileNamesPattern: Regex = """^(${escape(PipEnvFileHelper.PIP_FILE)}|${escape(PipEnvFileHelper.PIP_FILE_LOCK)})$""".toRegex() + override val fileNamesPattern: Regex = """^(${escape(PIP_FILE)}|${escape(PIP_FILE_LOCK)})$""".toRegex() override fun getManager(e: AnActionEvent): PipEnvPackageManager? = e.getPythonPackageManager() } diff --git a/python/src/com/jetbrains/python/packaging/setupPy/SetupPyHelpers.kt b/python/src/com/jetbrains/python/packaging/setupPy/SetupPyHelpers.kt index 6e945320eb86..569fdc62f59c 100644 --- a/python/src/com/jetbrains/python/packaging/setupPy/SetupPyHelpers.kt +++ b/python/src/com/jetbrains/python/packaging/setupPy/SetupPyHelpers.kt @@ -30,7 +30,6 @@ import com.jetbrains.python.psi.PyUtil import com.jetbrains.python.psi.impl.PyPsiUtils import com.jetbrains.python.psi.resolve.PyResolveContext import com.jetbrains.python.psi.types.TypeEvalContext -import com.jetbrains.python.sdk.rootManager import org.jetbrains.annotations.ApiStatus @ApiStatus.Internal diff --git a/python/src/com/jetbrains/python/pipenv/sdk/configuration/PyPipfileSdkConfiguration.kt b/python/src/com/jetbrains/python/pipenv/sdk/configuration/PyPipfileSdkConfiguration.kt index ff5de2ec6f2d..31083e73ee9f 100644 --- a/python/src/com/jetbrains/python/pipenv/sdk/configuration/PyPipfileSdkConfiguration.kt +++ b/python/src/com/jetbrains/python/pipenv/sdk/configuration/PyPipfileSdkConfiguration.kt @@ -31,7 +31,7 @@ import com.jetbrains.python.sdk.findAmongRoots import com.jetbrains.python.sdk.impl.PySdkBundle import com.jetbrains.python.sdk.impl.resolvePythonBinary import com.jetbrains.python.sdk.legacy.PythonSdkUtil -import com.jetbrains.python.sdk.pipenv.PipEnvFileHelper +import com.jetbrains.python.sdk.pipenv.PIP_FILE import com.jetbrains.python.sdk.pipenv.PyPipEnvSdkAdditionalData import com.jetbrains.python.sdk.pipenv.getPipEnvExecutable import com.jetbrains.python.sdk.pipenv.runPipEnv @@ -62,7 +62,7 @@ internal class PyPipfileSdkConfiguration : PyProjectSdkConfigurationExtension { private suspend fun checkManageableEnv( module: Module, ): EnvCheckerResult = withBackgroundProgress(module.project, PyBundle.message("python.sdk.validating.environment")) { - val pipfile = findAmongRoots(module, PipEnvFileHelper.PIP_FILE)?.name ?: return@withBackgroundProgress EnvCheckerResult.CannotConfigure + val pipfile = findAmongRoots(module, PIP_FILE)?.name ?: return@withBackgroundProgress EnvCheckerResult.CannotConfigure val pipEnvExecutable = getPipEnvExecutable() ?: return@withBackgroundProgress EnvCheckerResult.CannotConfigure val canManage = pipEnvExecutable.isExecutable() val intentionName = PyBundle.message("sdk.create.pipenv.suggestion", pipfile) diff --git a/python/src/com/jetbrains/python/poetry/sdk/configuration/PyPoetrySdkConfiguration.kt b/python/src/com/jetbrains/python/poetry/sdk/configuration/PyPoetrySdkConfiguration.kt index a9857269d8db..d839d52df36b 100644 --- a/python/src/com/jetbrains/python/poetry/sdk/configuration/PyPoetrySdkConfiguration.kt +++ b/python/src/com/jetbrains/python/poetry/sdk/configuration/PyPoetrySdkConfiguration.kt @@ -78,8 +78,8 @@ internal class PyPoetrySdkConfiguration : PyProjectTomlConfigurationExtension { val isPoetryProject = if (checkToml) { withContext(Dispatchers.IO) { - PyProjectToml.findFile(module) - ?.let { toml -> getPyProjectTomlForPoetry(toml) } != null || poetryLockExists + PyProjectToml.findPyProjectTomlFile(module) + ?.let { toml -> getPyProjectTomlForPoetry(toml.virtualFile) } != null || poetryLockExists } } else true @@ -126,9 +126,9 @@ internal class PyPoetrySdkConfiguration : PyProjectTomlConfigurationExtension { ) ) } - val tomlFile = PyProjectToml.findFile(module) - val versionSpecifiers = tomlFile?.let { vf -> - readAction { vf.findPsiFile(module.project)?.resolvePythonVersionSpecifiers() } + val tomlFile = PyProjectToml.findPyProjectTomlFile(module) + val versionSpecifiers = tomlFile?.let { pyProjectTomlFile -> + readAction { pyProjectTomlFile.virtualFile.findPsiFile(module.project)?.resolvePythonVersionSpecifiers() } } ?: PyVersionSpecifiers.ANY_SUPPORTED val baseSystemPython = getSystemPython( diff --git a/python/src/com/jetbrains/python/poetry/sdk/evolution/PoetrySelectSdkProvider.kt b/python/src/com/jetbrains/python/poetry/sdk/evolution/PoetrySelectSdkProvider.kt index a4ef5566d044..f0db7e3197d4 100644 --- a/python/src/com/jetbrains/python/poetry/sdk/evolution/PoetrySelectSdkProvider.kt +++ b/python/src/com/jetbrains/python/poetry/sdk/evolution/PoetrySelectSdkProvider.kt @@ -35,7 +35,7 @@ internal class PoetrySelectSdkProvider() : EvoSelectSdkProvider { getPoetryExecutable() ?:PyResult.localizedError(PyBundle.message("python.sdk.poetry.execution.exception.no.poetry.message")) val pyProjectTomlFile = withContext(Dispatchers.IO) { - PyProjectToml.findFile(evoModuleSdk.module) + PyProjectToml.findPyProjectTomlFile(evoModuleSdk.module)?.virtualFile } ?: return@EvoTreeLazyNodeElement PyResult.localizedError(PyBundle.message("evolution.pyproject.toml.file.is.required.for.poetry")) val envList = runPoetry(pyProjectTomlFile.parent.toNioPath(), "env", "list", "--full-path").getOr { return@EvoTreeLazyNodeElement it } @@ -44,7 +44,7 @@ internal class PoetrySelectSdkProvider() : EvoSelectSdkProvider { val envByFolders = environments.groupBy { it.parent }.toMutableMap() - val (projectName, requiresPython) = withContext(Dispatchers.IO) { + val (projectName, _) = withContext(Dispatchers.IO) { val toml = PyProjectToml.parse(pyProjectTomlFile.readText()) (toml.project?.name) to (toml.project?.requiresPython) } diff --git a/python/src/com/jetbrains/python/sdk/PySdkExt.kt b/python/src/com/jetbrains/python/sdk/PySdkExt.kt index b50304f9e4ac..764ffe574ec5 100644 --- a/python/src/com/jetbrains/python/sdk/PySdkExt.kt +++ b/python/src/com/jetbrains/python/sdk/PySdkExt.kt @@ -248,35 +248,6 @@ fun Sdk.isAssociatedWithAnotherModule(module: Module?): Boolean { return basePath != associatedPath } -@get:Internal -val Sdk.associatedModulePath: String? - // TODO: Support .project associations - get() = associatedPathFromAdditionalData /*?: associatedPathFromDotProject*/ - - -@get:Internal -val Sdk.associatedModuleNioPath: Path? - get() = - try { - associatedModulePath?.let { Path(it) } - } - catch (e: InvalidPathException) { - if (getUserData(SDK_ERROR_REPORTED) != true) { - LOGGER.warn("Can't convert ${associatedModulePath} to path", e) - putUserData(SDK_ERROR_REPORTED, true) - } - null - } - -private val SDK_ERROR_REPORTED = Key.create("pySdkErrorReported") - -@get:Internal -val Sdk.associatedModuleDir: VirtualFile? - get() { - val nioPath = associatedModuleNioPath ?: return null - return VirtualFileManager.getInstance().findFileByNioPath(nioPath) ?: TempFileSystem.getInstance().findFileByNioFile(nioPath) - } - @Internal fun Sdk.setup(existingSdks: List): Sdk? { val homeDir = homeDirectory ?: return null @@ -384,9 +355,6 @@ val Sdk.isReadOnly: Boolean val Sdk.readOnlyErrorMessage: String get() = PythonSdkReadOnlyProvider.getReadOnlyMessage(this) ?: PyBundle.message("python.sdk.read.only", name) -private val Sdk.associatedPathFromAdditionalData: String? - get() = (sdkAdditionalData as? PythonSdkAdditionalData)?.associatedModulePath - val Sdk.sdkFlavor: PythonSdkFlavor<*> get() = getOrCreateAdditionalData().flavor private fun Sdk.isLocatedInsideModule(module: Module): Boolean { diff --git a/python/src/com/jetbrains/python/sdk/add/v2/conda/CondaViewModel.kt b/python/src/com/jetbrains/python/sdk/add/v2/conda/CondaViewModel.kt index 7a6171bc6ac3..3d241828ef76 100644 --- a/python/src/com/jetbrains/python/sdk/add/v2/conda/CondaViewModel.kt +++ b/python/src/com/jetbrains/python/sdk/add/v2/conda/CondaViewModel.kt @@ -15,8 +15,8 @@ import com.jetbrains.python.PyBundle.message import com.jetbrains.python.conda.loadLocalPythonCondaPath import com.jetbrains.python.errorProcessing.PyResult import com.jetbrains.python.newProjectWizard.projectPath.ProjectPathFlows -import com.jetbrains.python.packaging.conda.environmentYml.CondaEnvironmentYmlSdkUtils -import com.jetbrains.python.packaging.conda.environmentYml.format.CondaEnvironmentYmlParser +import com.intellij.python.community.impl.conda.environmentYml.CondaEnvironmentYmlSdkUtils +import com.intellij.python.community.impl.conda.environmentYml.format.CondaEnvironmentYmlParser import com.jetbrains.python.sdk.add.v2.FileSystem import com.jetbrains.python.sdk.add.v2.PathHolder import com.jetbrains.python.sdk.add.v2.PythonToolViewModel diff --git a/python/src/com/jetbrains/python/sdk/add/v2/models.kt b/python/src/com/jetbrains/python/sdk/add/v2/models.kt index c76f5b3a36fd..977d266d30eb 100644 --- a/python/src/com/jetbrains/python/sdk/add/v2/models.kt +++ b/python/src/com/jetbrains/python/sdk/add/v2/models.kt @@ -310,7 +310,7 @@ internal val

PythonAddInterpreterModel

.existingSdks: List>()?.map { it.sdkWrapper.sdk } ?: emptyList() internal suspend fun PythonAddInterpreterModel<*>.getBasePath(module: Module?): Path = withContext(Dispatchers.IO) { - val pyProjectTomlBased = module?.let { PyProjectToml.findFile(it)?.toNioPathOrNull()?.parent } + val pyProjectTomlBased = module?.let { PyProjectToml.findPyProjectTomlFile(it)?.virtualFile?.toNioPathOrNull()?.parent } pyProjectTomlBased ?: module?.baseDir?.path?.let { Path.of(it) } ?: projectPathFlows.projectPathWithDefault.first() } diff --git a/python/src/com/jetbrains/python/sdk/pipenv/PipEnvFileHelper.kt b/python/src/com/jetbrains/python/sdk/pipenv/PipEnvFileHelper.kt deleted file mode 100644 index ac18112fe04b..000000000000 --- a/python/src/com/jetbrains/python/sdk/pipenv/PipEnvFileHelper.kt +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.jetbrains.python.sdk.pipenv - -import com.intellij.openapi.module.Module -import com.intellij.openapi.projectRoots.Sdk -import com.intellij.openapi.vfs.StandardFileSystems -import com.intellij.openapi.vfs.VirtualFile -import com.jetbrains.python.sdk.associatedModulePath -import com.jetbrains.python.sdk.findAmongRoots -import org.jetbrains.annotations.ApiStatus.Internal - -@Internal -object PipEnvFileHelper { - const val PIP_FILE: String = "Pipfile" - const val PIP_FILE_LOCK: String = "Pipfile.lock" - - fun getPipFileLock(sdk: Sdk): VirtualFile? = - sdk.associatedModulePath?.let { StandardFileSystems.local().findFileByPath(it)?.findChild(PIP_FILE_LOCK) } - - suspend fun pipFile(module: Module): VirtualFile? = findAmongRoots(module, PIP_FILE) -} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/pipenv/PipEnvPipFileWatcher.kt b/python/src/com/jetbrains/python/sdk/pipenv/PipEnvPipFileWatcher.kt index 1bf77f3da4fd..529766af319a 100644 --- a/python/src/com/jetbrains/python/sdk/pipenv/PipEnvPipFileWatcher.kt +++ b/python/src/com/jetbrains/python/sdk/pipenv/PipEnvPipFileWatcher.kt @@ -5,7 +5,6 @@ import com.intellij.notification.NotificationGroupManager import com.intellij.notification.NotificationListener import com.intellij.notification.NotificationType import com.intellij.openapi.application.EDT -import com.intellij.openapi.application.edtWriteAction import com.intellij.openapi.application.writeAction import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor @@ -78,7 +77,7 @@ internal class PipEnvPipFileWatcher : EditorFactoryListener { private suspend fun notifyPipFileChanged(module: Module) { if (module.getUserData(notificationActive) == true) return val title = when { - getPipFileLock(module) == null -> PyBundle.message("python.sdk.pipenv.pip.file.lock.not.found") + findAmongRoots(module, PIP_FILE_LOCK) == null -> PyBundle.message("python.sdk.pipenv.pip.file.lock.not.found") else -> PyBundle.message("python.sdk.pipenv.pip.file.lock.out.of.date") } val content = PyBundle.message("python.sdk.pipenv.pip.file.notification.content") @@ -129,18 +128,15 @@ internal class PipEnvPipFileWatcher : EditorFactoryListener { private suspend fun isPipFileEditor(editor: Editor): Boolean { val file = editor.document.virtualFile ?: return false - if (file.name != PipEnvFileHelper.PIP_FILE) return false + if (file.name != PIP_FILE) return false val project = editor.project ?: return false val module = file.getModule(project) ?: return false - if (PipEnvFileHelper.pipFile(module) != file) return false + if (findAmongRoots(module, PIP_FILE) != file) return false return module.pythonSdk?.isPipEnv == true } private val Document.virtualFile: VirtualFile? get() = FileDocumentManager.getInstance().getFile(this) - private fun VirtualFile.getModule(project: Project): Module? = - ModuleUtil.findModuleForFile(this, project) - - private suspend fun getPipFileLock(module: Module): VirtualFile? = findAmongRoots(module, PipEnvFileHelper.PIP_FILE_LOCK) + private fun VirtualFile.getModule(project: Project): Module? = ModuleUtil.findModuleForFile(this, project) } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/pipenv/PipFileLockFile.kt b/python/src/com/jetbrains/python/sdk/pipenv/PipFileLockFile.kt new file mode 100644 index 000000000000..95b396136d34 --- /dev/null +++ b/python/src/com/jetbrains/python/sdk/pipenv/PipFileLockFile.kt @@ -0,0 +1,28 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.jetbrains.python.sdk.pipenv + +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.vfs.StandardFileSystems +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.python.requirements.icons.PythonRequirementsIcons +import com.jetbrains.python.requirements.PyDependenciesFile +import com.jetbrains.python.sdk.associatedModulePath +import org.jetbrains.annotations.ApiStatus.Internal +import javax.swing.Icon + +@Internal +const val PIP_FILE: String = "Pipfile" + +@Internal +const val PIP_FILE_LOCK: String = "Pipfile.lock" + +@Internal +class PipFileLockFile(override val virtualFile: VirtualFile) : PyDependenciesFile { + override val icon: Icon get() = PythonRequirementsIcons.Requirements +} + + +internal fun Sdk.findPipFileLockFile(): PipFileLockFile? { + val virtualFile = associatedModulePath?.let { StandardFileSystems.local().findFileByPath(it)?.findChild(PIP_FILE_LOCK) } + return virtualFile?.let { PipFileLockFile(it) } +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/pipenv/PipenvCommandExecutor.kt b/python/src/com/jetbrains/python/sdk/pipenv/PipenvCommandExecutor.kt index dffdf9215168..eeb5d8cd00c0 100644 --- a/python/src/com/jetbrains/python/sdk/pipenv/PipenvCommandExecutor.kt +++ b/python/src/com/jetbrains/python/sdk/pipenv/PipenvCommandExecutor.kt @@ -76,7 +76,7 @@ suspend fun setupPipEnv( basePythonBinaryPath: PythonBinary?, installPackages: Boolean, ): PyResult<@SystemDependent String> { - val pipfile = projectPath.resolve(PipEnvFileHelper.PIP_FILE) + val pipfile = projectPath.resolve(PIP_FILE) if (!pipfile.exists()) { // Currently, if a Pipenv file exists inside the user's home directory, then it will NOT create a new Pipenv in the current project diff --git a/python/src/com/jetbrains/python/sdk/poetry/PoetryPackageManager.kt b/python/src/com/jetbrains/python/sdk/poetry/PoetryPackageManager.kt index 4eb50777948a..818de1401f42 100644 --- a/python/src/com/jetbrains/python/sdk/poetry/PoetryPackageManager.kt +++ b/python/src/com/jetbrains/python/sdk/poetry/PoetryPackageManager.kt @@ -4,7 +4,7 @@ package com.jetbrains.python.sdk.poetry import com.intellij.openapi.module.Module import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk -import com.intellij.openapi.vfs.VirtualFile +import com.intellij.python.pyproject.PyProjectTomlFile import com.jetbrains.python.PyBundle import com.jetbrains.python.errorProcessing.PyResult import com.jetbrains.python.getOrNull @@ -219,7 +219,7 @@ class PoetryPackageManager(project: Project, sdk: Sdk) : PythonPackageManager(pr return PackageCollectionPackageStructureNode(declared, undeclared + standalonePackages) } - override fun getDependencyFile(): VirtualFile? { + override fun getDependencyFile(): PyProjectTomlFile? { val projectPathStr = sdk.associatedModulePath ?: return null val projectPath = Path.of(projectPathStr) return resolvePyProjectToml(projectPath) diff --git a/python/src/com/jetbrains/python/sdk/uv/UvPackageManager.kt b/python/src/com/jetbrains/python/sdk/uv/UvPackageManager.kt index 48986e5c49e2..27a02e18e139 100644 --- a/python/src/com/jetbrains/python/sdk/uv/UvPackageManager.kt +++ b/python/src/com/jetbrains/python/sdk/uv/UvPackageManager.kt @@ -8,9 +8,9 @@ import com.intellij.openapi.progress.runBlockingMaybeCancellable import com.intellij.openapi.project.Project import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.ModuleRootManager -import com.intellij.openapi.vfs.VirtualFile import com.intellij.python.community.impl.uv.common.UV_TOOL_ID import com.intellij.python.pyproject.PyProjectToml +import com.intellij.python.pyproject.PyProjectTomlFile import com.intellij.python.pyproject.model.internal.workspaceBridge.getToolWorkspaceLayout import com.intellij.util.cancelOnDispose import com.jetbrains.python.PyBundle.message @@ -270,17 +270,17 @@ class UvPackageManager internal constructor(project: Project, sdk: Sdk, uvExecut override fun updateLockedAction(): suspend () -> PyResult = suspend { syncLocked().mapSuccess { } } private suspend fun resolvePackageName(module: Module): String { - val pyProjectFile = PyProjectToml.findFile(module) ?: return module.name - return PyProjectToml.parseCached(module.project, pyProjectFile)?.project?.name ?: module.name + val pyProjectFile = PyProjectToml.findPyProjectTomlFile(module) ?: return module.name + return PyProjectToml.parseCached(module.project, pyProjectFile.virtualFile)?.project?.name ?: module.name } // TODO PY-87712 Double check for remotes - override fun getDependencyFile(): VirtualFile? { + override fun getDependencyFile(): PyProjectTomlFile? { val uvWorkingDirectory = runBlockingMaybeCancellable { uvExecutionContextDeferred.await().workingDir } return resolvePyProjectToml(uvWorkingDirectory) } - override suspend fun getDependencyFiles(): List { + override suspend fun getDependencyFiles(): List { val rootFile = getDependencyFile() ?: return emptyList() val uvWorkingDirectory = uvExecutionContextDeferred.await().workingDir val memberModules = readAction { @@ -289,7 +289,7 @@ class UvPackageManager internal constructor(project: Project, sdk: Sdk, uvExecut } ?: return@readAction emptyList() rootModule.getToolWorkspaceLayout(UV_TOOL_ID)?.memberModules.orEmpty() } - val memberFiles = memberModules.mapNotNull { PyProjectToml.findFile(it) } + val memberFiles = memberModules.mapNotNull { PyProjectToml.findPyProjectTomlFile(it) } return listOf(rootFile) + memberFiles } diff --git a/python/testSrc/com/jetbrains/python/packaging/management/TestPackageManager.kt b/python/testSrc/com/jetbrains/python/packaging/management/TestPackageManager.kt index 5e363f6bb651..34820539f930 100644 --- a/python/testSrc/com/jetbrains/python/packaging/management/TestPackageManager.kt +++ b/python/testSrc/com/jetbrains/python/packaging/management/TestPackageManager.kt @@ -16,9 +16,13 @@ import com.jetbrains.python.packaging.common.PythonPackageDetails import com.jetbrains.python.packaging.common.PythonRepositoryPackageSpecification import com.jetbrains.python.packaging.common.toPythonPackage import com.jetbrains.python.packaging.common.toPythonPackages -import com.jetbrains.python.packaging.conda.environmentYml.format.CondaEnvironmentYmlParser +import com.intellij.python.community.impl.conda.environmentYml.CondaEnvironmentYmlFile +import com.intellij.python.community.impl.conda.environmentYml.format.CondaEnvironmentYmlParser import com.jetbrains.python.packaging.setupPy.SetupPyHelpers import com.jetbrains.python.psi.PyFile +import com.jetbrains.python.requirements.PyDependenciesFile +import com.jetbrains.python.requirements.RequirementsTxtFile +import com.jetbrains.python.requirements.SetupPyFile import com.jetbrains.python.sdk.associatedModuleDir import org.jetbrains.annotations.TestOnly @@ -80,14 +84,14 @@ class TestPythonPackageManager(project: Project, sdk: Sdk) : PythonPackageManage return PyResult.success(listInstalledPackagesSnapshot()) } - override fun getDependencyFile(): VirtualFile? { + override fun getDependencyFile(): PyDependenciesFile? { val providerType = sdk.getUserData(REQUIREMENTS_PROVIDER_KEY) ?: return null val moduleDir = sdk.associatedModuleDir ?: return null return when (providerType) { - RequirementsProviderType.REQUIREMENTS_TXT -> moduleDir.findChild("requirements.txt") - RequirementsProviderType.SETUP_PY -> moduleDir.findChild("setup.py") - RequirementsProviderType.ENVIRONMENT_YML -> moduleDir.findChild("environment.yml") + RequirementsProviderType.REQUIREMENTS_TXT -> moduleDir.findChild("requirements.txt")?.let { RequirementsTxtFile(it) } + RequirementsProviderType.SETUP_PY -> moduleDir.findChild("setup.py")?.let { SetupPyFile(it) } + RequirementsProviderType.ENVIRONMENT_YML -> moduleDir.findChild("environment.yml")?.let { CondaEnvironmentYmlFile(it) } } }