PY-87404 add PyDependenciesFile entity for SDK dependency declarations

Add the PyDependenciesFile interface in intellij.python.requirements
together with its implementations:

  - RequirementsTxtFile, SetupPyFile (intellij.python.requirements)
  - PyProjectTomlFile (intellij.python.pyproject)
  - CondaEnvironmentYmlFile (intellij.python.community.impl.conda;
    renamed from CondaEnvironmentYmlSdkUtils.kt)
  - PipFileLockFile (intellij.python.community.impl;
    replaces PipEnvFileHelper)

Each implementation owns its display icon (requirements/toml/yaml
SVGs are colocated with the new owners, with generated icon classes
PythonRequirementsIcons/PythonPyprojectIcons.Toml/
PythonCommunityImplCondaIcons.Yaml).

Wire the entity into the packaging API: PythonPackageManager exposes
an open getDependencyFile() hook, and the pip, conda, poetry, uv,
hatch, and pipenv managers each return the appropriate concrete
PyDependenciesFile (or null when none applies). Update SDK-extension
helpers (associatedModuleDir moves from PySdkExt to SdkExt so the
shared module can resolve dependency paths) and rename
PyProjectToml.findFile -> findPyProjectTomlFile to return the new
typed entity. Refresh callers across configuration, conda, poetry,
and v2 SDK-add flows accordingly, plus the in-memory
TestPackageManager.

