system python providers for unix/mac/win/legacy platform; pyenv/brew system python providers; PY-47379

(cherry picked from commit 02c3c3f3e682aa60e48bd429b7862d431ce5ddac)

GitOrigin-RevId: f0b84f7603bc2d3604d797f9d078a25c54e1ec3e
This commit is contained in:
Aleksandr Sorotskii
2025-06-10 19:18:34 +02:00
committed by intellij-monorepo-bot
parent ef37368a9a
commit 363175ed94
18 changed files with 450 additions and 19 deletions

View File

@@ -139,8 +139,8 @@
<applicationService serviceImplementation="com.jetbrains.python.packaging.PyPackageService"/>
<applicationService serviceInterface="com.jetbrains.python.module.PyModuleService"
serviceImplementation="com.jetbrains.python.module.PyModuleServiceImpl"/>
<applicationService serviceInterface="com.jetbrains.python.sdk.flavors.WinRegistryService"
serviceImplementation="com.jetbrains.python.sdk.flavors.WinRegistryServiceImpl"/>
<applicationService serviceInterface="com.jetbrains.python.sdk.WinRegistryService"
serviceImplementation="com.jetbrains.python.sdk.WinRegistryServiceImpl"/>
<typedHandler implementation="com.jetbrains.python.codeInsight.PyMethodNameTypedHandler" id="pyMethodNameTypedHandler"/>
<idIndexer filetype="Python" implementationClass="com.jetbrains.python.PyIdIndexer"/>
@@ -459,6 +459,9 @@
<registryKey defaultValue="false" description="Don't run `lock` before listing dependencies when using Poetry"
restartRequired="false" key="python.poetry.list.packages.without.lock"/>
<registryKey defaultValue="false" description="Use legacy system python detection method based on flavors"
restartRequired="false" key="python.use.system.legacy.provider"/>
<!-- typing -->
<multiHostInjector implementation="com.jetbrains.python.codeInsight.typing.PyTypingAnnotationInjector"/>
@@ -896,6 +899,12 @@
<systemPythonProvider implementation="com.jetbrains.python.sdk.uv.UvSystemPythonProvider"/>
<systemPythonProvider implementation="com.intellij.python.community.services.systemPython.impl.providers.BrewSystemPythonProvider"/>
<systemPythonProvider implementation="com.intellij.python.community.services.systemPython.impl.providers.LegacySystemPythonProvider"/>
<systemPythonProvider implementation="com.intellij.python.community.services.systemPython.impl.providers.MacSystemPythonProvider"/>
<systemPythonProvider implementation="com.intellij.python.community.services.systemPython.impl.providers.PyenvSystemPythonProvider"/>
<systemPythonProvider implementation="com.intellij.python.community.services.systemPython.impl.providers.UnixSystemPythonProvider"/>
<systemPythonProvider implementation="com.intellij.python.community.services.systemPython.impl.providers.WindowsSystemPythonProvider"/>
<inspectionExtension implementation="com.jetbrains.python.sdk.configuration.PyInterpreterInspectionSuppressor"/>
<inspectionExtension implementation="com.jetbrains.python.inspections.PyUnresolvedReferenceDefaultInspectionExtension" order="last"/>
@@ -1120,4 +1129,4 @@
dynamic="true"
interface="com.intellij.python.community.services.systemPython.SystemPythonProvider"/>
</extensionPoints>
</idea-plugin>
</idea-plugin>

View File

