From bb1ccaf504dfe2bfd813a73246d9a78524c5f796 Mon Sep 17 00:00:00 2001 From: Ilya Kazakevich Date: Fri, 13 Feb 2026 15:15:05 +0000 Subject: [PATCH] PY-86605: Do not access IO from `interpreterType`. This property violates kotlin ideology: properties must be fast, `O(1)`, and thread agnostic. This one is used all over the system including plugins (unfortunately) and we can't force callers to use BG. So we have to make it thread-agnostic. We sacrifice accuracy in favor of speed. Most people use `.pyenv` as a dir name, and those who do not, will be excluded from the statistics. Note, that this function was never intended to be used for anything but statistics. `LOG.error` throws exception in tests (but not in prod) so we use it to make sure we do not access IO from this function. Merge-request: IJ-MR-191539 Merged-by: Ilya Kazakevich GitOrigin-RevId: a92a233bcce22d47e522034c04ba2c93d2efb33b --- .../testFramework/junit5/src/fixture/fixtures.kt | 8 ++++++-- .../jetbrains/python/venvReader/VirtualEnvReader.kt | 13 ++++++++++++- .../jetbrains/python/statistics/PyStatisticTools.kt | 8 +++++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/platform/testFramework/junit5/src/fixture/fixtures.kt b/platform/testFramework/junit5/src/fixture/fixtures.kt index 326dbf61e76a..df3f1370088c 100644 --- a/platform/testFramework/junit5/src/fixture/fixtures.kt +++ b/platform/testFramework/junit5/src/fixture/fixtures.kt @@ -54,6 +54,7 @@ import java.nio.file.Path import kotlin.io.path.ExperimentalPathApi import kotlin.io.path.Path import kotlin.io.path.copyToRecursively +import kotlin.io.path.createDirectory import kotlin.io.path.exists @JvmOverloads @@ -71,8 +72,8 @@ fun testNameFixture(lowerCaseFirstLetter: Boolean = true): TestFixture = @JvmOverloads @TestOnly -fun tempPathFixture(root: Path? = null, prefix: String = "IJ"): TestFixture = testFixture { - val tempDir = withContext(Dispatchers.IO) { +fun tempPathFixture(root: Path? = null, prefix: String = "IJ", subdirName: String? = null): TestFixture = testFixture { + var tempDir = withContext(Dispatchers.IO) { if (root == null) { it.eel?.fs?.createTemporaryDirectory(CreateTemporaryEntryOptions.Builder().prefix(prefix).build())?.getOrThrow()?.asNioPath() ?: Files.createTempDirectory(prefix) @@ -84,6 +85,9 @@ fun tempPathFixture(root: Path? = null, prefix: String = "IJ"): TestFixture runBlockingMaybeCancellable { eel.exec.environmentVariables().eelIt().await() } } ?: System.getenv() @@ -245,6 +253,7 @@ class VirtualEnvReader private constructor( companion object { + private val logger = fileLogger() internal val Instance: VirtualEnvReader = VirtualEnvReader(forcedVars = null, forcedOs = null) @@ -259,6 +268,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" + const val PYENV_DEFAULT_DIR_NAME: String = ".pyenv" + private val POSIX_PYTHON_PATTERN = Regex("^(pypy|python)(\\d+(\\.\\d+)*)?$") private val WIN_PYTHON_PATTERN = Regex("^(pypy|python)(\\d+(\\.\\d+)*)?\\.exe$", RegexOption.IGNORE_CASE) private fun getLocalEelIfApp(): EelApi? = if (ApplicationManager.getApplication() != null) localEel else null diff --git a/python/src/com/jetbrains/python/statistics/PyStatisticTools.kt b/python/src/com/jetbrains/python/statistics/PyStatisticTools.kt index af7ade6176f5..0640efbe9780 100644 --- a/python/src/com/jetbrains/python/statistics/PyStatisticTools.kt +++ b/python/src/com/jetbrains/python/statistics/PyStatisticTools.kt @@ -182,6 +182,11 @@ val Sdk.executionType: InterpreterTarget else -> LOCAL } +/** + * This method is for statistics only and might be somewhat inaccurate. + * Moreover, interpreters will become a pluggable system soon, so there will be no predefined enum. + * Please, do not use it. + */ val Sdk.interpreterType: InterpreterType get() = when { // The order of checks is important here since e.g. a pipenv is a virtualenv @@ -190,7 +195,7 @@ val Sdk.interpreterType: InterpreterType isPoetry -> POETRY isHatch -> HATCH this.isCondaVirtualEnv || this.sdkAdditionalData.asSafely()?.flavor is CondaEnvSdkFlavor -> CONDAVENV - VirtualEnvReader().isPyenvSdk(getHomePath()) -> PYENV + homePath?.contains(PYENV_PATTERN) == true -> PYENV this.isVirtualEnv -> VIRTUALENV else -> REGULAR } @@ -213,3 +218,4 @@ private val PyTargetAwareAdditionalData.executionType: InterpreterTarget } } ?: REMOTE_UNKNOWN +private val PYENV_PATTERN = Regex("[/\\\\]${VirtualEnvReader.PYENV_DEFAULT_DIR_NAME}[/\\\\]") \ No newline at end of file