GitOrigin-RevId: 64abfc53fe94aa3a221be293200e11cb9abbc432
This commit is contained in:
Vitaly Legchilkin
2026-05-11 23:39:51 +00:00
committed by intellij-monorepo-bot
parent 334ace79a0
commit 0460aa53f0
41 changed files with 288 additions and 128 deletions
@@ -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);
}
@@ -0,0 +1,7 @@
<!-- Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 2C11.5523 2 12 2.44772 12 3V9H11C10.4477 9 10 9.44772 10 10V14H5C4.44772 14 4 13.5523 4 13V6H6C7.10457 6 8 5.10457 8 4V2H11Z" fill="#FFF7F7"/>
<path d="M7 4C7 4.06893 6.99277 4.13617 6.97949 4.20117C6.88625 4.65686 6.48325 5 6 5H4.41406L7 2.41406V4Z" fill="#FFF7F7"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 1C12.1046 1 13 1.89543 13 3V9H12V3C12 2.44772 11.5523 2 11 2H8V4C8 5.10457 7.10457 6 6 6H4V13C4 13.5523 4.44772 14 5 14H10V15H5C3.89543 15 3 14.1046 3 13V5.41406C3.00004 5.1489 3.10547 4.89453 3.29297 4.70703L6.70703 1.29297C6.89453 1.10547 7.1489 1.00004 7.41406 1H11ZM4.41406 5H6C6.55228 5 7 4.55228 7 4V2.41406L4.41406 5Z" fill="#DB3B4B"/>
<path d="M12.4018 16V13.827L10.647 10.014H11.7212L12.656 12.0886C12.727 12.2526 12.7872 12.4139 12.8364 12.5724C12.8856 12.7309 12.9184 12.8539 12.9348 12.9414C12.9566 12.8539 12.9894 12.7309 13.0332 12.5724C13.0824 12.4139 13.1425 12.2526 13.2136 12.0886L14.1238 10.014H15.1898L13.435 13.827V16H12.4018Z" fill="#6C707E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,7 @@
<!-- Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 2C11.5523 2 12 2.44772 12 3V9H11C10.4477 9 10 9.44772 10 10V14H5C4.44772 14 4 13.5523 4 13V6H6C7.10457 6 8 5.10457 8 4V2H11Z" fill="#402929"/>
<path d="M7 4C7 4.06893 6.99277 4.13617 6.97949 4.20117C6.88625 4.65686 6.48325 5 6 5H4.41406L7 2.41406V4Z" fill="#402929"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 1C12.1046 1 13 1.89543 13 3V9H12V3C12 2.44772 11.5523 2 11 2H8V4C8 5.10457 7.10457 6 6 6H4V13C4 13.5523 4.44772 14 5 14H10V15H5C3.89543 15 3 14.1046 3 13V5.41406C3.00004 5.1489 3.10547 4.89453 3.29297 4.70703L6.70703 1.29297C6.89453 1.10547 7.1489 1.00004 7.41406 1H11ZM4.41406 5H6C6.55228 5 7 4.55228 7 4V2.41406L4.41406 5Z" fill="#DB5C5C"/>
<path d="M12.4018 16V13.827L10.647 10.014H11.7212L12.656 12.0886C12.727 12.2526 12.7872 12.4139 12.8364 12.5724C12.8856 12.7309 12.9184 12.8539 12.9348 12.9414C12.9566 12.8539 12.9894 12.7309 13.0332 12.5724C13.0824 12.4139 13.1425 12.2526 13.2136 12.0886L14.1238 10.014H15.1898L13.435 13.827V16H12.4018Z" fill="#CED0D6"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -1,8 +1,11 @@
<idea-plugin visibility="internal">
<dependencies>
<!-- region Generated dependencies - run `Generate Product Layouts` to regenerate -->
<module name="intellij.python.community"/>
<module name="intellij.python.processOutput.common"/>
<module name="intellij.python.requirements"/>
<module name="intellij.python.sdk"/>
<!-- endregion -->
</dependencies>
<extensions defaultExtensionNs="com.intellij">
<python.processOutput.common.processOutputIconMapping implementation="com.intellij.python.community.impl.conda.CondaIconMapping"/>
@@ -0,0 +1 @@
command.name.add.package.to.conda.environments.yml=Add a package to conda environments.yml
@@ -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)
}
}
@@ -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
@@ -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);
}
@@ -0,0 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 4C1 2.89543 1.89543 2 3 2H13C14.1046 2 15 2.89543 15 4V12C15 13.1046 14.1046 14 13 14H3C1.89543 14 1 13.1046 1 12V4Z" fill="#FFF4EB"/>
<path d="M3.5 13.5H2.5C1.94772 13.5 1.5 13.0523 1.5 12.5V3.5C1.5 2.94771 1.94772 2.5 2.5 2.5H3.5" stroke="#E56D17" stroke-linecap="round"/>
<path d="M12.5 13.5H13.5C14.0523 13.5 14.5 13.0523 14.5 12.5V3.5C14.5 2.94771 14.0523 2.5 13.5 2.5H12.5" stroke="#E56D17" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 5H5V4H11V5H8.5V12H7.5V5Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 632 B

@@ -0,0 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 4C1 2.89543 1.89543 2 3 2H13C14.1046 2 15 2.89543 15 4V12C15 13.1046 14.1046 14 13 14H3C1.89543 14 1 13.1046 1 12V4Z" fill="#45322B"/>
<path d="M3.5 13.5H2.5C1.94772 13.5 1.5 13.0523 1.5 12.5V3.5C1.5 2.94771 1.94772 2.5 2.5 2.5H3.5" stroke="#C77D55" stroke-linecap="round"/>
<path d="M12.5 13.5H13.5C14.0523 13.5 14.5 13.0523 14.5 12.5V3.5C14.5 2.94771 14.0523 2.5 13.5 2.5H12.5" stroke="#C77D55" stroke-linecap="round"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 5H5V4H11V5H8.5V12H7.5V5Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 632 B

