cleanup [python]: Fix a couple of VirtualEnvReader bottlenecks.

1. No need to check if file is directory: FS isn't atomic anyway, and we need to handle exceptions. Let's not do one more useless syscall which might be expensive on eel.
2. Check filename before reading it metadata (one more syscall)

GitOrigin-RevId: 0fac159784c2c1d80c3418eb0d9c60a501662b7d
This commit is contained in:
Ilya.Kazakevich
2025-12-27 05:01:58 +00:00
committed by intellij-monorepo-bot
parent 4cc9b0a3d3
commit 3e47d60be1
2 changed files with 36 additions and 21 deletions
@@ -18,6 +18,8 @@ import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.NonNls
import org.jetbrains.annotations.TestOnly
import java.io.IOException
import java.nio.file.NoSuchFileException
import java.nio.file.NotDirectoryException
import java.nio.file.Path
import kotlin.io.path.*
@@ -57,7 +59,7 @@ class VirtualEnvReader private constructor(
*/
@RequiresBackgroundThread
fun findVEnvInterpreters(): List<PythonBinary> =
findLocalInterpreters(getVEnvRootDir())
findVenvsInDir(getVEnvRootDir())
@RequiresBackgroundThread
fun getPyenvRootDir(eel: EelApi? = getLocalEelIfApp()): Directory {
@@ -66,16 +68,26 @@ class VirtualEnvReader private constructor(
@RequiresBackgroundThread
fun findPyenvInterpreters(): List<PythonBinary> =
findLocalInterpreters(getPyenvVersionsDir())
findVenvsInDir(getPyenvVersionsDir())
/**
* List contents of [root] looking for envs there, returns all pythons it were able to find
*/
@RequiresBackgroundThread
fun findLocalInterpreters(root: Directory): List<PythonBinary> {
if (!root.isDirectory()) {
return listOf()
}
fun findVenvsInDir(root: Directory): List<PythonBinary> {
val candidates: ArrayList<Path> = arrayListOf()
for (dir in root.listDirectoryEntries()) {
val children = try {
root.listDirectoryEntries()
}
catch (_: NoSuchFileException) {
return emptyList()
}
catch (_: NotDirectoryException) {
return emptyList()
}
for (dir in children) {
findPythonInPythonRoot(dir)?.let { candidates.add(it) }
}
@@ -120,19 +132,12 @@ class VirtualEnvReader private constructor(
*/
@RequiresBackgroundThread
fun findPythonInPythonRoot(dir: PythonHomePath): PythonBinary? {
if (!dir.isDirectory()) {
return null
}
val bin = dir.resolve("bin")
if (bin.isDirectory()) {
findInterpreter(bin)?.let { return it }
}
findInterpreter(bin)?.let { return it }
val scripts = dir.resolve("Scripts")
if (scripts.isDirectory()) {
findInterpreter(scripts)?.let { return it }
}
findInterpreter(scripts)?.let { return it }
return findInterpreter(dir)
}
@@ -170,10 +175,18 @@ class VirtualEnvReader private constructor(
@RequiresBackgroundThread
private fun findInterpreter(dir: Path): PythonBinary? {
val pythonNames = when (forcedOs ?: dir.getEelDescriptor().osFamily) {
EelOsFamily.Posix -> setOf("pypy", "python")
EelOsFamily.Windows -> setOf("pypy.exe", "python.exe")
EelOsFamily.Posix -> POSIX_BINS
EelOsFamily.Windows -> WIN_BINS
}
return try {
dir.listDirectoryEntries().firstOrNull { it.name.lowercase() in pythonNames && it.isRegularFile() }
}
catch (_: NotDirectoryException) {
return null
}
catch (_: NoSuchFileException) {
return null
}
return dir.listDirectoryEntries().firstOrNull { it.isRegularFile() && it.name.lowercase() in pythonNames }
}
@RequiresBackgroundThread
@@ -203,6 +216,8 @@ class VirtualEnvReader private constructor(
@Suppress("VENV_IS_OK") // The only place it should be used in prod
const val DEFAULT_VIRTUALENV_DIRNAME: String = ".venv"
private val POSIX_BINS = setOf("pypy", "python")
private val WIN_BINS = setOf("pypy.exe", "python.exe")
private fun getLocalEelIfApp(): EelApi? = if (ApplicationManager.getApplication() != null) localEel else null
}
}