@@ -1,5 +1,5 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.jetbrains.python.sdk.flavors
package com.jetbrains.python.sdk
import com.intellij.openapi.util.io.WindowsRegistryUtil
import org.jetbrains.annotations.ApiStatus
@@ -10,7 +10,6 @@ import org.jetbrains.annotations.ApiStatus
* @author Ilya.Kazakevich
*/
@ApiStatus.Internal
interface WinRegistryService {
/**
* @param basePath path like "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node"

View File

@@ -17,7 +17,7 @@ jvm_library(
name = "system-python",
module_name = "intellij.python.community.services.systemPython",
visibility = ["//visibility:public"],
srcs = glob(["src/**/*.kt", "src/**/*.java"], allow_empty = True),
srcs = glob(["gen/**/*.kt", "gen/**/*.java", "src/**/*.kt", "src/**/*.java"], allow_empty = True),
deps = [
"@lib//:kotlin-stdlib",
"//python/python-sdk:sdk",
@@ -33,6 +33,7 @@ jvm_library(
"//platform/util",
"//python/installer",
"//python/python-venv:community-impl-venv",
"//python/python-psi-impl:psi-impl",
],
exports = ["//python/services/shared"],
runtime_deps = [":system-python_resources"]
@@ -74,6 +75,7 @@ jvm_library(
"@lib//:io-mockk-jvm",
"//platform/testFramework",
"//platform/testFramework:testFramework_test_lib",
"//python/python-psi-impl:psi-impl",
],
runtime_deps = [
":system-python_resources",

View File

@@ -0,0 +1,19 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.community.services.systemPython;
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 PythonCommunityServicesSystemPythonIcons {
private static @NotNull Icon load(@NotNull String path, int cacheKey, int flags) {
return IconManager.getInstance().loadRasterizedIcon(path, PythonCommunityServicesSystemPythonIcons.class.getClassLoader(), cacheKey, flags);
}
/** 16x16 */ public static final @NotNull Icon Homebrew = load("icons/com/intellij/python/community/system/providers/expui/homebrew.svg", -2104807435, 0);
/** 16x16 */ public static final @NotNull Icon Pyenv = load("icons/com/intellij/python/community/system/providers/expui/pyenv.svg", -770384196, 0);
}

View File

@@ -3,6 +3,7 @@
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/testResources" type="java-test-resource" />
@@ -33,5 +34,6 @@
<orderEntry type="library" scope="TEST" name="io.mockk" level="project" />
<orderEntry type="library" scope="TEST" name="io.mockk.jvm" level="project" />
<orderEntry type="module" module-name="intellij.platform.testFramework" scope="TEST" />
<orderEntry type="module" module-name="intellij.python.psi.impl" />
</component>
</module>

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" fill-rule="evenodd" stroke-linejoin="round" clip-rule="evenodd" viewBox="0 0 180 271"><path fill="#D1D3D4" stroke="#000" stroke-width="4.79" d="M74.54 50.33c.74-23.487-7.172-37.334-29.917-46.481C38.158 26.463 64.848 47.314 74.54 50.33Z"/><path fill="#D1D3D4" stroke="#000" stroke-width="4.79" d="M136.223 90.134c.74-19.286-4.131-27.07-7.787-32.636-3.709-5.192-12.609-13.352-23.544-13.694-10.341-.324-12.828 5.392-22.368 5.737-1.564-17.372 11.547-26.905 11.547-26.905s-3.585-5.067-6.551-6.305c-4.266 3.791-16.008 16.436-13.291 32.708-8.778-1.678-14.974-6.702-24.195-4.888-12.562 2.472-22.917 10.067-28.819 26.733-5.901 16.667 2.083 36.111 3.819 41.664 1.737 5.558 14.584 29.164 24.653 32.638 10.067 3.475 10.07 1.909 15.452-.173 5.383-2.083 13.54-5.902 25.522-1.561 11.975 4.339 13.537 5.379 21 .519 7.469-4.859 17.188-19.962 19.443-24.303 2.257-4.339 3.473-6.597 4.17-8.853 7.256-3.621.283-3.397.949-20.681Z"/><path fill="#fff" fill-rule="nonzero" stroke="#000" stroke-width="4.79" d="M155.714 231.084a18 18 0 0 0 18.064-17.998c.002-18.273.002-46.51.002-64.833a18 18 0 0 0-18-18h-14.838a4 4 0 0 1-4-4v-9.43H19.912l.295 138.213a4.001 4.001 0 0 0 1.85 3.365c5.212 3.018 21.921 8.75 57.004 8.75 35.825 0 51.839-7.674 56.424-11.139a3.979 3.979 0 0 0 1.438-3.062c.019-3.825.019-12.688.019-17.924a3.999 3.999 0 0 1 4.083-3.999c4.401.062 9.674.071 14.689.057Zm-18.772-79.806c0-1.326.527-2.598 1.464-3.536a5.004 5.004 0 0 1 3.536-1.464h12.738c1.326 0 2.598.527 3.536 1.464a5.004 5.004 0 0 1 1.464 3.536v58.185a5.004 5.004 0 0 1-1.464 3.536 5.004 5.004 0 0 1-3.536 1.464h-12.738a5.004 5.004 0 0 1-3.536-1.464 5.004 5.004 0 0 1-1.464-3.536v-58.185Z"/><path fill="#FBB040" d="M31.449 115.663v123.986c0 1.431.764 2.753 2.003 3.467 5.061 2.412 19.743 8.169 45.484 8.169 25.912 0 40.158-6.995 44.928-9.878a3.987 3.987 0 0 0 1.827-3.351c.011-16.441.011-122.393.011-122.393H31.449Z"/><path fill="none" stroke="#FFDB96" stroke-linecap="round" stroke-linejoin="miter" stroke-width="11.96" d="M35.521.249v112.816" transform="matrix(1 0 0 .84931 9.782 136.452)"/><path fill="#fff" stroke="#000" stroke-linecap="round" stroke-width="4.79" d="M98.555 141.106c0-2-1.177-4.388-3.11-5.587a16.734 16.734 0 0 1-6.942-8.3c-25.591.861-48.949-3.633-60.716-6.806a98.218 98.218 0 0 0-9.171-2.13c-7.043-1.397-12.396-7.582-12.396-15.018 0-8.185 6.494-14.836 14.608-15.127 1.565-8.657 9.121-15.229 18.229-15.229 10.242 0 18.544 8.304 18.544 18.545 0 2.155-.389 4.218-1.064 6.144l.055.009c2.287-7.153 8.983-12.336 16.896-12.336 7.862 0 14.522 5.122 16.848 12.208a18.673 18.673 0 0 1-.101-1.947c0-10.652 8.634-19.286 19.284-19.286 8.622 0 15.919 5.659 18.388 13.462l1.681-.438a15.083 15.083 0 0 1 7.432-1.955c8.369 0 15.154 6.785 15.151 15.152 0 7.666-5.691 14.001-13.079 15.012a22.339 22.339 0 0 0-4.891 1.27c-4.391 1.655-8.881 3.027-13.417 4.159a16.66 16.66 0 0 1-1.909 6.231 20.723 20.723 0 0 0-2.38 11.257c-.002.236.007 1.472.007 1.71 0 4.956-4.016 7.972-8.976 7.972a8.971 8.971 0 0 1-8.971-8.972Z"/></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,6 @@
<!-- Copyright 2000-2024 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="M8 5C8.55228 5 9 4.55228 9 4C9 3.44772 8.55228 3 8 3C7.44772 3 7 3.44772 7 4C7 4.55228 7.44772 5 8 5Z" fill="#6C707E"/>
<path d="M9 12C9 12.5523 8.55228 13 8 13C7.44772 13 7 12.5523 7 12C7 11.4477 7.44772 11 8 11C8.55228 11 9 11.4477 9 12Z" fill="#6C707E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.5 1C5.11929 1 4 2.11929 4 3.5V4H3.5C2.11929 4 1 5.11929 1 6.5V9.5C1 10.8807 2.11929 12 3.5 12H4V12.5C4 13.8807 5.11929 15 6.5 15H9.5C10.8807 15 12 13.8807 12 12.5V12H12.5C13.8807 12 15 10.8807 15 9.5V6.5C15 5.11929 13.8807 4 12.5 4H12V3.5C12 2.11929 10.8807 1 9.5 1H6.5ZM5 3.5C5 2.67157 5.67157 2 6.5 2H9.5C10.3284 2 11 2.67157 11 3.5V6C11 6.82843 10.3284 7.5 9.5 7.5H6.5C5.11929 7.5 4 8.61929 4 10V11H3.5C2.67157 11 2 10.3284 2 9.5V6.5C2 5.67157 2.67157 5 3.5 5H4C4.55228 5 5 4.55228 5 4V3.5ZM5 12.5C5 13.3284 5.67157 14 6.5 14H9.5C10.3284 14 11 13.3284 11 12.5V12C11 11.4477 11.4477 11 12 11H12.5C13.3284 11 14 10.3284 14 9.5V6.5C14 5.67157 13.3284 5 12.5 5H12V6C12 7.38071 10.8807 8.5 9.5 8.5H6.5C5.67157 8.5 5 9.17157 5 10V12.5Z" fill="#6C707E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,45 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.community.services.systemPython.impl.providers
import com.intellij.openapi.diagnostic.Logger
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.isMac
import com.intellij.python.community.services.shared.UICustomization
import com.intellij.python.community.services.systemPython.PythonCommunityServicesSystemPythonIcons
import com.intellij.python.community.services.systemPython.SystemPythonProvider
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.PyResult
import java.nio.file.Path
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
private class BrewSystemPythonProvider : SystemPythonProvider {
private val LOGGER: Logger = Logger.getInstance(BrewSystemPythonProvider::class.java)
private val binDirectory = Path.of("/opt/homebrew/bin")
override suspend fun findSystemPythons(eelApi: EelApi): PyResult<Set<PythonBinary>> {
if (!eelApi.platform.isMac) {
return PyResult.success(emptySet())
}
val pythons = withContext(Dispatchers.IO) {
try {
return@withContext collectPythonsInPaths(eelApi, listOf(binDirectory), listOf(python3XNamePattern))
}
catch (e: RuntimeException) {
LOGGER.error("failed to discover brew pythons", e)
}
return@withContext emptySet()
}
return PyResult.success(pythons)
}
override val uiCustomization: UICustomization?
get() {
// TODO: proper icon
return UICustomization(title = "homebrew", icon = PythonCommunityServicesSystemPythonIcons.Homebrew)
}
}

View File

@@ -1,28 +1,33 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.community.services.systemPython.impl
package com.intellij.python.community.services.systemPython.impl.providers
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.provider.localEel
import com.intellij.python.community.services.systemPython.SystemPythonProvider
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.sdk.flavors.PythonSdkFlavor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
/**
* [SystemPythonProvider] based ob [PythonSdkFlavor] (kind of bridge)
* [SystemPythonProvider] based of [PythonSdkFlavor]
*/
internal object CoreSystemPythonProvider : SystemPythonProvider {
internal class LegacySystemPythonProvider : SystemPythonProvider {
override suspend fun findSystemPythons(eelApi: EelApi): PyResult<Set<PythonBinary>> {
if (eelApi != localEel) return Result.success(emptySet())
with(Dispatchers.IO) {
if (eelApi != localEel || !useLegacyPythonProvider()) {
return PyResult.success(emptySet())
}
val pythons = withContext(Dispatchers.IO) {
val paths = PythonSdkFlavor.getApplicableFlavors(false)
.flatMap {
it.dropCaches()
it.suggestLocalHomePaths(null, null)
}
return Result.success(paths.toSet())
return@withContext PyResult.success(paths.toSet())
}
return pythons
}
}

View File

@@ -0,0 +1,59 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.community.services.systemPython.impl.providers
import com.intellij.openapi.diagnostic.Logger
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.isMac
import com.intellij.python.community.services.shared.UICustomization
import com.intellij.python.community.services.systemPython.SystemPythonProvider
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.PyResult
import java.nio.file.Path
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
private class MacSystemPythonProvider : SystemPythonProvider {
private val LOGGER: Logger = Logger.getInstance(MacSystemPythonProvider::class.java)
private val directories = listOf(
Path.of("/usr/bin"),
Path.of("/usr/local/bin"),
Path.of("/usr/local/Cellar/python"),
Path.of("/Library/Frameworks/Python.framework/Versions"),
Path.of("/System/Library/Frameworks/Python.framework/Versions"),
)
// Patterns to match Python executable filenames
private val names = listOf(
python3NamePattern,
python3XNamePattern,
pypyNamePattern,
)
override suspend fun findSystemPythons(eelApi: EelApi): PyResult<Set<PythonBinary>> {
// Check if we're on a Unix system that's not Mac
if (!eelApi.platform.isMac || useLegacyPythonProvider()) {
return PyResult.success(emptySet())
}
val pythons = withContext(Dispatchers.IO) {
try {
return@withContext collectPythonsInPaths(eelApi, directories, names)
}
catch (e: RuntimeException) {
LOGGER.error("Failed to discover mac system pythons", e)
}
return@withContext emptySet()
}
return PyResult.success(pythons)
}
override val uiCustomization: UICustomization?
get() {
// TODO:
return null
}
}

View File

@@ -0,0 +1,64 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.community.services.systemPython.impl.providers
import com.intellij.openapi.diagnostic.Logger
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.getOrNull
import com.intellij.platform.eel.path.EelPath
import com.intellij.platform.eel.provider.asNioPath
import com.intellij.python.community.services.shared.UICustomization
import com.intellij.python.community.services.systemPython.PythonCommunityServicesSystemPythonIcons
import com.intellij.python.community.services.systemPython.SystemPythonProvider
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.PyResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
private class PyenvSystemPythonProvider : SystemPythonProvider {
private val LOGGER: Logger = Logger.getInstance(PyenvSystemPythonProvider::class.java)
override suspend fun findSystemPythons(eelApi: EelApi): PyResult<Set<PythonBinary>> {
if (useLegacyPythonProvider()) {
return PyResult.success(emptySet())
}
val pythons = withContext(Dispatchers.IO) {
try {
val env = eelApi.exec.fetchLoginShellEnvVariables()
val pyenvRoot = if ("PYENV_ROOT" in env) {
EelPath.parse(env["PYENV_ROOT"]!!, eelApi.descriptor)
}
else {
eelApi.userInfo.home.resolve(".pyenv")
}
val versionsDir = pyenvRoot.resolve("versions")
val entries = eelApi.fs.listDirectory(versionsDir)
.getOrNull()
if (entries == null) {
return@withContext emptySet<PythonBinary>()
}
val paths = entries
.map { versionsDir.resolve(it).resolve("bin").asNioPath() }
return@withContext collectPythonsInPaths(eelApi, paths, listOf(python3NamePattern))
}
catch (e: RuntimeException) {
LOGGER.error("failed to discover pyenv pythons", e)
}
return@withContext emptySet()
}
return PyResult.success(pythons)
}
override val uiCustomization: UICustomization?
get() {
// TODO: proper icon
return UICustomization(title = "pyenv", icon = PythonCommunityServicesSystemPythonIcons.Pyenv)
}
}

View File

@@ -0,0 +1,56 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.community.services.systemPython.impl.providers
import com.intellij.openapi.diagnostic.Logger
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.EelPlatform
import com.intellij.platform.eel.isMac
import com.intellij.python.community.services.systemPython.SystemPythonProvider
import com.intellij.python.community.services.shared.UICustomization
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.PyResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.nio.file.Path
private class UnixSystemPythonProvider : SystemPythonProvider {
private val LOGGER: Logger = Logger.getInstance(UnixSystemPythonProvider::class.java)
private val directories = listOf(
Path.of("/usr/bin"),
Path.of("/usr/local/bin"))
// Patterns to match Python executable filenames
private val names = listOf(
python3NamePattern,
python3XNamePattern,
pypyNamePattern,
)
override suspend fun findSystemPythons(eelApi: EelApi): PyResult<Set<PythonBinary>> {
// Check if we're on a Unix system that's not Mac
if (eelApi.platform !is EelPlatform.Posix || eelApi.platform.isMac || useLegacyPythonProvider()) {
return PyResult.success(emptySet())
}
val pythons = withContext(Dispatchers.IO) {
try {
return@withContext collectPythonsInPaths(eelApi, directories, names)
}
catch (e: RuntimeException) {
LOGGER.error("Failed to discover unix system pythons", e)
}
return@withContext emptySet()
}
return PyResult.success(pythons)
}
override val uiCustomization: UICustomization?
get() {
// TODO:
return null
}
}

View File

@@ -0,0 +1,44 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.community.services.systemPython.impl.providers
import com.intellij.openapi.util.registry.Registry
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.fs.EelFileSystemApi.StatError
import com.intellij.platform.eel.fs.stat
import com.intellij.platform.eel.getOrNull
import com.intellij.platform.eel.path.EelPath
import com.intellij.platform.eel.provider.asNioPath
import java.nio.file.Path
import java.util.regex.Pattern
import kotlin.collections.map
import kotlin.io.path.pathString
internal val pypyNamePattern: Pattern = Pattern.compile("pypy$")
internal val python3NamePattern: Pattern = Pattern.compile("python3$")
internal val python3XNamePattern: Pattern = Pattern.compile("python3\\.[0-9]+$")
internal fun useLegacyPythonProvider(): Boolean {
return Registry.`is`("python.use.system.legacy.provider")
}
internal suspend fun collectPythonsInPaths(eelApi: EelApi, paths: List<Path>, names: List<Pattern>): Set<Path> {
val pythons = mutableSetOf<Path>()
for (path in paths) {
val directory = EelPath.parse(path.pathString, eelApi.descriptor)
if (eelApi.fs.stat(directory).eelIt() is StatError) {
continue
}
val entries = eelApi.fs.listDirectory(directory)
.getOrNull()
entries
?.map { directory.resolve(it).asNioPath() }
?.filter { names.firstOrNull { name -> name.matcher(it.fileName.toString()).matches() } != null }
?.let { pythons.addAll(it) }
}
return pythons
}

View File

@@ -0,0 +1,119 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.python.community.services.systemPython.impl.providers
import com.intellij.execution.configurations.PathEnvironmentVariableUtil
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.platform.eel.EelApi
import com.intellij.platform.eel.isWindows
import com.intellij.platform.eel.provider.localEel
import com.intellij.python.community.services.systemPython.SystemPythonProvider
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.errorProcessing.PyResult
import com.jetbrains.python.sdk.PythonSdkUtil
import com.jetbrains.python.sdk.WinRegistryService
import com.jetbrains.python.sdk.getAppxFiles
import com.jetbrains.python.venvReader.tryResolvePath
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.nio.file.Path
import java.util.regex.Pattern
import kotlin.collections.iterator
import kotlin.io.path.exists
import kotlin.io.path.pathString
class WindowsSystemPythonProvider(val winRegistryBase: WinRegistryService? = null) : SystemPythonProvider {
private val LOGGER: Logger = Logger.getInstance(WindowsSystemPythonProvider::class.java)
private val names = listOf(
"pypy.exe",
"python.exe")
// Registry roots and product mappings from WinPythonSdkFlavor
private val REG_ROOTS = arrayOf("HKEY_LOCAL_MACHINE", "HKEY_CURRENT_USER")
private val REGISTRY_MAP = mapOf(
"Python" to "python.exe",
"IronPython" to "ipy.exe"
)
val winRegistry: Lazy<WinRegistryService> = lazy {
winRegistryBase ?: ApplicationManager.getApplication().getService(WinRegistryService::class.java)
}
// Windows Store Python product name
private val APPX_PRODUCT = "Python"
private val pythonVersionedExePattern = Pattern.compile("python[0-9.]*?\\.exe$")
override suspend fun findSystemPythons(eelApi: EelApi): PyResult<Set<PythonBinary>> {
if (eelApi != localEel || !eelApi.platform.isWindows || useLegacyPythonProvider()) {
return PyResult.success(emptySet())
}
val pythons = withContext(Dispatchers.IO) {
try {
val candidates = mutableSetOf<Path>()
for (name in names) {
val binaries = PathEnvironmentVariableUtil.findAllExeFilesInPath(name)
.mapNotNull { it.toPath() }
.filter { !PythonSdkUtil.isConda(it.pathString) }
.toSet()
candidates.addAll(binaries)
}
candidates.addAll(getPythonsFromStore())
candidates.addAll(getPythonsFromRegistry())
return@withContext candidates
}
catch (e: RuntimeException) {
LOGGER.error("Failed to discover Windows system pythons", e)
}
return@withContext emptySet<PythonBinary>()
}
return PyResult.success(pythons)
}
// Check https://www.python.org/dev/peps/pep-0514/ for windows registry layout to understand
private fun getPythonsFromRegistry(): Set<Path> {
val candidates = mutableSetOf<Path>()
for (regRoot in REG_ROOTS) {
for ((productId, exe) in REGISTRY_MAP) {
val companiesPath = "$regRoot\\SOFTWARE\\$productId"
val companiesPathWow = "$regRoot\\SOFTWARE\\Wow6432Node\\$productId"
for (path in arrayOf(companiesPath, companiesPathWow)) {
for (company in winRegistry.value.listBranches(path)) {
val pathToCompany = "$path\\$company"
for (version in winRegistry.value.listBranches(pathToCompany)) {
val folder = winRegistry.value.getDefaultKey("$pathToCompany\\$version\\InstallPath")
tryResolvePath(folder)
?.resolve(exe)
?.takeIf(Path::exists)
?.let { candidates.add(it) }
}
}
}
}
}
return candidates
}
private fun getPythonsFromStore(): Set<Path> {
return try {
getAppxFiles(APPX_PRODUCT, pythonVersionedExePattern.toRegex())
.map { it.toAbsolutePath() }
.toSet()
}
catch (e: Exception) {
LOGGER.debug("Error getting Python from Windows Store", e)
emptySet()
}
}
}

View File

@@ -15,7 +15,7 @@ import com.intellij.python.community.services.internal.impl.VanillaPythonWithLan
import com.intellij.python.community.services.shared.UICustomization
import com.intellij.python.community.services.systemPython.SystemPythonServiceImpl.MyServiceState
import com.intellij.python.community.services.systemPython.impl.Cache
import com.intellij.python.community.services.systemPython.impl.CoreSystemPythonProvider
import com.intellij.python.community.services.systemPython.impl.PySystemPythonBundle
import com.jetbrains.python.PythonBinary
import com.jetbrains.python.Result
import com.jetbrains.python.errorProcessing.PyResult
@@ -91,7 +91,7 @@ internal class SystemPythonServiceImpl(scope: CoroutineScope) : SystemPythonServ
// Only strings are supported by serializer
var userProvidedPythons by list<String>()
val userProvidedPythonsAsPath: Collection<Path>
get() = userProvidedPythons.filterNotNull().mapNotNull {
get() = userProvidedPythons.mapNotNull {
try {
Path.of(it)
}
@@ -107,8 +107,7 @@ internal class SystemPythonServiceImpl(scope: CoroutineScope) : SystemPythonServ
findPythonsMutex.withLock {
val pythonsUi = mutableMapOf<PythonBinary, UICustomization>()
val pythonsFromExtensions = (SystemPythonProvider.EP
.extensionList + listOf(CoreSystemPythonProvider))
val pythonsFromExtensions = SystemPythonProvider.EP.extensionList
.flatMap { provider ->
val pythons = provider.findSystemPythons(eelApi).getOrNull() ?: emptyList()
val ui = provider.uiCustomization

View File

@@ -16,6 +16,7 @@ import com.intellij.python.community.helpersLocator.PythonHelpersLocator;
import com.intellij.util.concurrency.SynchronizedClearableLazy;
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.sdk.WinRegistryService;
import kotlin.text.Regex;
import org.jetbrains.annotations.*;

View File

@@ -49,14 +49,14 @@ import kotlin.time.Duration.Companion.minutes
/**
* This source code is edited by @koxudaxi Koudai Aono <koxudaxi@gmail.com>
*/
private const val REPLACE_PYTHON_VERSION = """import re,sys;f=open("pyproject.toml", "r+");orig=f.read();f.seek(0);f.write(re.sub(r"(python = \"\^)[^\"]+(\")", "\g<1>"+'.'.join(str(v) for v in sys.version_info[:2])+"\g<2>", orig))"""
private const val REPLACE_PYTHON_VERSION = """import re,sys;f=open("$PY_PROJECT_TOML", "r+");orig=f.read();f.seek(0);f.write(re.sub(r"(python = \"\^)[^\"]+(\")", "\g<1>"+'.'.join(str(v) for v in sys.version_info[:2])+"\g<2>", orig))"""
private val poetryNotFoundException: @Nls String = PyBundle.message("python.sdk.poetry.execution.exception.no.poetry.message")
private val VERSION_2 = "2.0.0".toVersion()
@Internal
suspend fun runPoetry(projectPath: Path?, vararg args: String): PyResult<String> {
val executable = getPoetryExecutable().getOr { return it }
return runExecutableWithProgress(executable, projectPath, 10.minutes, *args)
return runExecutableWithProgress(executable, projectPath, 10.minutes, args = args)
}

View File

@@ -2,6 +2,7 @@
package com.jetbrains.python.sdk.flavors;
import com.intellij.openapi.util.io.FileUtil;
import com.jetbrains.python.sdk.WinRegistryService;
import org.easymock.EasyMock;
import org.easymock.IMocksControl;
import org.easymock.MockType;