@@ -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() }
@@ -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
}
@@ -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);
}
@@ -0,0 +1,4 @@
@ApiStatus.Internal
package com.intellij.python.requirements.icons;
import org.jetbrains.annotations.ApiStatus;
@@ -0,0 +1,9 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.14453 10.0137C3.53799 10.0137 3.88262 10.0878 4.17773 10.2354C4.47823 10.3829 4.70773 10.5907 4.86621 10.8584C5.03016 11.1262 5.11225 11.4378 5.1123 11.793C5.1123 12.1866 5.01418 12.5343 4.81738 12.835C4.62058 13.1356 4.34928 13.3489 4.00488 13.4746L5.19434 16H4.0459L3.00488 13.6221H2.27539V16H1.25V10.0137H3.14453ZM2.27539 12.7285H3.14453C3.42861 12.7285 3.65248 12.6487 3.81641 12.4902C3.98586 12.3262 4.0703 12.1049 4.07031 11.8262C4.07031 11.5419 3.98587 11.3173 3.81641 11.1533C3.6525 10.9896 3.42847 10.9083 3.14453 10.9082H2.27539V12.7285Z" fill="#3574F0"/>
<path d="M9.85645 10.8994H7.27344V12.4658H9.56934V13.3516H7.27344V15.1143H9.85645V16H6.26562V10.0137H9.85645V10.8994Z" fill="#3574F0"/>
<path d="M13.4912 10C13.6387 10.0366 13.7766 10.0871 13.9043 10.1533C14.1886 10.3009 14.4108 10.5085 14.5693 10.7764C14.6883 10.9773 14.7624 11.2046 14.792 11.458C14.8022 11.5426 14.8066 11.6299 14.8066 11.7197V14.2939C14.8066 14.5107 14.7783 14.7092 14.7227 14.8896C14.7189 14.902 14.713 14.9136 14.709 14.9258L13.7812 13.417V11.7197C13.7812 11.5419 13.7497 11.3887 13.6875 11.2598C13.6505 11.1853 13.6025 11.12 13.5439 11.0635C13.4976 11.0155 13.4451 10.9758 13.3877 10.9424C13.2559 10.8707 13.0951 10.834 12.9043 10.834C12.7151 10.834 12.5563 10.8678 12.4277 10.9355C12.3649 10.9701 12.3078 11.0125 12.2568 11.0635L12.2031 11.126C12.1528 11.1911 12.1128 11.2646 12.084 11.3467C12.0467 11.4551 12.0273 11.5793 12.0273 11.7197V14.2939C12.0273 14.4397 12.049 14.5687 12.0908 14.6816C12.1305 14.7864 12.188 14.8762 12.2646 14.9502C12.3107 14.9979 12.363 15.037 12.4199 15.0703C12.552 15.1424 12.7129 15.1797 12.9043 15.1797C13.0105 15.1797 13.1064 15.1662 13.1934 15.1445L12.4893 14H13.5938L14.8193 15.999H13.7197L13.6904 15.9521C13.641 15.9703 13.59 15.9856 13.5381 16H12.2656C12.1376 15.9646 12.0169 15.9188 11.9043 15.8604C11.62 15.7128 11.3978 15.5052 11.2393 15.2373C11.0807 14.9694 11.002 14.6547 11.002 14.2939V11.7197C11.002 11.3535 11.0817 11.0388 11.2402 10.7764C11.3988 10.5087 11.6202 10.3008 11.9043 10.1533C12.032 10.0871 12.1698 10.0366 12.3174 10H13.4912Z" fill="#3574F0"/>
<path d="M12.2041 2.01074C13.2128 2.113 14 2.96435 14 4V9H13V4C13 3.44772 12.5523 3 12 3H4C3.44772 3 3 3.44772 3 4V9H2V4C2 2.89543 2.89543 2 4 2H12L12.2041 2.01074Z" fill="#6C707E"/>
<path d="M10.5 8C10.7761 8 11 8.22386 11 8.5C11 8.77614 10.7761 9 10.5 9H5.5C5.22386 9 5 8.77614 5 8.5C5 8.22386 5.22386 8 5.5 8H10.5Z" fill="#6C707E"/>
<path d="M11 5.5C11 5.77614 10.7761 6 10.5 6H5.5C5.22386 6 5 5.77614 5 5.5C5 5.22386 5.22386 5 5.5 5H10.5C10.7761 5 11 5.22386 11 5.5Z" fill="#6C707E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 3C12.5523 3 13 3.44772 13 4V9H10.5C10.7761 9 11 8.77614 11 8.5C11 8.22386 10.7761 8 10.5 8H5.5C5.22386 8 5 8.22386 5 8.5C5 8.77614 5.22386 9 5.5 9H3V4C3 3.44772 3.44772 3 4 3H12ZM5.5 5C5.22386 5 5 5.22386 5 5.5C5 5.77614 5.22386 6 5.5 6H10.5C10.7761 6 11 5.77614 11 5.5C11 5.22386 10.7761 5 10.5 5H5.5Z" fill="#EBECF0"/>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -0,0 +1,9 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.14453 10.0137C3.53799 10.0137 3.88262 10.0878 4.17773 10.2354C4.47823 10.3829 4.70773 10.5907 4.86621 10.8584C5.03016 11.1262 5.11225 11.4378 5.1123 11.793C5.1123 12.1866 5.01418 12.5343 4.81738 12.835C4.62058 13.1356 4.34928 13.3489 4.00488 13.4746L5.19434 16H4.0459L3.00488 13.6221H2.27539V16H1.25V10.0137H3.14453ZM2.27539 12.7285H3.14453C3.42861 12.7285 3.65248 12.6487 3.81641 12.4902C3.98586 12.3262 4.0703 12.1049 4.07031 11.8262C4.07031 11.5419 3.98587 11.3173 3.81641 11.1533C3.6525 10.9896 3.42847 10.9083 3.14453 10.9082H2.27539V12.7285Z" fill="#548AF7"/>
<path d="M9.85645 10.8994H7.27344V12.4658H9.56934V13.3516H7.27344V15.1143H9.85645V16H6.26562V10.0137H9.85645V10.8994Z" fill="#548AF7"/>
<path d="M13.4912 10C13.6387 10.0366 13.7766 10.0871 13.9043 10.1533C14.1886 10.3009 14.4108 10.5085 14.5693 10.7764C14.6883 10.9773 14.7624 11.2046 14.792 11.458C14.8022 11.5426 14.8066 11.6299 14.8066 11.7197V14.2939C14.8066 14.5107 14.7783 14.7092 14.7227 14.8896C14.7189 14.902 14.713 14.9136 14.709 14.9258L13.7812 13.417V11.7197C13.7812 11.5419 13.7497 11.3887 13.6875 11.2598C13.6505 11.1853 13.6025 11.12 13.5439 11.0635C13.4976 11.0155 13.4451 10.9758 13.3877 10.9424C13.2559 10.8707 13.0951 10.834 12.9043 10.834C12.7151 10.834 12.5563 10.8678 12.4277 10.9355C12.3649 10.9701 12.3078 11.0125 12.2568 11.0635L12.2031 11.126C12.1528 11.1911 12.1128 11.2646 12.084 11.3467C12.0467 11.4551 12.0273 11.5793 12.0273 11.7197V14.2939C12.0273 14.4397 12.049 14.5687 12.0908 14.6816C12.1305 14.7864 12.188 14.8762 12.2646 14.9502C12.3107 14.9979 12.363 15.037 12.4199 15.0703C12.552 15.1424 12.7129 15.1797 12.9043 15.1797C13.0105 15.1797 13.1064 15.1662 13.1934 15.1445L12.4893 14H13.5938L14.8193 15.999H13.7197L13.6904 15.9521C13.641 15.9703 13.59 15.9856 13.5381 16H12.2656C12.1376 15.9646 12.0169 15.9188 11.9043 15.8604C11.62 15.7128 11.3978 15.5052 11.2393 15.2373C11.0807 14.9694 11.002 14.6547 11.002 14.2939V11.7197C11.002 11.3535 11.0817 11.0388 11.2402 10.7764C11.3988 10.5087 11.6202 10.3008 11.9043 10.1533C12.032 10.0871 12.1698 10.0366 12.3174 10H13.4912Z" fill="#548AF7"/>
<path d="M12.2041 2.01074C13.2128 2.113 14 2.96435 14 4V9H13V4C13 3.44772 12.5523 3 12 3H4C3.44772 3 3 3.44772 3 4V9H2V4C2 2.89543 2.89543 2 4 2H12L12.2041 2.01074Z" fill="#CED0D6"/>
<path d="M10.5 8C10.7761 8 11 8.22386 11 8.5C11 8.77614 10.7761 9 10.5 9H5.5C5.22386 9 5 8.77614 5 8.5C5 8.22386 5.22386 8 5.5 8H10.5Z" fill="#CED0D6"/>
<path d="M11 5.5C11 5.77614 10.7761 6 10.5 6H5.5C5.22386 6 5 5.77614 5 5.5C5 5.22386 5.22386 5 5.5 5H10.5C10.7761 5 11 5.22386 11 5.5Z" fill="#CED0D6"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 3C12.5523 3 13 3.44772 13 4V9H10.5C10.7761 9 11 8.77614 11 8.5C11 8.22386 10.7761 8 10.5 8H5.5C5.22386 8 5 8.22386 5 8.5C5 8.77614 5.22386 9 5.5 9H3V4C3 3.44772 3.44772 3 4 3H12ZM5.5 5C5.22386 5 5 5.22386 5 5.5C5 5.77614 5.22386 6 5.5 6H10.5C10.7761 6 11 5.77614 11 5.5C11 5.22386 10.7761 5 10.5 5H5.5Z" fill="#43454A"/>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -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
}
@@ -0,0 +1,4 @@
@ApiStatus.Internal
package com.intellij.python.sdk.icons;
import org.jetbrains.annotations.ApiStatus;
@@ -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<Path> = Key.create("PYTHON_PROJECT_BASE_PATH")
@@ -112,4 +118,36 @@ val Sdk.targetEnvConfiguration: TargetEnvironmentConfiguration?
@Internal
fun Sdk.pyInterpreterPresentation(customName: String? = null): PythonInterpreterPresentation = buildPresentationInfo(customName)
fun Sdk.pyInterpreterPresentation(customName: String? = null): PythonInterpreterPresentation = buildPresentationInfo(customName)
private val SDK_ERROR_REPORTED = Key.create<Boolean>("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
@@ -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
@@ -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)
@@ -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<Unit> {
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<Unit> {
@@ -151,16 +152,14 @@ class CondaPackageManager(project: Project, sdk: Sdk) : PythonPackageManager(pro
override suspend fun listDeclaredPackages(): PyResult<List<PythonPackage>>? {
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)
}
}
@@ -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
@@ -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<List<PythonPackage>>? {
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<VirtualFile> = listOfNotNull(getDependencyFile())
protected open suspend fun getDependencyFiles(): List<PyDependenciesFile> = listOfNotNull(getDependencyFile())
/**
* Adds a dependency to the project's dependency declaration file.
@@ -470,13 +472,10 @@ fun PythonPackageManager.listDeclaredPackagesAsync(): List<PythonPackage>? = 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
@@ -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<Unit> {
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<List<PythonPackage>>? {
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)
}
}
}
@@ -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<List<PythonPackage>>? {
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()
}
@@ -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<PipEnvPackageManager, String>() {
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()
}
@@ -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
@@ -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)
@@ -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(
@@ -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)
}
@@ -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<Boolean>("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>): 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 {
@@ -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
@@ -310,7 +310,7 @@ internal val <P : PathHolder> PythonAddInterpreterModel<P>.existingSdks: List<Sd
get() = allInterpreters.value?.filterIsInstance<ExistingSelectableInterpreter<P>>()?.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()
}
@@ -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)
}
@@ -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)
}
@@ -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) }
}
@@ -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
@@ -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)
@@ -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<Unit> = 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<VirtualFile> {
override suspend fun getDependencyFiles(): List<PyProjectTomlFile> {
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
}
@@ -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) }
}
}