diff --git a/python/helpers/syspath.py b/python/helpers/syspath.py index 762e2a33e6b2..b2707a441ce5 100644 --- a/python/helpers/syspath.py +++ b/python/helpers/syspath.py @@ -4,8 +4,48 @@ import sys _helpers_root = os.path.dirname(os.path.abspath(__file__)) _working_dir = os.getcwd() -for root in sys.path: - # The current working dir is not automatically included but can be if PYTHONPATH - # contains an empty entry. - if root != _helpers_root and root != os.curdir and root != _working_dir: - print(root) + +def get_pep660_editable_roots_from_metadata(): + try: + # importlib.metadata was added in 3.8 + from importlib.metadata import distributions, Distribution + except ImportError: + return [] + + def read_from_direct_url_json(dist): + # type: (Distribution) -> str | None + + import json + direct_url_file = dist._path / 'direct_url.json' + if direct_url_file.exists(): + with direct_url_file.open() as f: + data = json.load(f) + if data.get('dir_info', {}).get('editable'): + return data.get('url', '') + return None + + editable_roots = [] + for dist in distributions(): + url = None # type: str | None + try: + # 'origin' was added in Python 3.13 + if dist.origin.dir_info.editable: + url = dist.origin.url + except AttributeError: + url = read_from_direct_url_json(dist) + + FILE_PREFIX = "file://" + if url and url.startswith(FILE_PREFIX): + editable_roots.append(url[len(FILE_PREFIX):]) + return editable_roots + + +if __name__ == "__main__": + for root in sys.path: + # The current working dir is not automatically included but can be if PYTHONPATH + # contains an empty entry. + if root != _helpers_root and root != os.curdir and root != _working_dir: + print(root) + + for editable_root in get_pep660_editable_roots_from_metadata(): + print(editable_root) diff --git a/python/python-exec-service/src/com/intellij/python/community/execService/api.kt b/python/python-exec-service/src/com/intellij/python/community/execService/api.kt index 97d93cd04d52..75e42fd11d73 100644 --- a/python/python-exec-service/src/com/intellij/python/community/execService/api.kt +++ b/python/python-exec-service/src/com/intellij/python/community/execService/api.kt @@ -237,8 +237,10 @@ class Args(vararg initialArgs: String) { return this } + fun addArgs(args: List): Args = addArgs(*args.toTypedArray()) + /** - * This file will be copied to remote machine and its remote name will be added to the list of arguments. + * This file will be copied to remote machine, and its remote name will be added to the list of arguments. * Use [argGenerator] to modify name */ fun addLocalFile(localFile: Path, argGenerator: FileArgGenerator = FileArgGenerator { it }): Args { @@ -267,5 +269,3 @@ class Args(vararg initialArgs: String) { } } } - -fun Args.addArgs(args: List): Args = addArgs(*args.toTypedArray()) \ No newline at end of file diff --git a/python/python-psi-impl/src/com/jetbrains/python/PyAdditionalLibraryRootsProvider.kt b/python/python-psi-impl/src/com/jetbrains/python/PyAdditionalLibraryRootsProvider.kt new file mode 100644 index 000000000000..0239f74cab37 --- /dev/null +++ b/python/python-psi-impl/src/com/jetbrains/python/PyAdditionalLibraryRootsProvider.kt @@ -0,0 +1,22 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.jetbrains.python + +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.AdditionalLibraryRootsProvider +import com.intellij.openapi.roots.SyntheticLibrary +import com.intellij.openapi.vfs.VirtualFile +import org.jetbrains.annotations.Unmodifiable + +// Notes (please do not remove): +// - If you think you want to add library roots here: Consider extending file community/python/helpers/syspath.py instead. +// - If used, this file needs to be added to community/python/python-psi-impl/resources/intellij.python.psi.impl.xml. +@Suppress("unused") +internal class PyAdditionalLibraryRootsProvider : AdditionalLibraryRootsProvider() { + override fun getRootsToWatch(project: Project): @Unmodifiable Collection { + return emptyList() + } + + override fun getAdditionalProjectLibraries(project: Project): Collection { + return emptyList() + } +} diff --git a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/RootVisitor.java b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/RootVisitor.java index 27e96210f751..b5daa9e67e92 100644 --- a/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/RootVisitor.java +++ b/python/python-psi-impl/src/com/jetbrains/python/psi/resolve/RootVisitor.java @@ -29,6 +29,7 @@ public interface RootVisitor { * @param root what we're visiting. * @param module the module to which the root belongs, or null * @param sdk the SDK to which the root belongs, or null + * @param isModuleSource true iff the root belongs to the module in case both module and sdk are present * * @return false when visiting must stop. */ diff --git a/python/src/com/jetbrains/python/packaging/management/PythonPackageInstallRequest.kt b/python/src/com/jetbrains/python/packaging/management/PythonPackageInstallRequest.kt index 751a49327180..951db163420a 100644 --- a/python/src/com/jetbrains/python/packaging/management/PythonPackageInstallRequest.kt +++ b/python/src/com/jetbrains/python/packaging/management/PythonPackageInstallRequest.kt @@ -1,12 +1,13 @@ // 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.management +import com.intellij.openapi.util.NlsSafe import com.jetbrains.python.packaging.common.PythonRepositoryPackageSpecification import org.jetbrains.annotations.ApiStatus import java.net.URI @ApiStatus.Internal -sealed class PythonPackageInstallRequest(val title: String) { +sealed class PythonPackageInstallRequest(val title: @NlsSafe String) { data class ByLocation(val location: URI) : PythonPackageInstallRequest(location.toString()) data class ByRepositoryPythonPackageSpecifications(val specifications: List) : PythonPackageInstallRequest( specifications.joinToString(", ") { it.nameWithVersionSpec }) diff --git a/python/src/com/jetbrains/python/packaging/pip/PipPackageManagerEngine.kt b/python/src/com/jetbrains/python/packaging/pip/PipPackageManagerEngine.kt index 92b8595020ce..a6c0be6390c4 100644 --- a/python/src/com/jetbrains/python/packaging/pip/PipPackageManagerEngine.kt +++ b/python/src/com/jetbrains/python/packaging/pip/PipPackageManagerEngine.kt @@ -103,10 +103,10 @@ class PipPackageManagerEngine( runPackagingTool(operation, Args(*arguments.toTypedArray())) - private fun partitionPackagesBySource(installRequest: PythonPackageInstallRequest): List> { + private fun partitionPackagesBySource(installRequest: PythonPackageInstallRequest): List { when (installRequest) { is PythonPackageInstallRequest.ByLocation -> { - return listOf(listOf(installRequest.location.toString())) + return listOf(Args(installRequest.location.toString())) } is PythonPackageInstallRequest.ByRepositoryPythonPackageSpecifications -> { return partitionPackagesBySource(installRequest.specifications) @@ -114,7 +114,7 @@ class PipPackageManagerEngine( } } - private fun partitionPackagesBySource(specifications: List): List> { + private fun partitionPackagesBySource(specifications: List): List { val (pypiSpecs, nonPypi) = specifications.partition { val url = it.repository.urlForInstallation?.toString() url == null || url == PyPIPackageUtil.PYPI_LIST_URL @@ -127,25 +127,27 @@ class PipPackageManagerEngine( return@mapNotNull null } - listOf( + val argsStr = listOf( "--index-url", url ) + specs.map { it.nameWithVersionSpec } + + Args().addArgs(argsStr) } - val pypi = mutableListOf>() + val pypi = mutableListOf() if (pypiSpecs.isNotEmpty()) { - pypi.add(pypiSpecs.map { it.nameWithVersionsSpec }) + pypi.add(Args().addArgs(pypiSpecs.map { it.nameWithVersionsSpec })) } return pypi + byRepository } - suspend fun performInstall(argumentsGroups: List>, options: List): PyResult { + suspend fun performInstall(argumentsGroups: List, options: List): PyResult { for (argumentsGroup in argumentsGroups) { val result = runPackagingTool( operation = "install", - arguments = argumentsGroup + options + arguments = argumentsGroup.addArgs(options) ) result.onFailure { diff --git a/python/src/com/jetbrains/python/spellchecker/PythonSpellcheckerGenerateDictionariesAction.java b/python/src/com/jetbrains/python/spellchecker/PythonSpellcheckerGenerateDictionariesAction.java index f01f39ee4555..cb80f450b207 100644 --- a/python/src/com/jetbrains/python/spellchecker/PythonSpellcheckerGenerateDictionariesAction.java +++ b/python/src/com/jetbrains/python/spellchecker/PythonSpellcheckerGenerateDictionariesAction.java @@ -10,6 +10,7 @@ import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.ModuleRootManager; import com.intellij.openapi.roots.OrderRootType; import com.intellij.openapi.vfs.VirtualFile; +import com.jetbrains.python.PyNames; import com.jetbrains.python.sdk.legacy.PythonSdkUtil; import org.jetbrains.annotations.NotNull; @@ -38,9 +39,9 @@ public class PythonSpellcheckerGenerateDictionariesAction extends AnAction { if (root.getName().equals("Lib")) { generator.addFolder("python", root); generator.excludeFolder(root.findChild("test")); - generator.excludeFolder(root.findChild("site-packages")); + generator.excludeFolder(root.findChild(PyNames.SITE_PACKAGES)); } - else if (root.getName().equals("site-packages")) { + else if (root.getName().equals(PyNames.SITE_PACKAGES)) { VirtualFile djangoRoot = root.findChild("django"); if (djangoRoot != null) { generator.addFolder("django", djangoRoot);