From 4f5b07293f2e08eeeca773545f36b67a88077d26 Mon Sep 17 00:00:00 2001 From: Anton Efimchuk Date: Sat, 10 Jan 2026 00:33:44 +0000 Subject: [PATCH] PY-86778 [python] Migrate Python test environments from local builds to prebuilt standalone distributions Replace Gradle-based local Python compilation with prebuilt standalone distributions downloaded from JetBrains cache redirector. Introduces provider-based architecture for test environment management with improved JUnit4/JUnit5 integration. Key changes: - Remove setup-test-environment Gradle module and build infrastructure - Introduce python-test-env modules (core, common, plain, conda, uv, junit4, junit5) - Implement PyEnvironmentProvider/PyEnvironmentSpec abstractions - Add JUnit5 annotations (@RequiresPoetry, @RequiresUv, @RunOnEnvironments) - Implement caching system with PyEnvDownloadCache and variant-specific directories - Predefined environments like: VANILLA_2_7, VANILLA_3_11, VANILLA_3_12, VANILLA_3_13 Benefits: - Faster test environment provisioning (no compilation required) - Consistent cross-platform behavior with identical Python builds - Improved reliability by eliminating platform-specific build failures - Better test framework integration with parameterized environment testing GitOrigin-RevId: a44957c3014f9e1fc7dbec347a7e245675f999c1 --- .idea/modules.xml | 10 +- build/bazel-generated-file-list.txt | 10 +- .../junit5/src/fixture/TestContextImpl.kt | 3 + .../junit5/src/fixture/testFixture.kt | 6 + python/BUILD.bazel | 25 +- python/helpers/generator3/core.py | 2 +- python/helpers/generator3/util_methods.py | 4 + python/ide/impl/BUILD.bazel | 2 - python/intellij.python.community.tests.iml | 14 +- python/interpreters/BUILD.bazel | 51 +- ...intellij.python.community.interpreters.iml | 1 - python/junit5Tests-framework/BUILD.bazel | 11 +- .../junit5Tests-framework/conda/BUILD.bazel | 44 - ....community.junit5Tests.framework.conda.iml | 20 - .../framework/conda/PyEnvTestCaseWithConda.kt | 18 - .../conda/impl/CondaPythonEnvExtension.kt | 12 - .../framework/conda/impl/package-info.java | 5 - ...nity.junit5Tests.framework.conda._test.xml | 6 - ...python.community.junit5Tests.framework.iml | 1 - .../framework/env/PyEnvTestCase.kt | 19 - .../env/impl/PythonEnvExtensionBase.kt | 102 - .../env/impl/VanillaPythonEnvExtension.kt | 76 - .../junit5Tests/framework/env/sdkFixture.kt | 41 +- ....community.junit5Tests.framework._test.xml | 2 +- .../execService.python/BUILD.bazel | 41 +- ...ij.python.community.execService.python.iml | 1 - python/python-features-trainer/BUILD.bazel | 2 + .../intellij.python.featuresTrainer.iml | 2 + .../intellij.python.featuresTrainer._test.xml | 1 + .../junit5Tests/env/PythonLangSupportTest.kt | 5 +- python/python-process-output/impl/BUILD.bazel | 1 + .../intellij.python.processOutput.impl.iml | 1 + ...tellij.python.processOutput.impl._test.xml | 1 + python/python-pyproject/BUILD.bazel | 2 - .../intellij.python.pyproject.iml | 1 - .../pyProjectToml/FSWalkInfoWithToml.kt | 2 +- .../internal/pyProjectToml/tomFileTools.kt | 2 +- .../intellij.python.pyproject._test.xml | 1 - python/python-terminal/BUILD.bazel | 3 +- .../intellij.python.terminal.iml | 3 +- .../intellij.python.terminal._test.xml | 2 +- .../PyVirtualEnvTerminalCustomizerTest.kt | 2 +- python/python-test-env/common/BUILD.bazel | 99 + .../intellij.python.test.env.common.iml | 32 + .../intellij.python.test.env.common.xml | 9 + .../test/env/common/EnvTestPythonProvider.kt | 42 + .../env/common/PredefinedPyEnvironments.kt | 226 + .../test/env/common/PyEnvironmentFactories.kt | 33 + .../intellij/python/test/env/common/sdk.kt | 62 + python/python-test-env/conda/BUILD.bazel | 63 + .../conda/intellij.python.test.env.conda.iml} | 15 +- .../intellij.python.test.env.conda.xml | 8 + .../env/conda/CondaPyEnvironmentProvider.kt | 271 + python/python-test-env/core/BUILD.bazel | 75 + .../test/env/core/python-version-mapping.json | 5192 +++++++++++++++++ .../core/intellij.python.test.env.core.iml | 27 + .../intellij.python.test.env.core.xml | 6 + .../generate_python_version_mapping.py | 397 ++ .../env/core/CachingPyEnvironmentFactory.kt | 22 + .../env/core/DefaultPyEnvironmentFactory.kt | 34 + .../python/test/env/core/ProcessUtils.kt | 46 + .../test/env/core/PyEnvDownloadCache.kt | 139 + .../test/env/core/PyEnvironmentCache.kt | 136 + .../test/env/core/PyEnvironmentFactory.kt | 15 + .../test/env/core/PyEnvironmentProvider.kt | 156 + .../python/test/env/core/PyEnvironmentSpec.kt | 70 + .../python/test/env/core/PyVersionMapping.kt | 162 + .../python/test/env/core/PythonVersion.kt | 100 + .../intellij/python/test/env/core/install.kt | 105 + python/python-test-env/junit4/BUILD.bazel | 46 + .../intellij.python.test.env.junit4.iml | 17 + .../intellij.python.test.env.junit4.xml | 2 + .../test/env/junit4/JUnit4FactoryHolder.kt | 20 + python/python-test-env/junit5/BUILD.bazel | 82 + .../intellij.python.test.env.junit5.iml | 36 + .../intellij.python.test.env.junit5.xml | 8 + .../junit5Tests/framework/conda/CondaEnv.kt | 0 .../framework/conda/PyEnvTestCaseWithConda.kt | 28 + .../framework/conda/condaEnvTool.kt | 0 .../env/BeforeRunOnEnvironmentInvocation.kt | 10 + .../framework/env/PyEnvTestCase.kt | 41 + .../framework/env/PythonBinaryPath.kt | 1 - .../junit5Tests/framework/env/PythonSdk.kt | 15 + .../framework/env/RequiresPoetry.kt | 46 + .../junit5Tests/framework/env/RequiresUv.kt | 46 + .../framework/env/RunOnEnvironments.kt | 16 + .../junit5Tests/framework/env/pySdkFixture.kt | 58 + .../junit5/EnvTestPythonProviderExtension.kt | 57 + .../env/junit5/PythonBinaryPathExtension.kt | 26 + .../test/env/junit5/PythonFactoryExtension.kt | 57 + .../test/env/junit5/PythonSdkExtension.kt | 45 + .../env/junit5/RequiresPoetryExtension.kt | 73 + .../test/env/junit5/RequiresUvExtension.kt | 66 + .../env/junit5/RunOnEnvironmentsExtension.kt | 158 + .../junit5/conda/CondaPythonEnvExtension.kt | 41 + .../test/env/junit5/conda}/package-info.java | 2 +- .../python/test/env/junit5}/pyVenvFixture.kt | 19 +- .../showcase}/PyEnvWithCondaShowCaseTest.kt | 2 +- .../showcase/RunOnEnvironmentsShowCaseTest.kt | 40 + python/python-test-env/plain/BUILD.bazel | 70 + .../plain/intellij.python.test.env.plain.iml | 27 + .../intellij.python.test.env.plain.xml | 6 + .../env/plain/PlainPyEnvironmentProvider.kt | 163 + .../test/env/plain/PlainPyEnvironmentSpec.kt | 49 + .../plain/VirtualenvPyEnvironmentProvider.kt | 82 + .../env/plain/VirtualenvPyEnvironmentSpec.kt | 49 + python/python-test-env/uv/BUILD.bazel | 70 + .../uv/intellij.python.test.env.uv.iml} | 26 +- .../intellij.python.test.env.uv.xml} | 2 +- .../test/env/uv/UvPyEnvironmentProvider.kt | 219 + python/python-venv/BUILD.bazel | 33 +- .../intellij.python.community.impl.venv.iml | 4 +- python/services/internal-impl/BUILD.bazel | 1 - python/services/shared/BUILD.bazel | 1 - python/services/system-python/BUILD.bazel | 3 - ...python.community.services.systemPython.iml | 1 - .../systemPython/systemPythonServiceImpl.kt | 2 +- .../testResources/META-INF/plugin.xml | 5 +- ....community.services.systemPython._test.xml | 8 +- .../impl/EnvTestPythonProvider.kt | 41 - .../{ => impl}/SystemPythonRootsFixer.kt | 6 +- python/setup-test-environment/.dockerignore | 2 - python/setup-test-environment/.gitignore | 1 - python/setup-test-environment/BUILD.bazel | 30 - python/setup-test-environment/Dockerfile | 51 - python/setup-test-environment/README.txt | 3 - .../setup-test-environment/build.gradle.kts | 205 - .../buildserver_win_fix/README.txt | 6 - .../buildserver_win_fix/fix_path.cmd | 18 - .../buildserver_win_fix/fix_path.py | 53 - .../setup-test-environment/conda/BUILD.bazel | 28 - ....community.testFramework.testEnv.conda.xml | 5 - .../testFramework/testEnv/conda/TypeConda.kt | 79 - .../gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 0 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 - python/setup-test-environment/gradlew | 240 - python/setup-test-environment/gradlew.bat | 91 - .../settings.gradle.kts | 10 - .../testEnv/PyEnvTestSettings.kt | 139 - .../testFramework/testEnv/PythonType.kt | 113 - .../testEnv/TypeVanillaPython3.kt | 32 - .../testFramework/testEnv/envTags.kt | 29 - .../testFramework/testEnv/package-info.java | 8 - python/testFramework/BUILD.bazel | 10 +- ...ntellij.python.community.testFramework.iml | 2 +- ...ntellij.python.community.testFramework.xml | 6 + .../src/com/jetbrains/python/tools/sdk.kt | 69 +- .../junit5Tests/env/HelpersShowCaseTest.kt | 0 .../env/PythonBinaryValidationTest.kt | 0 .../env/pyproject/PyWalkFileSystemEnvTest.kt | 0 .../impl/PythonWithLanguageLevelImplTest.kt | 0 .../junit5Tests/env/systemPython/Py27Test.kt | 0 .../SystemPythonServiceShowCaseTest.kt | 0 .../env/systemPython/impl/EnvProviderTest.kt | 0 .../env/systemPython/impl/package-info.java | 0 .../InterpreterServiceShowCaseTest.kt | 10 +- .../showCase/PyEnvWithVenvShowCaseTest.kt | 2 +- .../PyVenvCreationManuallyShowCaseTest.kt | 2 +- .../jetbrains/env/ProviderTestEnvironment.kt | 35 + .../com/jetbrains/env/PyEnvTaskRunner.java | 148 +- .../com/jetbrains/env/PyEnvTestCase.java | 94 +- .../com/jetbrains/env/PyEnvTestSettings.kt | 74 + .../com/jetbrains/env/PyTestEnvironment.java | 42 + .../tests/PythonDebuggerVariablesViewTest.kt | 11 +- .../env/python/PyEnvSufficiencyTest.java | 54 - .../com/jetbrains/env/python/PySDKRule.kt | 13 +- .../com/jetbrains/env/python/PyToxTest.java | 80 +- .../env/python/conda/LocalCondaRule.kt | 14 +- 168 files changed, 9804 insertions(+), 2036 deletions(-) delete mode 100644 python/junit5Tests-framework/conda/BUILD.bazel delete mode 100644 python/junit5Tests-framework/conda/intellij.python.community.junit5Tests.framework.conda.iml delete mode 100644 python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/PyEnvTestCaseWithConda.kt delete mode 100644 python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/impl/CondaPythonEnvExtension.kt delete mode 100644 python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/impl/package-info.java delete mode 100644 python/junit5Tests-framework/conda/testResources/intellij.python.community.junit5Tests.framework.conda._test.xml delete mode 100644 python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/PyEnvTestCase.kt delete mode 100644 python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/impl/PythonEnvExtensionBase.kt delete mode 100644 python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/impl/VanillaPythonEnvExtension.kt create mode 100644 python/python-test-env/common/BUILD.bazel create mode 100644 python/python-test-env/common/intellij.python.test.env.common.iml create mode 100644 python/python-test-env/common/resources/intellij.python.test.env.common.xml create mode 100644 python/python-test-env/common/src/com/intellij/python/test/env/common/EnvTestPythonProvider.kt create mode 100644 python/python-test-env/common/src/com/intellij/python/test/env/common/PredefinedPyEnvironments.kt create mode 100644 python/python-test-env/common/src/com/intellij/python/test/env/common/PyEnvironmentFactories.kt create mode 100644 python/python-test-env/common/src/com/intellij/python/test/env/common/sdk.kt create mode 100644 python/python-test-env/conda/BUILD.bazel rename python/{setup-test-environment/conda/intellij.python.community.testFramework.testEnv.conda.iml => python-test-env/conda/intellij.python.test.env.conda.iml} (70%) create mode 100644 python/python-test-env/conda/resources/intellij.python.test.env.conda.xml create mode 100644 python/python-test-env/conda/src/com/intellij/python/test/env/conda/CondaPyEnvironmentProvider.kt create mode 100644 python/python-test-env/core/BUILD.bazel create mode 100644 python/python-test-env/core/gen/com/intellij/python/test/env/core/python-version-mapping.json create mode 100644 python/python-test-env/core/intellij.python.test.env.core.iml create mode 100644 python/python-test-env/core/resources/intellij.python.test.env.core.xml create mode 100755 python/python-test-env/core/scripts/generate_python_version_mapping.py create mode 100644 python/python-test-env/core/src/com/intellij/python/test/env/core/CachingPyEnvironmentFactory.kt create mode 100644 python/python-test-env/core/src/com/intellij/python/test/env/core/DefaultPyEnvironmentFactory.kt create mode 100644 python/python-test-env/core/src/com/intellij/python/test/env/core/ProcessUtils.kt create mode 100644 python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvDownloadCache.kt create mode 100644 python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentCache.kt create mode 100644 python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentFactory.kt create mode 100644 python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentProvider.kt create mode 100644 python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentSpec.kt create mode 100644 python/python-test-env/core/src/com/intellij/python/test/env/core/PyVersionMapping.kt create mode 100644 python/python-test-env/core/src/com/intellij/python/test/env/core/PythonVersion.kt create mode 100644 python/python-test-env/core/src/com/intellij/python/test/env/core/install.kt create mode 100644 python/python-test-env/junit4/BUILD.bazel create mode 100644 python/python-test-env/junit4/intellij.python.test.env.junit4.iml create mode 100644 python/python-test-env/junit4/resources/intellij.python.test.env.junit4.xml create mode 100644 python/python-test-env/junit4/src/com/intellij/python/test/env/junit4/JUnit4FactoryHolder.kt create mode 100644 python/python-test-env/junit5/BUILD.bazel create mode 100644 python/python-test-env/junit5/intellij.python.test.env.junit5.iml create mode 100644 python/python-test-env/junit5/resources/intellij.python.test.env.junit5.xml rename python/{junit5Tests-framework/conda => python-test-env/junit5}/src/com/intellij/python/community/junit5Tests/framework/conda/CondaEnv.kt (100%) create mode 100644 python/python-test-env/junit5/src/com/intellij/python/community/junit5Tests/framework/conda/PyEnvTestCaseWithConda.kt rename python/{junit5Tests-framework/conda => python-test-env/junit5}/src/com/intellij/python/community/junit5Tests/framework/conda/condaEnvTool.kt (100%) create mode 100644 python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/BeforeRunOnEnvironmentInvocation.kt create mode 100644 python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/PyEnvTestCase.kt rename python/{junit5Tests-framework => python-test-env/junit5}/src/com/intellij/python/junit5Tests/framework/env/PythonBinaryPath.kt (92%) create mode 100644 python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/PythonSdk.kt create mode 100644 python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/RequiresPoetry.kt create mode 100644 python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/RequiresUv.kt create mode 100644 python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/RunOnEnvironments.kt create mode 100644 python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/pySdkFixture.kt create mode 100644 python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/EnvTestPythonProviderExtension.kt create mode 100644 python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/PythonBinaryPathExtension.kt create mode 100644 python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/PythonFactoryExtension.kt create mode 100644 python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/PythonSdkExtension.kt create mode 100644 python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/RequiresPoetryExtension.kt create mode 100644 python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/RequiresUvExtension.kt create mode 100644 python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/RunOnEnvironmentsExtension.kt create mode 100644 python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/conda/CondaPythonEnvExtension.kt rename python/{junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/impl => python-test-env/junit5/src/com/intellij/python/test/env/junit5/conda}/package-info.java (75%) rename python/{python-venv/tests/com/intellij/python/community/impl/venv/tests => python-test-env/junit5/src/com/intellij/python/test/env/junit5}/pyVenvFixture.kt (75%) rename python/{junit5Tests-framework/conda/src/com/intellij/python/junit5Tests/env/conda/showCase => python-test-env/junit5/test/com/intellij/python/test/env/junit5/conda/showcase}/PyEnvWithCondaShowCaseTest.kt (95%) create mode 100644 python/python-test-env/junit5/test/com/intellij/python/test/env/junit5/conda/showcase/RunOnEnvironmentsShowCaseTest.kt create mode 100644 python/python-test-env/plain/BUILD.bazel create mode 100644 python/python-test-env/plain/intellij.python.test.env.plain.iml create mode 100644 python/python-test-env/plain/resources/intellij.python.test.env.plain.xml create mode 100644 python/python-test-env/plain/src/com/intellij/python/test/env/plain/PlainPyEnvironmentProvider.kt create mode 100644 python/python-test-env/plain/src/com/intellij/python/test/env/plain/PlainPyEnvironmentSpec.kt create mode 100644 python/python-test-env/plain/src/com/intellij/python/test/env/plain/VirtualenvPyEnvironmentProvider.kt create mode 100644 python/python-test-env/plain/src/com/intellij/python/test/env/plain/VirtualenvPyEnvironmentSpec.kt create mode 100644 python/python-test-env/uv/BUILD.bazel rename python/{setup-test-environment/intellij.python.community.testFramework.testEnv.iml => python-test-env/uv/intellij.python.test.env.uv.iml} (68%) rename python/{setup-test-environment/resources/intellij.python.community.testFramework.testEnv.xml => python-test-env/uv/resources/intellij.python.test.env.uv.xml} (55%) create mode 100644 python/python-test-env/uv/src/com/intellij/python/test/env/uv/UvPyEnvironmentProvider.kt delete mode 100644 python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/impl/EnvTestPythonProvider.kt rename python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/{ => impl}/SystemPythonRootsFixer.kt (80%) delete mode 100644 python/setup-test-environment/.dockerignore delete mode 100644 python/setup-test-environment/.gitignore delete mode 100644 python/setup-test-environment/BUILD.bazel delete mode 100644 python/setup-test-environment/Dockerfile delete mode 100644 python/setup-test-environment/README.txt delete mode 100644 python/setup-test-environment/build.gradle.kts delete mode 100644 python/setup-test-environment/buildserver_win_fix/README.txt delete mode 100644 python/setup-test-environment/buildserver_win_fix/fix_path.cmd delete mode 100644 python/setup-test-environment/buildserver_win_fix/fix_path.py delete mode 100644 python/setup-test-environment/conda/BUILD.bazel delete mode 100644 python/setup-test-environment/conda/resources/intellij.python.community.testFramework.testEnv.conda.xml delete mode 100644 python/setup-test-environment/conda/src/com/intellij/python/community/testFramework/testEnv/conda/TypeConda.kt delete mode 100644 python/setup-test-environment/gradle/wrapper/gradle-wrapper.jar delete mode 100644 python/setup-test-environment/gradle/wrapper/gradle-wrapper.properties delete mode 100755 python/setup-test-environment/gradlew delete mode 100644 python/setup-test-environment/gradlew.bat delete mode 100644 python/setup-test-environment/settings.gradle.kts delete mode 100644 python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/PyEnvTestSettings.kt delete mode 100644 python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/PythonType.kt delete mode 100644 python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/TypeVanillaPython3.kt delete mode 100644 python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/envTags.kt delete mode 100644 python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/package-info.java create mode 100644 python/testFramework/resources/intellij.python.community.testFramework.xml rename python/{python-exec-service/execService.python/tests => testSrc}/com/intellij/python/junit5Tests/env/HelpersShowCaseTest.kt (100%) rename python/{python-exec-service/execService.python/tests => testSrc}/com/intellij/python/junit5Tests/env/PythonBinaryValidationTest.kt (100%) rename python/{python-pyproject/test => testSrc}/com/intellij/python/junit5Tests/env/pyproject/PyWalkFileSystemEnvTest.kt (100%) rename python/{services/internal-impl/tests => testSrc}/com/intellij/python/junit5Tests/env/services/internal/impl/PythonWithLanguageLevelImplTest.kt (100%) rename python/{services/system-python/tests => testSrc}/com/intellij/python/junit5Tests/env/systemPython/Py27Test.kt (100%) rename python/{services/system-python/tests => testSrc}/com/intellij/python/junit5Tests/env/systemPython/SystemPythonServiceShowCaseTest.kt (100%) rename python/{services/system-python/tests => testSrc}/com/intellij/python/junit5Tests/env/systemPython/impl/EnvProviderTest.kt (100%) rename python/{services/system-python/tests => testSrc}/com/intellij/python/junit5Tests/env/systemPython/impl/package-info.java (100%) rename python/{interpreters/tests => testSrc}/com/intellij/python/junit5Tests/env/tests/interpreters/InterpreterServiceShowCaseTest.kt (91%) rename python/{python-venv/tests => testSrc}/com/intellij/python/junit5Tests/env/venv/showCase/PyEnvWithVenvShowCaseTest.kt (94%) rename python/{python-venv/tests => testSrc}/com/intellij/python/junit5Tests/env/venv/showCase/PyVenvCreationManuallyShowCaseTest.kt (95%) create mode 100644 python/testSrc/com/jetbrains/env/ProviderTestEnvironment.kt create mode 100644 python/testSrc/com/jetbrains/env/PyEnvTestSettings.kt create mode 100644 python/testSrc/com/jetbrains/env/PyTestEnvironment.java delete mode 100644 python/testSrc/com/jetbrains/env/python/PyEnvSufficiencyTest.java diff --git a/.idea/modules.xml b/.idea/modules.xml index d469e34b1941..fdaf85ef7e92 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -1432,7 +1432,6 @@ - @@ -1444,8 +1443,6 @@ - - @@ -1471,6 +1468,13 @@ + + + + + + + diff --git a/build/bazel-generated-file-list.txt b/build/bazel-generated-file-list.txt index 16523c9115f2..e433dd9c58cb 100644 --- a/build/bazel-generated-file-list.txt +++ b/build/bazel-generated-file-list.txt @@ -1471,7 +1471,6 @@ python/intellij.python.community.communityOnly python/intellij.python.ml.features python/interpreters python/junit5Tests-framework -python/junit5Tests-framework/conda python/openapi python/pipenv python/pluginCore @@ -1505,14 +1504,19 @@ python/python-sdk-ui python/python-syntax python/python-syntax-core python/python-terminal +python/python-test-env/common +python/python-test-env/conda +python/python-test-env/core +python/python-test-env/junit4 +python/python-test-env/junit5 +python/python-test-env/plain +python/python-test-env/uv python/python-uv/backend python/python-uv/common python/python-venv python/services/internal-impl python/services/shared python/services/system-python -python/setup-test-environment -python/setup-test-environment/conda python/testFramework python/tools resources diff --git a/platform/testFramework/junit5/src/fixture/TestContextImpl.kt b/platform/testFramework/junit5/src/fixture/TestContextImpl.kt index d86ab6451eb6..446489570fb0 100644 --- a/platform/testFramework/junit5/src/fixture/TestContextImpl.kt +++ b/platform/testFramework/junit5/src/fixture/TestContextImpl.kt @@ -18,6 +18,9 @@ internal class TestContextImpl(private val context: ExtensionContext, override v else name } + override val extensionContext: ExtensionContext + get() = context + override fun findAnnotation(clazz: Class): T? { var extContext: ExtensionContext? = context while (extContext != null) { diff --git a/platform/testFramework/junit5/src/fixture/testFixture.kt b/platform/testFramework/junit5/src/fixture/testFixture.kt index be6cf05f2fd4..6606f918f4ca 100644 --- a/platform/testFramework/junit5/src/fixture/testFixture.kt +++ b/platform/testFramework/junit5/src/fixture/testFixture.kt @@ -77,6 +77,12 @@ sealed interface TestContext { */ val testName: String + /** + * The underlying JUnit5 extension context. + * Provides access to the test execution context and allows extensions to interact with the JUnit5 framework. + */ + val extensionContext: org.junit.jupiter.api.extension.ExtensionContext + /** * Returns the annotation with which a test or container is marked or null if there is none. */ diff --git a/python/BUILD.bazel b/python/BUILD.bazel index 2de2ab76785b..930aa3241006 100644 --- a/python/BUILD.bazel +++ b/python/BUILD.bazel @@ -360,13 +360,10 @@ jvm_library( "//python/python-sdk:sdk_test_lib", "//platform/statistics:statistics_test_lib", "//platform/backend/observation:observation_test_lib", - "//python/python-venv:community-impl-venv_test_lib", "//python/python-exec-service:community-execService_test_lib", "//python/services/system-python:system-python_test_lib", "//python/services/internal-impl:python-community-services-internal-impl_test_lib", "//python/python-pyproject:pyproject_test_lib", - "//python/python-exec-service/execService.python:execService.python_test_lib", - "//python/interpreters:interpreters_test_lib", ] ) ### auto-generated section `build intellij.python.community.impl` end @@ -441,6 +438,8 @@ jvm_library( "//jupyter:notebooks-jupyter-core", "//python/python-grazie:grazie", "//platform/forms_rt:java-guiForms-rt", + "//python/python-test-env/common", + "//python/services/shared", ] ) @@ -529,8 +528,6 @@ jvm_library( "//platform/testFramework/junit5:junit5_test_lib", "//libraries/kotlin/reflect", "//platform/todo", - "//python/setup-test-environment:community-testFramework-testEnv", - "//python/setup-test-environment/conda", "//platform/external-system-api:externalSystem", "//platform/external-system-impl:externalSystem-impl", "//platform/external-system-impl:externalSystem-tests_test_lib", @@ -547,6 +544,24 @@ jvm_library( "//platform/execution", "@lib//:jet_check", "//platform/forms_rt:java-guiForms-rt", + "//python/python-test-env/common", + "//python/python-test-env/common:common_test_lib", + "//python/python-test-env/junit4:junit4_test_lib", + "//python/python-test-env/junit5:junit5_test_lib", + "//platform/testFramework/junit5/eel", + "//platform/testFramework/junit5/eel:eel_test_lib", + "//platform/eel-provider", + "//python/python-exec-service/execService.python", + "//python/services/internal-impl:python-community-services-internal-impl", + "//python/services/internal-impl:python-community-services-internal-impl_test_lib", + "//python/junit5Tests-framework:community-junit5Tests-framework_test_lib", + "//python/services/shared", + "//python/services/shared:shared_test_lib", + "//python/python-venv:community-impl-venv", + "//python/python-venv:community-impl-venv_test_lib", + "//python/services/system-python", + "//python/services/system-python:system-python_test_lib", + "//python/interpreters", ], runtime_deps = [":python-community-tests"] ) diff --git a/python/helpers/generator3/core.py b/python/helpers/generator3/core.py index 0829821edf00..fd7b8f4566f0 100644 --- a/python/helpers/generator3/core.py +++ b/python/helpers/generator3/core.py @@ -517,7 +517,7 @@ class SkeletonGenerator(object): status = self.reuse_or_generate_skeleton(mod_name, mod_path, sdk_skeleton_state) control_message('generation_result', { 'module_name': mod_name, - 'module_origin': get_module_origin(mod_path, mod_name), + 'module_origin': get_module_origin(mod_path, mod_name).replace('%', '%%'), 'generation_status': status }) if mod_path: diff --git a/python/helpers/generator3/util_methods.py b/python/helpers/generator3/util_methods.py index 4175a8b407e9..d6035cc28c91 100644 --- a/python/helpers/generator3/util_methods.py +++ b/python/helpers/generator3/util_methods.py @@ -345,6 +345,10 @@ def report(msg, *data): def say(msg, *data): """Say something at info level (stdout)""" + sys.stderr.write(msg) + sys.stderr.write("\n") + sys.stderr.write(str(data)) + sys.stderr.write("\n") sys.stdout.write(msg % data) sys.stdout.write("\n") sys.stdout.flush() diff --git a/python/ide/impl/BUILD.bazel b/python/ide/impl/BUILD.bazel index e134aac19984..240769246125 100644 --- a/python/ide/impl/BUILD.bazel +++ b/python/ide/impl/BUILD.bazel @@ -133,7 +133,6 @@ jvm_library( "//python/services/shared:shared_test_lib", "//python/pipenv", "//python/python-venv:community-impl-venv", - "//python/python-venv:community-impl-venv_test_lib", "@lib//:jetbrains-annotations", "//python/python-pyproject:pyproject", "//python/python-pyproject:pyproject_test_lib", @@ -152,7 +151,6 @@ jvm_library( "//python/python-poetry/common", "//python/python-uv/common", "//python/python-exec-service/execService.python", - "//python/python-exec-service/execService.python:execService.python_test_lib", "//platform/execution", "//platform/forms_rt:java-guiForms-rt", ], diff --git a/python/intellij.python.community.tests.iml b/python/intellij.python.community.tests.iml index 636155827b32..510603816c1a 100644 --- a/python/intellij.python.community.tests.iml +++ b/python/intellij.python.community.tests.iml @@ -85,8 +85,6 @@ - - @@ -100,5 +98,17 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/python/interpreters/BUILD.bazel b/python/interpreters/BUILD.bazel index c50a39aebaff..034e1a05ef6e 100644 --- a/python/interpreters/BUILD.bazel +++ b/python/interpreters/BUILD.bazel @@ -31,53 +31,4 @@ jvm_library( "//platform/eel-provider", ] ) - -jvm_library( - name = "interpreters_test_lib", - visibility = ["//visibility:public"], - srcs = glob(["tests/**/*.kt", "tests/**/*.java", "tests/**/*.form"], allow_empty = True), - associates = [":interpreters"], - deps = [ - "@lib//:kotlin-stdlib", - "//python/services/shared", - "//python/services/shared:shared_test_lib", - "@lib//:jetbrains-annotations", - "//platform/extensions", - "//python/python-sdk:sdk", - "//python/python-sdk:sdk_test_lib", - "//python/python-psi-impl:psi-impl", - "//platform/projectModel-api:projectModel", - "//platform/util", - "//platform/diagnostic", - "//python/python-exec-service/execService.python", - "//python/python-exec-service/execService.python:execService.python_test_lib", - "//python/python-exec-service:community-execService", - "//python/python-exec-service:community-execService_test_lib", - "//python/python-parser:parser", - "//python/openapi:community", - "//python/openapi:community_test_lib", - "//platform/core-api:core", - "//platform/eel-provider", - "//python/junit5Tests-framework:community-junit5Tests-framework_test_lib", - "@lib//:junit5", - "@lib//:junit5Params", - "//platform/testFramework/junit5", - "//platform/testFramework/junit5:junit5_test_lib", - "//platform/lang-core", - "//python/pluginCore:community-plugin", - "//python/python-venv:community-impl-venv", - "//python/python-venv:community-impl-venv_test_lib", - "//platform/execution", - "//python/impl.helperLocator:community-helpersLocator", - ] -) -### auto-generated section `build intellij.python.community.interpreters` end - -### auto-generated section `test intellij.python.community.interpreters` start -load("@community//build:tests-options.bzl", "jps_test") - -jps_test( - name = "interpreters_test", - runtime_deps = [":interpreters_test_lib"] -) -### auto-generated section `test intellij.python.community.interpreters` end \ No newline at end of file +### auto-generated section `build intellij.python.community.interpreters` end \ No newline at end of file diff --git a/python/interpreters/intellij.python.community.interpreters.iml b/python/interpreters/intellij.python.community.interpreters.iml index ea66fcbd6516..cffc5d82933c 100644 --- a/python/interpreters/intellij.python.community.interpreters.iml +++ b/python/interpreters/intellij.python.community.interpreters.iml @@ -5,7 +5,6 @@ - diff --git a/python/junit5Tests-framework/BUILD.bazel b/python/junit5Tests-framework/BUILD.bazel index cdfec02e94a9..447c81126e96 100644 --- a/python/junit5Tests-framework/BUILD.bazel +++ b/python/junit5Tests-framework/BUILD.bazel @@ -11,10 +11,7 @@ jvm_library( name = "community-junit5Tests-framework", visibility = ["//visibility:public"], srcs = glob([], allow_empty = True), - exports = [ - "//libraries/system-stubs-jupiter", - "//python/setup-test-environment:community-testFramework-testEnv", - ], + exports = ["//libraries/system-stubs-jupiter"], runtime_deps = ["//python/pluginCore:community-plugin"] ) @@ -60,7 +57,6 @@ jvm_library( "//python/openapi:community", "//python/openapi:community_test_lib", "//python/pipenv", - "//python/setup-test-environment:community-testFramework-testEnv", "//python/python-sdk:sdk", "//python/python-sdk:sdk_test_lib", "//python/python-poetry/common", @@ -70,10 +66,7 @@ jvm_library( "//platform/lang-core", "//platform/util/jdom", ], - exports = [ - "//libraries/system-stubs-jupiter", - "//python/setup-test-environment:community-testFramework-testEnv", - ], + exports = ["//libraries/system-stubs-jupiter"], runtime_deps = [":community-junit5Tests-framework"] ) ### auto-generated section `build intellij.python.community.junit5Tests.framework` end diff --git a/python/junit5Tests-framework/conda/BUILD.bazel b/python/junit5Tests-framework/conda/BUILD.bazel deleted file mode 100644 index 6ee3ff49bf26..000000000000 --- a/python/junit5Tests-framework/conda/BUILD.bazel +++ /dev/null @@ -1,44 +0,0 @@ -### auto-generated section `build intellij.python.community.junit5Tests.framework.conda` start -load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") - -resourcegroup( - name = "conda_test_resources", - srcs = glob(["testResources/**/*"]), - strip_prefix = "testResources" -) - -jvm_library( - name = "conda", - visibility = ["//visibility:public"], - srcs = glob([], allow_empty = True) -) - -jvm_library( - name = "conda_test_lib", - module_name = "intellij.python.community.junit5Tests.framework.conda", - visibility = ["//visibility:public"], - srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), - resources = [":conda_test_resources"], - deps = [ - "@lib//:kotlin-stdlib", - "//python/junit5Tests-framework:community-junit5Tests-framework_test_lib", - "//python:python-community-impl", - "//python:python-community-impl_test_lib", - "//python/setup-test-environment/conda", - "@lib//:junit5", - "@lib//:jetbrains-annotations", - "//platform/execution", - "//platform/core-api:core", - ], - runtime_deps = [":conda"] -) -### auto-generated section `build intellij.python.community.junit5Tests.framework.conda` end - -### auto-generated section `test intellij.python.community.junit5Tests.framework.conda` start -load("@community//build:tests-options.bzl", "jps_test") - -jps_test( - name = "conda_test", - runtime_deps = [":conda_test_lib"] -) -### auto-generated section `test intellij.python.community.junit5Tests.framework.conda` end \ No newline at end of file diff --git a/python/junit5Tests-framework/conda/intellij.python.community.junit5Tests.framework.conda.iml b/python/junit5Tests-framework/conda/intellij.python.community.junit5Tests.framework.conda.iml deleted file mode 100644 index 6d2913dd7d75..000000000000 --- a/python/junit5Tests-framework/conda/intellij.python.community.junit5Tests.framework.conda.iml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/PyEnvTestCaseWithConda.kt b/python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/PyEnvTestCaseWithConda.kt deleted file mode 100644 index 4e7d4c18b7a6..000000000000 --- a/python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/PyEnvTestCaseWithConda.kt +++ /dev/null @@ -1,18 +0,0 @@ -// 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.junit5Tests.framework.conda - -import com.intellij.python.community.junit5Tests.framework.conda.impl.CondaPythonEnvExtension -import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase -import org.junit.jupiter.api.extension.ExtendWith - -/** - * Python and conda env test case that supports [com.intellij.python.junit5Tests.framework.env.PythonBinaryPath] and [CondaEnv] - * Example: - * ```kotlin -@PyEnvTestCaseWithConda -class PyEnvTestExample - * ``` - */ -@PyEnvTestCase -@ExtendWith(CondaPythonEnvExtension::class) -annotation class PyEnvTestCaseWithConda \ No newline at end of file diff --git a/python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/impl/CondaPythonEnvExtension.kt b/python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/impl/CondaPythonEnvExtension.kt deleted file mode 100644 index 7595ca0a7318..000000000000 --- a/python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/impl/CondaPythonEnvExtension.kt +++ /dev/null @@ -1,12 +0,0 @@ -// 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.junit5Tests.framework.conda.impl - -import com.intellij.python.community.junit5Tests.framework.conda.CondaEnv -import com.intellij.python.community.testFramework.testEnv.conda.TypeConda -import com.intellij.python.junit5Tests.framework.env.impl.PythonEnvExtensionBase -import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv - -/** - * Mark [com.jetbrains.python.sdk.flavors.conda.PyCondaEnv] param with [com.intellij.python.junit5Tests.framework.env.CondaEnv] annotation and register this extension with [com.intellij.python.junit5Tests.framework.env.PyEnvTestCaseWithConda] - */ -internal class CondaPythonEnvExtension : PythonEnvExtensionBase(CondaEnv::class, TypeConda, PyCondaEnv::class) \ No newline at end of file diff --git a/python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/impl/package-info.java b/python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/impl/package-info.java deleted file mode 100644 index 4552e778cbff..000000000000 --- a/python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/impl/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -@ApiStatus.Internal -package com.intellij.python.community.junit5Tests.framework.conda.impl; - -import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/python/junit5Tests-framework/conda/testResources/intellij.python.community.junit5Tests.framework.conda._test.xml b/python/junit5Tests-framework/conda/testResources/intellij.python.community.junit5Tests.framework.conda._test.xml deleted file mode 100644 index f9e957b7fca6..000000000000 --- a/python/junit5Tests-framework/conda/testResources/intellij.python.community.junit5Tests.framework.conda._test.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/python/junit5Tests-framework/intellij.python.community.junit5Tests.framework.iml b/python/junit5Tests-framework/intellij.python.community.junit5Tests.framework.iml index e82dd10066a1..72fd9f243943 100644 --- a/python/junit5Tests-framework/intellij.python.community.junit5Tests.framework.iml +++ b/python/junit5Tests-framework/intellij.python.community.junit5Tests.framework.iml @@ -39,7 +39,6 @@ - diff --git a/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/PyEnvTestCase.kt b/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/PyEnvTestCase.kt deleted file mode 100644 index 91669308119a..000000000000 --- a/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/PyEnvTestCase.kt +++ /dev/null @@ -1,19 +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.intellij.python.junit5Tests.framework.env - -import com.intellij.python.junit5Tests.framework.env.impl.VanillaPythonEnvExtension -import com.intellij.testFramework.junit5.TestApplication -import org.junit.jupiter.api.extension.ExtendWith - -/** - * Python env test case that supports [PythonBinaryPath] etc. - * Example: - * ```kotlin -@PyEnvTestCase -class PyEnvTestExample - * ``` - */ -@TestApplication -@Target(AnnotationTarget.CLASS) -@ExtendWith(VanillaPythonEnvExtension::class) -annotation class PyEnvTestCase \ No newline at end of file diff --git a/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/impl/PythonEnvExtensionBase.kt b/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/impl/PythonEnvExtensionBase.kt deleted file mode 100644 index 290e2c6f41dc..000000000000 --- a/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/impl/PythonEnvExtensionBase.kt +++ /dev/null @@ -1,102 +0,0 @@ -// 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.junit5Tests.framework.env.impl - -import com.intellij.openapi.diagnostic.Logger -import com.intellij.python.community.testFramework.testEnv.PythonType -import kotlinx.coroutines.runBlocking -import org.jetbrains.annotations.ApiStatus.Internal -import org.jetbrains.annotations.NonNls -import org.junit.jupiter.api.extension.BeforeAllCallback -import org.junit.jupiter.api.extension.ExtensionContext -import org.junit.jupiter.api.extension.ParameterContext -import org.junit.jupiter.api.extension.ParameterResolver -import org.opentest4j.TestAbortedException -import kotlin.reflect.KClass - - -/** - * Looks for the first env python and provides it as an argument. - * Looks at the system property that changes skip to failure. - * To be run by `PythonJUnit5TestsEnv`. - * - * To be used as an "adapter" to connect inheritor to JUnit5. - * [PYTHON_TYPE] creates [ENV] which is then disposed. - * - * [lazy]: postpone resource creation until first parameter used. - * [additionalTags]: only use pythons with these tags - */ -@Internal -abstract class PythonEnvExtensionBase>( - private val annotation: KClass, - private val pythonType: PYTHON_TYPE, - private val envType: KClass, - private val lazy: Boolean = true, - private vararg val additionalTags: @NonNls String, -) : ParameterResolver, BeforeAllCallback { - - protected companion object { - val LOG = Logger.getInstance(this::class.java) - } - - private class ResourceWrapper(val env: ENV, val closeable: AutoCloseable) : java.lang.AutoCloseable { - override fun close() { - closeable.close() - } - } - - private val namespace = ExtensionContext.Namespace.create(this::class.java, pythonType, envType) - private val key = "instance$envType" - - override fun beforeAll(context: ExtensionContext) { - if (!lazy) { - createResource(context) - } - } - - private fun createEnv(): ResourceWrapper { - val (_, autoClosable, env) = runBlocking { - pythonType.createSdkClosableEnv(*additionalTags).getOrElse { - // Logging due to IDEA-356206 - LOG.warn(it) - val message = "Couldn't find python to run test against ($pythonType , ${additionalTags.toList()})" - if (System.getProperty("pycharm.env.tests.fail.if.no.python") != null) { - throw AssertionError(message, it) - } - else { - throw TestAbortedException(message, it) - } - } - } - try { - onEnvFound(env) - } - catch (e: Throwable) { - autoClosable.close() - throw e - } - LOG.info("Env for python found at $env") - return ResourceWrapper(env, autoClosable) - } - - /** - * Callback when [ENV] created - */ - open fun onEnvFound(env: ENV) = Unit - - - override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean { - if (parameterContext.parameter.type != envType.java) return false - return parameterContext.parameter.isAnnotationPresent(annotation.java) - } - - - override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): ENV = createResource(extensionContext) - - private fun createResource(extensionContext: ExtensionContext): ENV { - val resourceWrapper = extensionContext.getStore(namespace).getOrComputeIfAbsent(key, { createEnv() }, ResourceWrapper::class.java) - val env = resourceWrapper.env - assert(envType.isInstance(env)) - @Suppress("UNCHECKED_CAST") - return env as ENV - } -} diff --git a/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/impl/VanillaPythonEnvExtension.kt b/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/impl/VanillaPythonEnvExtension.kt deleted file mode 100644 index d97a16148d79..000000000000 --- a/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/impl/VanillaPythonEnvExtension.kt +++ /dev/null @@ -1,76 +0,0 @@ -// 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.junit5Tests.framework.env.impl - -import com.intellij.execution.configurations.GeneralCommandLine -import com.intellij.execution.process.CapturingProcessHandler -import com.intellij.execution.process.ProcessNotCreatedException -import com.intellij.ide.util.PropertiesComponent -import com.intellij.python.community.impl.pipenv.pipenvPath -import com.intellij.python.community.impl.poetry.common.poetryPath -import com.intellij.python.community.testFramework.testEnv.PythonType -import com.intellij.python.community.testFramework.testEnv.TypeVanillaPython3 -import com.intellij.python.junit5Tests.framework.env.PythonBinaryPath -import com.intellij.python.junit5Tests.framework.resolvePythonTool -import com.jetbrains.python.PythonBinary -import com.jetbrains.python.sdk.impl.resolvePythonHome -import java.nio.file.Path - -/** - * Mark [PythonBinary] param with [PythonBinary] annotation and register this extension with [com.intellij.python.junit5Tests.framework.env.PyEnvTestCase]. - * - * It also searches for poetry and stores it in [PropertiesComponent] - */ -internal class VanillaPythonEnvExtension : PythonEnvExtensionBase( - annotation = PythonBinaryPath::class, - pythonType = TypeVanillaPython3, - envType = PythonBinary::class, - lazy = false, - additionalTags = arrayOf("poetry") -) { - private companion object { - val checkedTools = mutableMapOf>() - } - - override fun onEnvFound(env: PythonBinary) { - // There is no API that accepts path to poetry or pipenv: only this global object is used - PropertiesComponent.getInstance().poetryPath = checkAndGetToolPath(env, "poetry", true) - PropertiesComponent.getInstance().pipenvPath = checkAndGetToolPath(env, "pipenv", false) - - val uv = env.resolvePythonHome().resolvePythonTool("uv") - PropertiesComponent.getInstance().setValue( - "PyCharm.Uv.Path", - uv.toString() - ) - } - - private fun checkAndGetToolPath(env: PythonBinary, toolName: String, toThrow: Boolean): String? { - val tool = env.resolvePythonHome().resolvePythonTool(toolName) - if (checkedTools[toolName]?.contains(tool) != true) { - val output = try { - CapturingProcessHandler(GeneralCommandLine(tool.toString(), "--version")).runProcess(60_000, true) - } - catch (e: ProcessNotCreatedException) { - val customPythonMessage = buildString { - PythonType.customPythonMessage?.let { - append(it) - append(" install ${toolName} there, i.e: 'python -m pip install ${toolName}' ") - } - append(" or run/rerun ") - append(PythonType.BUILD_KTS_MESSAGE) - } - if (toThrow) { - throw AssertionError(customPythonMessage, e) - } - else { - LOG.warn(customPythonMessage) - return null - } - } - assert(output.exitCode == 0) { "$tool seems to be broken, output: $output. For Windows check `fix_path.cmd`" } - LOG.info("${toolName} found at $tool") - checkedTools.compute(toolName) { _, v -> (v ?: mutableSetOf()).also { it.add(tool) } } - } - - return tool.toString() - } -} \ No newline at end of file diff --git a/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/sdkFixture.kt b/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/sdkFixture.kt index 3fc8d7066390..4eb85c6cf14a 100644 --- a/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/sdkFixture.kt +++ b/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/sdkFixture.kt @@ -1,28 +1,21 @@ -// 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.junit5Tests.framework.env -import com.intellij.openapi.application.edtWriteAction import com.intellij.openapi.project.Project -import com.intellij.openapi.projectRoots.ProjectJdkTable import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.projectRoots.SdkAdditionalData import com.intellij.openapi.projectRoots.SdkTypeId import com.intellij.platform.testFramework.junit5.projectStructure.fixture.sdkFixture -import com.intellij.python.community.testFramework.testEnv.PythonType -import com.intellij.python.community.testFramework.testEnv.TypeVanillaPython3 import com.intellij.testFramework.junit5.fixture.TestFixture -import com.intellij.testFramework.junit5.fixture.testFixture import com.jetbrains.python.PyNames -import com.jetbrains.python.PythonBinary -import com.jetbrains.python.sdk.persist -import org.jetbrains.annotations.ApiStatus.Internal +import org.jdom.Element +import org.jetbrains.annotations.ApiStatus import java.nio.file.Path /** * Sdk with environment info. Use it as a regular sdk, but env fixtures (venv, conda) might use [env] */ -@Internal -class SdkFixture internal constructor(private val sdk: Sdk, val env: ENV) : Sdk by sdk { +@ApiStatus.Internal +class SdkFixture(private val sdk: Sdk, val env: ENV) : Sdk by sdk { override fun equals(other: Any?): Boolean { if (this === other) return true @@ -38,42 +31,20 @@ class SdkFixture internal constructor(private val sdk: Sdk, val env: } } - /** * Create mock (not a real python, but with [homePath]) SDK */ fun TestFixture.pyMockSdkFixture(homePath: TestFixture) = sdkFixture("PyMockSDK" + System.currentTimeMillis().toString(), PyMockSdkTypeId, homePath) -/** - * Create sdk fixture with [TypeVanillaPython3] - */ -fun pySdkFixture(): TestFixture> = pySdkFixture(TypeVanillaPython3) - -/** - * Creates [Sdk] (if you only need a python path, use [PythonBinaryPath] or [com.intellij.python.community.junit5Tests.framework.conda.CondaEnv]) - */ -fun pySdkFixture( - pythonType: PythonType, -): TestFixture> = testFixture { - val (sdk, autoClosable, env) = pythonType.createSdkClosableEnv().getOrThrow() - sdk.persist() - initialized(SdkFixture(sdk, env)) { - edtWriteAction { - ProjectJdkTable.getInstance().removeJdk(sdk) - autoClosable.close() - } - } -} - private object PyMockSdkTypeId : SdkTypeId { override fun getName(): String = PyNames.PYTHON_SDK_ID_NAME override fun getVersionString(sdk: Sdk): String? = null - override fun saveAdditionalData(additionalData: SdkAdditionalData, additional: org.jdom.Element) = Unit + override fun saveAdditionalData(additionalData: SdkAdditionalData, additional: Element) = Unit override fun loadAdditionalData( currentSdk: Sdk, - additional: org.jdom.Element, + additional: Element, ): SdkAdditionalData? = null } \ No newline at end of file diff --git a/python/junit5Tests-framework/testResources/intellij.python.community.junit5Tests.framework._test.xml b/python/junit5Tests-framework/testResources/intellij.python.community.junit5Tests.framework._test.xml index 1e2935adc5f6..6460d1294947 100644 --- a/python/junit5Tests-framework/testResources/intellij.python.community.junit5Tests.framework._test.xml +++ b/python/junit5Tests-framework/testResources/intellij.python.community.junit5Tests.framework._test.xml @@ -6,9 +6,9 @@ - + \ No newline at end of file diff --git a/python/python-exec-service/execService.python/BUILD.bazel b/python/python-exec-service/execService.python/BUILD.bazel index 73d5627172ad..44541c94de4e 100644 --- a/python/python-exec-service/execService.python/BUILD.bazel +++ b/python/python-exec-service/execService.python/BUILD.bazel @@ -27,43 +27,4 @@ jvm_library( "//python/python-sdk:sdk", ] ) - -jvm_library( - name = "execService.python_test_lib", - visibility = ["//visibility:public"], - srcs = glob(["tests/**/*.kt", "tests/**/*.java", "tests/**/*.form"], allow_empty = True), - associates = [":execService.python"], - deps = [ - "@lib//:kotlin-stdlib", - "//python/python-exec-service:community-execService", - "//python/python-exec-service:community-execService_test_lib", - "//python/openapi:community", - "//python/openapi:community_test_lib", - "@lib//:jetbrains-annotations", - "//libraries/kotlinx/coroutines/core", - "//platform/eel", - "//platform/eel-provider", - "//python/impl.helperLocator:community-helpersLocator", - "@lib//:junit5", - "//platform/testFramework/junit5", - "//platform/testFramework/junit5:junit5_test_lib", - "//python/junit5Tests-framework:community-junit5Tests-framework_test_lib", - "//platform/testFramework/junit5/eel", - "//platform/testFramework/junit5/eel:eel_test_lib", - "@lib//:junit5Params", - "//platform/util", - "//platform/core-api:core", - "//python/python-sdk:sdk", - "//python/python-sdk:sdk_test_lib", - ] -) -### auto-generated section `build intellij.python.community.execService.python` end - -### auto-generated section `test intellij.python.community.execService.python` start -load("@community//build:tests-options.bzl", "jps_test") - -jps_test( - name = "execService.python_test", - runtime_deps = [":execService.python_test_lib"] -) -### auto-generated section `test intellij.python.community.execService.python` end \ No newline at end of file +### auto-generated section `build intellij.python.community.execService.python` end \ No newline at end of file diff --git a/python/python-exec-service/execService.python/intellij.python.community.execService.python.iml b/python/python-exec-service/execService.python/intellij.python.community.execService.python.iml index 8e8942a076f4..40720d651d08 100644 --- a/python/python-exec-service/execService.python/intellij.python.community.execService.python.iml +++ b/python/python-exec-service/execService.python/intellij.python.community.execService.python.iml @@ -5,7 +5,6 @@ - diff --git a/python/python-features-trainer/BUILD.bazel b/python/python-features-trainer/BUILD.bazel index 07ce25202c7a..888d439860cf 100644 --- a/python/python-features-trainer/BUILD.bazel +++ b/python/python-features-trainer/BUILD.bazel @@ -90,6 +90,8 @@ jvm_library( "//python/services/internal-impl:python-community-services-internal-impl_test_lib", "//python/pluginCore:community-plugin", "//libraries/stream", + "//python/python-test-env/junit5:junit5_test_lib", + "//python/python-test-env/common:common_test_lib", ] ) ### auto-generated section `build intellij.python.featuresTrainer` end diff --git a/python/python-features-trainer/intellij.python.featuresTrainer.iml b/python/python-features-trainer/intellij.python.featuresTrainer.iml index b33aaba17eb2..7911fc4ef5f1 100644 --- a/python/python-features-trainer/intellij.python.featuresTrainer.iml +++ b/python/python-features-trainer/intellij.python.featuresTrainer.iml @@ -41,5 +41,7 @@ + + \ No newline at end of file diff --git a/python/python-features-trainer/testResources/intellij.python.featuresTrainer._test.xml b/python/python-features-trainer/testResources/intellij.python.featuresTrainer._test.xml index 5b07ae9110f3..266f454adb15 100644 --- a/python/python-features-trainer/testResources/intellij.python.featuresTrainer._test.xml +++ b/python/python-features-trainer/testResources/intellij.python.featuresTrainer._test.xml @@ -4,5 +4,6 @@ + \ No newline at end of file diff --git a/python/python-features-trainer/testSrc/com/intellij/python/junit5Tests/env/PythonLangSupportTest.kt b/python/python-features-trainer/testSrc/com/intellij/python/junit5Tests/env/PythonLangSupportTest.kt index 22608dcba263..f2560fc57476 100644 --- a/python/python-features-trainer/testSrc/com/intellij/python/junit5Tests/env/PythonLangSupportTest.kt +++ b/python/python-features-trainer/testSrc/com/intellij/python/junit5Tests/env/PythonLangSupportTest.kt @@ -13,16 +13,15 @@ import com.intellij.python.featuresTrainer.ift.PythonLangSupport import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase import com.intellij.python.junit5Tests.framework.env.PythonBinaryPath import com.intellij.python.junit5Tests.framework.winLockedFile.deleteCheckLocking +import com.intellij.python.test.env.common.PredefinedPyEnvironments import com.intellij.testFramework.common.timeoutRunBlocking import com.jetbrains.python.PythonBinary import com.jetbrains.python.errorProcessing.ErrorSink -import com.jetbrains.python.errorProcessing.PyError import com.jetbrains.python.getOrThrow import com.jetbrains.python.sdk.pythonSdk import com.jetbrains.python.venvReader.VirtualEnvReader.Companion.DEFAULT_VIRTUALENV_DIRNAME import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.withContext import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.Assertions @@ -35,7 +34,7 @@ import training.project.ProjectUtils import java.nio.file.Path import kotlin.time.Duration.Companion.minutes -@PyEnvTestCase +@PyEnvTestCase(PredefinedPyEnvironments.VANILLA_3_14) class PythonLangSupportTest { companion object { @JvmStatic diff --git a/python/python-process-output/impl/BUILD.bazel b/python/python-process-output/impl/BUILD.bazel index 5f6cf0db3ac8..759c78e699ea 100644 --- a/python/python-process-output/impl/BUILD.bazel +++ b/python/python-process-output/impl/BUILD.bazel @@ -103,6 +103,7 @@ jvm_library( "//libraries/kotlinx/collections-immutable:libraries-kotlinx-collections-immutable", "//libraries/kotlinx/coroutines/test", "//libraries/kotlin/reflect", + "//python/python-test-env/junit5:junit5_test_lib", ], plugins = ["@lib//:compose-plugin"] ) diff --git a/python/python-process-output/impl/intellij.python.processOutput.impl.iml b/python/python-process-output/impl/intellij.python.processOutput.impl.iml index 8fa5935db3b9..5ace656e3d9e 100644 --- a/python/python-process-output/impl/intellij.python.processOutput.impl.iml +++ b/python/python-process-output/impl/intellij.python.processOutput.impl.iml @@ -66,5 +66,6 @@ + \ No newline at end of file diff --git a/python/python-process-output/impl/testResources/intellij.python.processOutput.impl._test.xml b/python/python-process-output/impl/testResources/intellij.python.processOutput.impl._test.xml index d431c313f2b3..925f3e9c2039 100644 --- a/python/python-process-output/impl/testResources/intellij.python.processOutput.impl._test.xml +++ b/python/python-process-output/impl/testResources/intellij.python.processOutput.impl._test.xml @@ -3,6 +3,7 @@ + diff --git a/python/python-pyproject/BUILD.bazel b/python/python-pyproject/BUILD.bazel index e01300ec55e8..39b26baf47ce 100644 --- a/python/python-pyproject/BUILD.bazel +++ b/python/python-pyproject/BUILD.bazel @@ -89,8 +89,6 @@ jvm_library( "//python/junit5Tests-framework:community-junit5Tests-framework_test_lib", "//platform/testFramework", "//platform/testFramework:testFramework_test_lib", - "//python/python-venv:community-impl-venv", - "//python/python-venv:community-impl-venv_test_lib", "//platform/statistics", "//platform/statistics:statistics_test_lib", "//platform/indexing-api:indexing", diff --git a/python/python-pyproject/intellij.python.pyproject.iml b/python/python-pyproject/intellij.python.pyproject.iml index c4afebc6fb80..7582fe85adbf 100644 --- a/python/python-pyproject/intellij.python.pyproject.iml +++ b/python/python-pyproject/intellij.python.pyproject.iml @@ -39,7 +39,6 @@ - diff --git a/python/python-pyproject/src/com/intellij/python/pyproject/model/internal/pyProjectToml/FSWalkInfoWithToml.kt b/python/python-pyproject/src/com/intellij/python/pyproject/model/internal/pyProjectToml/FSWalkInfoWithToml.kt index 4e2e0d6485f7..d049ddf8b2b0 100644 --- a/python/python-pyproject/src/com/intellij/python/pyproject/model/internal/pyProjectToml/FSWalkInfoWithToml.kt +++ b/python/python-pyproject/src/com/intellij/python/pyproject/model/internal/pyProjectToml/FSWalkInfoWithToml.kt @@ -9,4 +9,4 @@ internal data class FSWalkInfoWithToml(val tomlFiles: Map, // Files only -internal data class FsWalkInfoNoToml(val rawTomlFiles: List, val excludedDirs: List) +data class FsWalkInfoNoToml(val rawTomlFiles: List, val excludedDirs: List) diff --git a/python/python-pyproject/src/com/intellij/python/pyproject/model/internal/pyProjectToml/tomFileTools.kt b/python/python-pyproject/src/com/intellij/python/pyproject/model/internal/pyProjectToml/tomFileTools.kt index 504a9eaaab2e..faef3c40aa96 100644 --- a/python/python-pyproject/src/com/intellij/python/pyproject/model/internal/pyProjectToml/tomFileTools.kt +++ b/python/python-pyproject/src/com/intellij/python/pyproject/model/internal/pyProjectToml/tomFileTools.kt @@ -42,7 +42,7 @@ internal suspend fun walkFileSystemWithTomlContent(root: Directory): Result { val excludedDirs = ArrayList(10) diff --git a/python/python-pyproject/testResources/intellij.python.pyproject._test.xml b/python/python-pyproject/testResources/intellij.python.pyproject._test.xml index ef260a941d18..442b0852f4f7 100644 --- a/python/python-pyproject/testResources/intellij.python.pyproject._test.xml +++ b/python/python-pyproject/testResources/intellij.python.pyproject._test.xml @@ -6,6 +6,5 @@ - \ No newline at end of file diff --git a/python/python-terminal/BUILD.bazel b/python/python-terminal/BUILD.bazel index 479fc0699a56..affb7a9aa872 100644 --- a/python/python-terminal/BUILD.bazel +++ b/python/python-terminal/BUILD.bazel @@ -55,13 +55,12 @@ jvm_library( "//python/testFramework", "//platform/eel-provider", "//libraries/hamcrest", - "//python/setup-test-environment:community-testFramework-testEnv", "//python/python-venv:community-impl-venv", "//python/python-venv:community-impl-venv_test_lib", - "//python/junit5Tests-framework/conda:conda_test_lib", "@lib//:junit5Params", "@lib//:junit5Pioneer", "//platform/testFramework/common", + "//python/python-test-env/junit5:junit5_test_lib", ] ) ### auto-generated section `build intellij.python.terminal` end diff --git a/python/python-terminal/intellij.python.terminal.iml b/python/python-terminal/intellij.python.terminal.iml index 84e2eebef262..a3a3bd9d83b7 100644 --- a/python/python-terminal/intellij.python.terminal.iml +++ b/python/python-terminal/intellij.python.terminal.iml @@ -25,11 +25,10 @@ - - + \ No newline at end of file diff --git a/python/python-terminal/testResources/intellij.python.terminal._test.xml b/python/python-terminal/testResources/intellij.python.terminal._test.xml index a578ad298ee1..6c93f5661378 100644 --- a/python/python-terminal/testResources/intellij.python.terminal._test.xml +++ b/python/python-terminal/testResources/intellij.python.terminal._test.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/python/python-terminal/tests/com/intellij/python/junit5Tests/env/terminal/PyVirtualEnvTerminalCustomizerTest.kt b/python/python-terminal/tests/com/intellij/python/junit5Tests/env/terminal/PyVirtualEnvTerminalCustomizerTest.kt index 12fe01657685..fde4735e1fe0 100644 --- a/python/python-terminal/tests/com/intellij/python/junit5Tests/env/terminal/PyVirtualEnvTerminalCustomizerTest.kt +++ b/python/python-terminal/tests/com/intellij/python/junit5Tests/env/terminal/PyVirtualEnvTerminalCustomizerTest.kt @@ -15,13 +15,13 @@ import com.intellij.platform.eel.provider.localEel import com.intellij.platform.eel.provider.utils.readWholeText import com.intellij.platform.eel.provider.utils.sendWholeText import com.intellij.platform.eel.spawnProcess -import com.intellij.python.community.impl.venv.tests.pyVenvFixture import com.intellij.python.community.junit5Tests.framework.conda.CondaEnv import com.intellij.python.community.junit5Tests.framework.conda.PyEnvTestCaseWithConda import com.intellij.python.community.junit5Tests.framework.conda.createCondaEnv import com.intellij.python.junit5Tests.framework.env.pySdkFixture import com.intellij.python.junit5Tests.framework.winLockedFile.deleteCheckLocking import com.intellij.python.terminal.PyVirtualEnvTerminalCustomizer +import com.intellij.python.test.env.junit5.pyVenvFixture import com.intellij.testFramework.common.timeoutRunBlocking import com.intellij.testFramework.junit5.fixture.moduleFixture import com.intellij.testFramework.junit5.fixture.projectFixture diff --git a/python/python-test-env/common/BUILD.bazel b/python/python-test-env/common/BUILD.bazel new file mode 100644 index 000000000000..6c1e78459da5 --- /dev/null +++ b/python/python-test-env/common/BUILD.bazel @@ -0,0 +1,99 @@ +### auto-generated section `build intellij.python.test.env.common` start +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +resourcegroup( + name = "common_test_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "common", + visibility = ["//visibility:public"], + srcs = glob([], allow_empty = True), + exports = [ + "//python/python-test-env/core", + "//python/python-test-env/conda", + "//python/python-test-env/plain", + "//python/python-test-env/uv", + ], + runtime_deps = [ + "@lib//:jetbrains-annotations", + "//python/python-test-env/core", + "//python/python-test-env/conda", + "@lib//:kotlin-stdlib", + "//python/python-test-env/plain", + "//python/python-test-env/uv", + "//platform/execution", + "//python:python-community-impl", + "//python/python-sdk:sdk", + "//platform/projectModel-api:projectModel", + "@lib//:kotlinx-coroutines-core", + "//platform/remote-core", + "//platform/core-api:core", + "//platform/lang-impl", + "//platform/lang-core", + "//platform/ide-core", + "//python/services/system-python", + "//platform/platform-impl:ide-impl", + "//platform/eel", + "//platform/eel-provider", + ] +) + +jvm_library( + name = "common_test_lib", + module_name = "intellij.python.test.env.common", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [":common_test_resources"], + deps = [ + "@lib//:jetbrains-annotations", + "//python/python-test-env/core", + "//python/python-test-env/core:core_test_lib", + "//python/python-test-env/conda", + "//python/python-test-env/conda:conda_test_lib", + "@lib//:kotlin-stdlib", + "//python/python-test-env/plain", + "//python/python-test-env/plain:plain_test_lib", + "//python/python-test-env/uv", + "//python/python-test-env/uv:uv_test_lib", + "//platform/execution", + "//python:python-community-impl", + "//python/python-sdk:sdk", + "//python/python-sdk:sdk_test_lib", + "//platform/projectModel-api:projectModel", + "@lib//:kotlinx-coroutines-core", + "//platform/remote-core", + "//platform/core-api:core", + "//platform/lang-impl", + "//platform/lang-core", + "//platform/ide-core", + "//python/services/system-python", + "//python/services/system-python:system-python_test_lib", + "//platform/platform-impl:ide-impl", + "//platform/eel", + "//platform/eel-provider", + ], + exports = [ + "//python/python-test-env/core", + "//python/python-test-env/core:core_test_lib", + "//python/python-test-env/conda", + "//python/python-test-env/conda:conda_test_lib", + "//python/python-test-env/plain", + "//python/python-test-env/plain:plain_test_lib", + "//python/python-test-env/uv", + "//python/python-test-env/uv:uv_test_lib", + ], + runtime_deps = [":common"] +) +### auto-generated section `build intellij.python.test.env.common` end + +### auto-generated section `test intellij.python.test.env.common` start +load("@community//build:tests-options.bzl", "jps_test") + +jps_test( + name = "common_test", + runtime_deps = [":common_test_lib"] +) +### auto-generated section `test intellij.python.test.env.common` end \ No newline at end of file diff --git a/python/python-test-env/common/intellij.python.test.env.common.iml b/python/python-test-env/common/intellij.python.test.env.common.iml new file mode 100644 index 000000000000..4893e3836168 --- /dev/null +++ b/python/python-test-env/common/intellij.python.test.env.common.iml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/python/python-test-env/common/resources/intellij.python.test.env.common.xml b/python/python-test-env/common/resources/intellij.python.test.env.common.xml new file mode 100644 index 000000000000..a3550ad7a05f --- /dev/null +++ b/python/python-test-env/common/resources/intellij.python.test.env.common.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/python/python-test-env/common/src/com/intellij/python/test/env/common/EnvTestPythonProvider.kt b/python/python-test-env/common/src/com/intellij/python/test/env/common/EnvTestPythonProvider.kt new file mode 100644 index 000000000000..8688ab870214 --- /dev/null +++ b/python/python-test-env/common/src/com/intellij/python/test/env/common/EnvTestPythonProvider.kt @@ -0,0 +1,42 @@ +// 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.test.env.common + +import com.intellij.platform.eel.EelApi +import com.intellij.platform.eel.provider.localEel +import com.intellij.python.community.services.systemPython.SystemPythonProvider +import com.intellij.python.test.env.core.PyEnvironmentFactory +import com.jetbrains.python.PythonBinary +import com.jetbrains.python.Result +import com.jetbrains.python.errorProcessing.PyResult +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking + +/** + * Register tests pythons as system pythons + */ +class EnvTestPythonProvider(private val factory: PyEnvironmentFactory) : SystemPythonProvider { + + private val localPythons by lazy { + runBlocking(Dispatchers.IO) { + listOf( + PredefinedPyEnvironments.VANILLA_3_12, + // Add Py27 temporary to test Py27 + PredefinedPyEnvironments.VANILLA_2_7, + ) + .map { factory.createEnvironment(it) } + .map { it.pythonPath } + .toSet() + } + } + + override suspend fun findSystemPythons(eelApi: EelApi): PyResult> { + // It is perfectly valid not to find any python because some tests might run without a python and still have this module on a class-path + val pythons = if (eelApi == localEel) { + localPythons + } else { + emptySet() + } + + return Result.Companion.success(pythons) + } +} \ No newline at end of file diff --git a/python/python-test-env/common/src/com/intellij/python/test/env/common/PredefinedPyEnvironments.kt b/python/python-test-env/common/src/com/intellij/python/test/env/common/PredefinedPyEnvironments.kt new file mode 100644 index 000000000000..de705f674168 --- /dev/null +++ b/python/python-test-env/common/src/com/intellij/python/test/env/common/PredefinedPyEnvironments.kt @@ -0,0 +1,226 @@ +// 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.test.env.common + +import com.intellij.python.test.env.conda.condaEnvironment +import com.intellij.python.test.env.core.PyEnvironmentFactory +import com.intellij.python.test.env.core.PyEnvironmentSpec +import com.intellij.python.test.env.plain.pythonEnvironment +import com.intellij.python.test.env.plain.venvEnvironment +import org.jetbrains.annotations.ApiStatus + +/** + * Predefined Python environments matching those defined in build.gradle.kts. + * These environments are set up by the Gradle build system. + */ +@ApiStatus.Internal +enum class PredefinedPyEnvironments(val spec: PyEnvironmentSpec<*>) { + + /** + * Python 2.7.18 + * ID: python2.7 + * Tags: python2.7 + * Packages: virtualenv + */ + VENV_2_7(venvEnvironment { + pythonVersion = pythonVersion("2.7") + libraries { + +"virtualenv" + } + }), + + VANILLA_2_7(pythonEnvironment { + pythonVersion = pythonVersion("2.7") + }), + + /** + * Python 3.8 with extensive testing libraries + * ID: python3.8 + * Tags: python3.8, python3, ipython, ipython780, skeletons, django, behave, behave-django, tox, jinja2, + * packaging, pytest, nose, django-nose, django2, xdist, untangle, pandas, qt (Linux only) + * Packages: virtualenv, ipython==7.8, django==2.2, behave, jinja2, tox>=2.0, nose, pytest, django-nose, + * behave-django, pytest-xdist, untangle, numpy, pandas, pyqt5==5.12, PySide2==5.12.1 (Linux only) + */ + VENV_3_8_FULL(venvEnvironment { + pythonVersion = pythonVersion("3.8") + libraries { + +"virtualenv" + +"ipython==7.8" + +"django==2.2" + +"behave" + +"jinja2" + +"tox>=2.0" + +"nose" + +"pytest" + +"django-nose" + +"behave-django" + +"pytest-xdist" + +"untangle" + +"numpy" + +"pandas" + } + }), + + /** + * Python 3.9 with pytest + * ID: python3.9 + * Tags: python3.9, python3, pytest, xdist, packaging + * Packages: virtualenv, pytest, pytest-xdist + */ + VENV_3_9(venvEnvironment { + pythonVersion = pythonVersion("3.9") + libraries { + +"virtualenv" + +"pytest" + +"pytest-xdist" + } + }), + + /** + * Python 3.10 minimal + * ID: python3.10 + * Tags: python3.10, untangle + * Packages: virtualenv, untangle + */ + VENV_3_10(venvEnvironment { + pythonVersion = pythonVersion("3.10") + libraries { + +"virtualenv" + +"untangle" + } + }), + + VANILLA_3_11(pythonEnvironment { + pythonVersion = pythonVersion("3.11") + }), + + /** + * Python 3.11 with modern tooling + * ID: python3.11 + * Tags: python3.11, python3, black, poetry, uv, joblib, tensorflow + * Packages: virtualenv, black==23.1.0, joblib, tensorflow, poetry, uv + */ + VENV_3_11(venvEnvironment { + pythonVersion = pythonVersion("3.11") + libraries { + +"virtualenv" + +"black==23.1.0" + +"joblib" + +"tensorflow" + +"poetry" + +"uv" + } + }), + + VANILLA_3_12(pythonEnvironment { + pythonVersion = pythonVersion("3.12") + }), + + /** + * Python 3.12 with extensive tooling + * ID: python3.12 + * Tags: python3, poetry, uv, hatch, pipenv, python3.12, messages, twisted, pytest, black-fragments-formatting + * Packages: virtualenv, teamcity-messages, Twisted, pytest, poetry, uv, hatch, pipenv, black>=23.11.0, pypiwin32 (Windows only) + */ + VENV_3_12(venvEnvironment { + pythonVersion = pythonVersion("3.12") + libraries { + +"virtualenv" + +"teamcity-messages" + +"Twisted" + +"pytest" + +"poetry" + +"uv" + +"hatch" + +"pipenv" + +"black>=23.11.0" + } + }), + + /** + * Python 3.12 with Django (latest) + * ID: py312_django_latest + * Tags: python3.12, django, django20, behave, behave-django, django2, pytest, untangle + * Packages: virtualenv, django, behave-django, behave, pytest, untangle, djangorestframework + */ + VENV_3_12_DJANGO(venvEnvironment { + pythonVersion = pythonVersion("3.12") + libraries { + +"virtualenv" + +"django" + +"behave-django" + +"behave" + +"pytest" + +"untangle" + +"djangorestframework" + } + }), + + VANILLA_3_13(pythonEnvironment { + pythonVersion = pythonVersion("3.13") + }), + + /** + * Python 3.13 with ruff + * ID: python3.13 + * Tags: python3.13, python3, ruff + * Packages: virtualenv, ruff + */ + VENV_3_13(venvEnvironment { + pythonVersion = pythonVersion("3.13") + libraries { + +"virtualenv" + +"ruff" + } + }), + + VANILLA_3_14(pythonEnvironment { + pythonVersion = pythonVersion("3.14") + }), + + VENV_3_14(venvEnvironment { + pythonVersion = pythonVersion("3.14") + libraries { + +"virtualenv" + } + }), + + /** + * Conda environment with pinned Miniconda version + * Tags: conda + */ + CONDA(condaEnvironment("py312_24.9.2-0") { + pythonVersion = pythonVersion("3.12") + }); + + companion object { + + /** + * Environment tags for finding which tags an environment supports + */ + val ENVIRONMENTS_TO_TAGS: Map> = mapOf( + VENV_2_7 to setOf("python2.7"), + VENV_3_8_FULL to setOf( + "python3", "python3.8", "pytest", "django", "django2", "behave", "behave-django", + "ipython", "ipython780", "skeletons", "tox", "jinja2", "packaging", "nose", + "django-nose", "xdist", "untangle", "pandas" + ), + VENV_3_9 to setOf("python3", "python3.9", "pytest", "xdist", "packaging"), + VENV_3_10 to setOf("python3.10", "untangle"), + VENV_3_11 to setOf("python3", "python3.11", "black", "poetry", "uv", "joblib", "tensorflow"), + VENV_3_12 to setOf( + "python3", "python3.12", "poetry", "uv", "hatch", "pipenv", "pytest", + "black", "black-fragments-formatting", "messages", "twisted" + ), + VENV_3_12_DJANGO to setOf( + "python3.12", "django", "django2", "django20", "behave", "behave-django", + "pytest", "untangle" + ), + VENV_3_13 to setOf("python3.13", "ruff"), + VENV_3_14 to setOf("python3", "python3.14", "ruff"), + VANILLA_3_14 to setOf("vanilla"), + CONDA to setOf("conda") + ) + } +} + +suspend fun PyEnvironmentFactory.createEnvironment(env: PredefinedPyEnvironments) = createEnvironment(env.spec) \ No newline at end of file diff --git a/python/python-test-env/common/src/com/intellij/python/test/env/common/PyEnvironmentFactories.kt b/python/python-test-env/common/src/com/intellij/python/test/env/common/PyEnvironmentFactories.kt new file mode 100644 index 000000000000..7b970763c301 --- /dev/null +++ b/python/python-test-env/common/src/com/intellij/python/test/env/common/PyEnvironmentFactories.kt @@ -0,0 +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.test.env.common + +import com.intellij.python.test.env.core.CachingPyEnvironmentFactory +import com.intellij.python.test.env.core.DefaultPyEnvironmentFactory +import com.intellij.python.test.env.core.PyEnvironmentSpec +import com.intellij.python.test.env.conda.CondaPyEnvironmentProvider +import com.intellij.python.test.env.conda.CondaPyEnvironmentSpec +import com.intellij.python.test.env.plain.PlainPyEnvironmentProvider +import com.intellij.python.test.env.plain.PlainPyEnvironmentSpec +import com.intellij.python.test.env.plain.VirtualenvPyEnvironmentProvider +import com.intellij.python.test.env.plain.VirtualenvPyEnvironmentSpec +import com.intellij.python.test.env.uv.UvPyEnvironmentProvider +import com.intellij.python.test.env.uv.UvPyEnvironmentSpec +import java.nio.file.Files +import kotlin.io.path.ExperimentalPathApi + +@OptIn(ExperimentalPathApi::class) +fun createPyEnvironmentFactory(): CachingPyEnvironmentFactory { + val workingDir = Files.createTempDirectory("pytest_java_" + System.nanoTime() + "_") + val cachingPyEnvironmentFactory = CachingPyEnvironmentFactory( + DefaultPyEnvironmentFactory( + workingDir, + listOf( + { spec: PyEnvironmentSpec<*> -> spec is PlainPyEnvironmentSpec } to PlainPyEnvironmentProvider(), + { spec: PyEnvironmentSpec<*> -> spec is VirtualenvPyEnvironmentSpec } to VirtualenvPyEnvironmentProvider(), + { spec: PyEnvironmentSpec<*> -> spec is UvPyEnvironmentSpec } to UvPyEnvironmentProvider(), + { spec: PyEnvironmentSpec<*> -> spec is CondaPyEnvironmentSpec } to CondaPyEnvironmentProvider(), + ) + ) + ) + return cachingPyEnvironmentFactory +} diff --git a/python/python-test-env/common/src/com/intellij/python/test/env/common/sdk.kt b/python/python-test-env/common/src/com/intellij/python/test/env/common/sdk.kt new file mode 100644 index 000000000000..157a6e0e3288 --- /dev/null +++ b/python/python-test-env/common/src/com/intellij/python/test/env/common/sdk.kt @@ -0,0 +1,62 @@ +// 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.test.env.common + +import com.intellij.execution.target.FullPathOnTarget +import com.intellij.execution.target.TargetEnvironmentConfiguration +import com.intellij.openapi.application.edtWriteAction +import com.intellij.openapi.projectRoots.ProjectJdkTable +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.python.test.env.core.PyEnvironmentFactory +import com.intellij.remote.RemoteSdkException +import com.jetbrains.python.sdk.PythonSdkType +import com.jetbrains.python.sdk.flavors.PyFlavorAndData +import com.jetbrains.python.sdk.flavors.PyFlavorData +import com.jetbrains.python.sdk.flavors.UnixPythonSdkFlavor +import com.jetbrains.python.target.PyTargetAwareAdditionalData +import com.jetbrains.python.target.getInterpreterVersionForJava +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +sealed class SdkCreationRequest { + object LocalPython : SdkCreationRequest() + data class RemotePython(val targetConfig: TargetEnvironmentConfiguration) : SdkCreationRequest() +} + +/** + * Creates sdk either local (you can choose type then) or remote (always vanilla) + */ +suspend fun PyEnvironmentFactory.createSdk(request: SdkCreationRequest): Pair = withContext(Dispatchers.IO) { + when (request) { + is SdkCreationRequest.LocalPython -> { + val environment = createEnvironment(PredefinedPyEnvironments.VENV_3_12) + val sdk = environment.prepareSdk() + Pair(sdk, environment) + } + is SdkCreationRequest.RemotePython -> { + val targetData = PyTargetAwareAdditionalData(PyFlavorAndData(PyFlavorData.Empty, UnixPythonSdkFlavor.getInstance()), + request.targetConfig).apply { + interpreterPath = PYTHON_PATH_ON_TARGET + } + try { + requireNotNull(targetData.getInterpreterVersionForJava()) { "No $PYTHON_PATH_ON_TARGET on target" } + } + catch (e: RemoteSdkException) { + throw RuntimeException("Error running $PYTHON_PATH_ON_TARGET", e) + } + Pair(ProjectJdkTable.getInstance().createSdk(PYTHON_PATH_ON_TARGET, PythonSdkType.getInstance()).apply { + sdkModificator.apply { + homePath = PYTHON_PATH_ON_TARGET + sdkAdditionalData = targetData + edtWriteAction { + commitChanges() + } + } + }, AutoCloseable { }) + } + } +} + +private const val PYTHON_PATH_ON_TARGET: FullPathOnTarget = "/usr/bin/python3" + + + diff --git a/python/python-test-env/conda/BUILD.bazel b/python/python-test-env/conda/BUILD.bazel new file mode 100644 index 000000000000..2ac17b8417ac --- /dev/null +++ b/python/python-test-env/conda/BUILD.bazel @@ -0,0 +1,63 @@ +### auto-generated section `build intellij.python.test.env.conda` start +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +resourcegroup( + name = "conda_test_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "conda", + visibility = ["//visibility:public"], + srcs = glob([], allow_empty = True), + runtime_deps = [ + "//python/python-test-env/core", + "@lib//:jetbrains-annotations", + "@lib//:kotlin-stdlib", + "@lib//:kotlinx-coroutines-core", + "//platform/util", + "//python:python-community-impl", + "//platform/core-api:core", + "//platform/projectModel-api:projectModel", + "//platform/execution", + "//platform/analysis-api:analysis", + "//python/python-exec-service:community-execService", + ] +) + +jvm_library( + name = "conda_test_lib", + module_name = "intellij.python.test.env.conda", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [":conda_test_resources"], + deps = [ + "//python/python-test-env/core", + "//python/python-test-env/core:core_test_lib", + "@lib//:jetbrains-annotations", + "@lib//:kotlin-stdlib", + "@lib//:kotlinx-coroutines-core", + "//platform/util", + "//python:python-community-impl", + "//platform/core-api:core", + "//platform/projectModel-api:projectModel", + "//platform/execution", + "//platform/analysis-api:analysis", + "//python/python-exec-service:community-execService", + "//python/python-exec-service:community-execService_test_lib", + "//python/python-sdk:sdk", + "//python/python-sdk:sdk_test_lib", + ], + runtime_deps = [":conda"] +) +### auto-generated section `build intellij.python.test.env.conda` end + +### auto-generated section `test intellij.python.test.env.conda` start +load("@community//build:tests-options.bzl", "jps_test") + +jps_test( + name = "conda_test", + runtime_deps = [":conda_test_lib"] +) +### auto-generated section `test intellij.python.test.env.conda` end \ No newline at end of file diff --git a/python/setup-test-environment/conda/intellij.python.community.testFramework.testEnv.conda.iml b/python/python-test-env/conda/intellij.python.test.env.conda.iml similarity index 70% rename from python/setup-test-environment/conda/intellij.python.community.testFramework.testEnv.conda.iml rename to python/python-test-env/conda/intellij.python.test.env.conda.iml index 90d35eb3d3c6..90d2725054ad 100644 --- a/python/setup-test-environment/conda/intellij.python.community.testFramework.testEnv.conda.iml +++ b/python/python-test-env/conda/intellij.python.test.env.conda.iml @@ -3,19 +3,22 @@ - - + + + + - + + - + - - + + \ No newline at end of file diff --git a/python/python-test-env/conda/resources/intellij.python.test.env.conda.xml b/python/python-test-env/conda/resources/intellij.python.test.env.conda.xml new file mode 100644 index 000000000000..ca99536911e1 --- /dev/null +++ b/python/python-test-env/conda/resources/intellij.python.test.env.conda.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/python/python-test-env/conda/src/com/intellij/python/test/env/conda/CondaPyEnvironmentProvider.kt b/python/python-test-env/conda/src/com/intellij/python/test/env/conda/CondaPyEnvironmentProvider.kt new file mode 100644 index 000000000000..1dadd75aecd9 --- /dev/null +++ b/python/python-test-env/conda/src/com/intellij/python/test/env/conda/CondaPyEnvironmentProvider.kt @@ -0,0 +1,271 @@ +// 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.test.env.conda + +import com.intellij.execution.processTools.getResultStdout +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.python.community.execService.BinOnEel +import com.intellij.python.test.env.core.* +import com.intellij.util.io.awaitExit +import com.intellij.util.system.CpuArch +import com.intellij.util.system.OS +import com.jetbrains.python.PythonBinary +import com.jetbrains.python.getOrThrow +import com.jetbrains.python.packaging.PyCondaPackageService +import com.jetbrains.python.sdk.conda.execution.CondaExecutor +import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv +import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext +import org.jetbrains.annotations.ApiStatus +import java.nio.file.Path +import kotlin.io.path.* + +/** + * Specification for Conda Python environment with mandatory version pinning. + * + * @property condaVersion Specific Miniconda version (e.g., "py312_24.9.2-0") + */ +@ApiStatus.Internal +class CondaPyEnvironmentSpec( + val condaVersion: String, +) : PyEnvironmentSpec() { + + override fun toCacheKey(): CacheKey { + return buildCacheKey("conda", "conda:$condaVersion") + } +} + +/** + * DSL entry point for creating Conda environment specifications. + * + * Version must be specified explicitly - no default or "latest" option. + * + * Example: + * ```kotlin + * val env = condaEnvironment("py312_24.9.2-0") { + * pythonVersion = "3.11" + * libraries { + * +"numpy==2.0.2" + * } + * } + * ``` + */ +@ApiStatus.Internal +fun condaEnvironment(condaVersion: String, block: CondaPyEnvironmentSpec.() -> Unit): CondaPyEnvironmentSpec { + val spec = CondaPyEnvironmentSpec(condaVersion) + spec.block() + return spec +} + +/** + * Environment provider for Conda-managed Python environments. + * + * This provider: + * 1. Downloads Miniconda for the current OS and architecture + * 2. Installs Miniconda to workingDir/conda// + * 3. Creates a new Conda environment with the requested Python version + * 4. Installs required libraries using conda/pip + * 5. Returns a PyEnvironment abstraction for further operations + * + */ +@ApiStatus.Internal +class CondaPyEnvironmentProvider : PyEnvironmentProvider("conda") { + + private companion object { + const val MINICONDA_BASE_URL = "https://repo.anaconda.com/miniconda" + } + + override suspend fun setupEnvironment(context: Context, spec: CondaPyEnvironmentSpec): PyEnvironment { + val logger = thisLogger() + val targetPath = nextEnvPath(context.workingDir) + + logger.info("Setting up Conda environment at: $targetPath") + logger.info("Conda version: ${spec.condaVersion}") + logger.info("Python version: ${spec.pythonVersion}") + + val condaExecutable = downloadAndSetupConda(spec.condaVersion) + logger.info("Conda executable: $condaExecutable") + + val fullPythonVersion = spec.pythonVersion.toString() + + logger.info("Creating Conda environment") + val binaryToExec = BinOnEel(condaExecutable) + CondaExecutor.createUnnamedEnv(binaryToExec, targetPath.pathString, fullPythonVersion).getOrThrow() + logger.info("Conda environment created") + + val pythonPath = findPythonInCondaEnv(targetPath) + logger.info("Conda Python path: $pythonPath") + + if (spec.libraries.isNotEmpty()) { + logger.info("Installing libraries: ${spec.libraries.joinToString(", ")}") + installLibraries(targetPath, condaExecutable, spec.libraries) + logger.info("Libraries installed successfully") + } + + logger.info("Conda environment setup completed at: $targetPath") + return CondaPyEnvironment(pythonPath, targetPath, condaExecutable) + } + + /** + * Downloads and sets up Miniconda in the cache directory. + * Directory name includes installer name to handle different OS/architecture variants. + * @return Path to conda executable + */ + @OptIn(ExperimentalPathApi::class) + private suspend fun downloadAndSetupConda(condaVersion: String): Path = withContext(Dispatchers.IO) { + val logger = thisLogger() + val installerName = getMinicondaInstallerName(condaVersion) + val installerBaseName = installerName.substringBeforeLast('.') + val condaDir = PyEnvDownloadCache.cacheDirectory().resolve("conda").resolve(installerBaseName) + val condaExecutable = getCondaExecutablePath(condaDir) + + extractIfNecessary(condaDir, logger) { + val downloadUrl = "$MINICONDA_BASE_URL/$installerName" + logger.info("Downloading Miniconda installer: $installerName") + val cachedInstaller = PyEnvDownloadCache.getOrDownload(downloadUrl, installerName) + logger.info("Miniconda installer downloaded: $cachedInstaller") + + logger.info("Installing Miniconda to: $condaDir") + installMiniconda(logger, cachedInstaller, condaDir) + logger.info("Miniconda installation completed") + + if (!condaExecutable.exists()) { + error("Conda executable not found after installation: $condaExecutable") + } + logger.info("Conda setup completed") + } + + condaExecutable + } + + /** + * Get the platform-specific Miniconda installer name. + */ + private fun getMinicondaInstallerName(version: String): String { + val arch = when { + CpuArch.isArm64() -> "arm64" + CpuArch.isIntel64() -> "x86_64" + else -> "x86" + } + + return when (OS.CURRENT) { + OS.Windows -> "Miniconda3-$version-Windows-$arch.exe" + OS.macOS -> "Miniconda3-$version-MacOSX-$arch.sh" + OS.Linux -> { + // Linux uses aarch64 instead of arm64 + val linuxArch = if (CpuArch.isArm64()) "aarch64" else arch + "Miniconda3-$version-Linux-$linuxArch.sh" + } + else -> throw UnsupportedOperationException("Unsupported OS: ${OS.CURRENT}") + } + } + + /** + * Install Miniconda from the downloaded installer. + */ + private suspend fun installMiniconda(logger: Logger, installerPath: Path, targetDir: Path) = withContext(Dispatchers.IO) { + val command = if (OS.CURRENT == OS.Windows) { + // Windows: Run .exe installer in silent mode + listOf( + installerPath.pathString, + "/S", // Silent install + "/D=${targetDir.pathString}" // Install directory + ) + } + else { + markExecutable(logger, installerPath) + listOf( + "sh", + installerPath.pathString, + "-b", // Batch mode (no prompts) + "-p", targetDir.pathString // Install prefix + ) + } + + executeProcess(command, thisLogger(), "miniconda installer") + } + + /** + * Get the path to conda executable based on installation directory. + */ + private fun getCondaExecutablePath(condaDir: Path): Path { + return if (OS.CURRENT == OS.Windows) { + condaDir.resolve("Scripts").resolve("conda.exe") + } + else { + condaDir.resolve("bin").resolve("conda") + } + } + + /** + * Find Python executable in a Conda environment directory. + */ + private fun findPythonInCondaEnv(envPath: Path): Path { + val pythonPath = if (OS.CURRENT == OS.Windows) { + envPath.resolve("python.exe") + } + else { + envPath.resolve("bin/python") + } + + return if (pythonPath.exists()) pythonPath else error("Failed to find Python executable in created Conda environment") + } + + private suspend fun installLibraries(envPath: Path, condaExecutable: Path, libraries: List) { + val logger = thisLogger() + + if (libraries.isEmpty()) { + logger.info("No libraries to install") + return + } + + val args = mutableListOf("install", "-p", envPath.pathString, "-y") + args.addAll(libraries) + + val command = listOf(condaExecutable.pathString) + args + executeProcess(command, logger, "conda") + } +} + +/** + * Conda Python environment implementation + */ +@ApiStatus.Internal +class CondaPyEnvironment( + override val pythonPath: PythonBinary, + override val envPath: Path, + val condaExecutable: Path, +) : PyEnvironment { + override fun close() { + runBlocking(Dispatchers.IO) { + val args = arrayOf(condaExecutable.toString(), "remove", "-p", envPath.pathString, "--all", "-y") + val exec = Runtime.getRuntime().exec(args) + launch { + try { + exec.awaitExit() + } + catch (e: CancellationException) { + exec.destroyForcibly() + throw e + } + } + exec.getResultStdout().getOrElse { + thisLogger().warn(it) + } + } + } + + override suspend fun prepareSdk(): Sdk { + // Save a path to conda because some legacy code might use it instead of a full conda path from additional data + PyCondaPackageService.onCondaEnvCreated(condaExecutable.pathString) + return PyCondaEnv( + envIdentity = PyCondaEnvIdentity.UnnamedEnv(envPath.pathString, isBase = true), + fullCondaPathOnTarget = condaExecutable.toString(), + ).createSdkFromThisEnv(null, emptyList()) + } +} diff --git a/python/python-test-env/core/BUILD.bazel b/python/python-test-env/core/BUILD.bazel new file mode 100644 index 000000000000..0901e3fc0788 --- /dev/null +++ b/python/python-test-env/core/BUILD.bazel @@ -0,0 +1,75 @@ +### auto-generated section `build intellij.python.test.env.core` start +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +resourcegroup( + name = "core_test_resources", + srcs = glob(["gen/**/*"]), + strip_prefix = "gen" +) + +resourcegroup( + name = "core_test_resources_1", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "core", + visibility = ["//visibility:public"], + srcs = glob([], allow_empty = True), + runtime_deps = [ + "@lib//:jetbrains-annotations", + "//python/openapi:community", + "@lib//:kotlin-stdlib", + "//platform/util", + "//platform/projectModel-api:projectModel", + "@lib//:kotlinx-coroutines-core", + "//platform/analysis-api:analysis", + "//platform/core-api:core", + "//python/testFramework", + "@lib//:jackson-module-kotlin", + "@lib//:jackson-databind", + "@lib//:jackson", + "//platform/lang-impl", + "//platform/lang-core", + ] +) + +jvm_library( + name = "core_test_lib", + module_name = "intellij.python.test.env.core", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [ + ":core_test_resources", + ":core_test_resources_1", + ], + deps = [ + "@lib//:jetbrains-annotations", + "//python/openapi:community", + "//python/openapi:community_test_lib", + "@lib//:kotlin-stdlib", + "//platform/util", + "//platform/projectModel-api:projectModel", + "@lib//:kotlinx-coroutines-core", + "//platform/analysis-api:analysis", + "//platform/core-api:core", + "//python/testFramework", + "@lib//:jackson-module-kotlin", + "@lib//:jackson-databind", + "@lib//:jackson", + "//platform/lang-impl", + "//platform/lang-core", + ], + runtime_deps = [":core"] +) +### auto-generated section `build intellij.python.test.env.core` end + +### auto-generated section `test intellij.python.test.env.core` start +load("@community//build:tests-options.bzl", "jps_test") + +jps_test( + name = "core_test", + runtime_deps = [":core_test_lib"] +) +### auto-generated section `test intellij.python.test.env.core` end \ No newline at end of file diff --git a/python/python-test-env/core/gen/com/intellij/python/test/env/core/python-version-mapping.json b/python/python-test-env/core/gen/com/intellij/python/test/env/core/python-version-mapping.json new file mode 100644 index 000000000000..07845c0a51aa --- /dev/null +++ b/python/python-test-env/core/gen/com/intellij/python/test/env/core/python-version-mapping.json @@ -0,0 +1,5192 @@ +{ + "2.7.18": { + "baseUrl": "https://github.com/qnox/python-2.7/releases/download/v20260109", + "files": { + "darwin-arm64-unknown-install_only": { + "filename": "cpython-2.7.18%2B20260109-arm64-apple-darwin-install_only.tar.gz", + "sha256": "24c48e374e1e3010ef00c11a7945443bd01d2fc7c231dad5a3eb9f546ad415c1", + "size": 25916185 + }, + "darwin-arm64-unknown-install_only_stripped": { + "filename": "cpython-2.7.18%2B20260109-arm64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "c92e1c03b006b551afdaa0ad6c59da76bbff9a1567a0c24102dfcc8f566051b9", + "size": 25674374 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-2.7.18%2B20260109-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "5e9706e6cda0cfcb2c7cf8abb83cf92901692baf7bc197a098616691f7415418", + "size": 25462939 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-2.7.18%2B20260109-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "2dbcd63b4786b9808521f51202f04a23f7537be188f8d21fa7f9629425b9002e", + "size": 25221102 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-2.7.18%2B20260109-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "6c52a93f5347fcc2adea00c180d7d24249f09400b7628203a0dd3cf35062af6f", + "size": 29702135 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-2.7.18%2B20260109-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a6eb506ddc168929f43d560dda921bce59867a893b853ca5ff1cee5c48c2e3ca", + "size": 25456025 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-2.7.18%2B20260109-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "78d355b64852fe95d1207c4208b703ed971db4aa29b65769f954c6f80dc30d95", + "size": 29848510 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-2.7.18%2B20260109-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "fc4dd8ce63a0afc5d9c500b98be04ed7dd74a70d1733b814acc56dce0fa4883a", + "size": 25501129 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-2.7.18%2B20260109-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "0079d54852015e0afe6e22fa534cd9da256785e0c2056d52f740a4c0cfe9bf1c", + "size": 16819705 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-2.7.18%2B20260109-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "b54ff892e27981b9908660c6b4452ff1b5d312514eef2eef043572677d4a2d92", + "size": 16819714 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-2.7.18%2B20260109-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "0eab8de590076f74a17802d8b613ad74ec050b0081ccd0e2c7a47432c9b169b8", + "size": 17504324 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-2.7.18%2B20260109-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "516741092c63d3aac5de0f4c3e6a03ee09ba7c693e2296086046ed67a9d1a492", + "size": 17504333 + } + } + }, + "3.10.11": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20230507", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.11%2B20230507-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "8348bc3c2311f94ec63751fb71bd0108174be1c4def002773cf519ee1506f96f", + "size": 17074991 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.11%2B20230507-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "bd3fc6e4da6f4033ebf19d66704e73b0804c22641ddae10bbe347c48f82374ad", + "size": 17354339 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.11%2B20230507-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "c7573fdb00239f86b22ea0e8e926ca881d24fde5e5890851339911d76110bc35", + "size": 24243246 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.11%2B20230507-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "c5bcaac91bc80bfc29cf510669ecad12d506035ecb3ad85ef213416d54aecd79", + "size": 26689876 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.10.11%2B20230507-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "9c2d3604a06fcd422289df73015cd00e7271d90de28d2c910f0e2309a7f73a68", + "size": 36247696 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.10.11%2B20230507-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "97ebca93a928802f421387dcc6ec5403a3e513f43c2df35b7c3e3cca844d79d0", + "size": 40802006 + } + } + }, + "3.10.12": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20230726", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.12%2B20230726-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "bc66c706ea8c5fc891635fda8f9da971a1a901d41342f6798c20ad0b2a25d1d6", + "size": 16817271 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.12%2B20230726-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "8a6e3ed973a671de468d9c691ed9cb2c3a4858c5defffcf0b08969fba9c1dd04", + "size": 17095895 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.12%2B20230726-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "fee80e221663eca5174bd794cb5047e40d3910dbeadcdf1f09d405a4c1c15fe4", + "size": 23973769 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.12%2B20230726-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "a476dbca9184df9fc69fe6309cda5ebaf031d27ca9e529852437c94ec1bc43d3", + "size": 26421382 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.10.12%2B20230726-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "c1a31c353ca44de7d1b1a3b6c55a823e9c1eed0423d4f9f66e617bdb1b608685", + "size": 36034179 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.10.12%2B20230726-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "2e098bdf1176e879a14c5f50b1bc78468079298cfff264c1cc5149f875ece753", + "size": 40632459 + } + } + }, + "3.10.13": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240224", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.13%2B20240224-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "5fdc0f6a5b5a90fd3c528e8b1da8e3aac931ea8690126c2fdb4254c84a3ff04a", + "size": 17298908 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.13%2B20240224-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "6378dfd22f58bb553ddb02be28304d739cd730c1f95c15c74955c923a1bc3d6a", + "size": 17641238 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.13%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "a898a88705611b372297bb8fe4d23cc16b8603ce5f24494c3a8cfa65d83787f9", + "size": 24901728 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.13%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "d995d032ca702afd2fc3a689c1f84a6c64972ecd82bba76a61d525f08eb0e195", + "size": 27409290 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.10.13%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "086f7fe9156b897bb401273db8359017104168ac36f60f3af4e31ac7acd6634e", + "size": 36226167 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.10.13%2B20240224-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "6f4c3d4dbab1e0c81fd315efad08c8b3327648fb12e19720b2ca107e2a4c102a", + "size": 40783500 + } + } + }, + "3.10.14": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240814", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.14%2B20240814-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "c9fbd43c47ebc15f3b326d518e3f4df2fed98db226cd16f97ecbcc496c099d5d", + "size": 17197934 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.10.14%2B20240814-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "f7ca9bffbce433c8d445edd33a5424c405553d735efee65a2fc5d8bbb1c8e137", + "size": 17070246 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.14%2B20240814-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "9e3df0cd6624f7943e1c4b72527356e1ad6edb59b3cf36562e0d380eb3e2b424", + "size": 17529670 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.10.14%2B20240814-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "4404f44ec69c0708d4d88e98f39c2c1fe3bd462dc6a958b60aaf63028550c485", + "size": 17409127 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.14%2B20240814-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "232921e70965f78dc32e2ed4d15f97b45d24ac68dbc5d6f4ff402f81bad11eda", + "size": 24774698 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.10.14%2B20240814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0ffe64c77cacda7e3afcb0d8ba271c59ca0a30dfda218da39a573b412bb4afd7", + "size": 19394162 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.14%2B20240814-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "88dee5e48def031cdaebdedeae13865f086b532161315dda7f39c1428dfef64f", + "size": 27449446 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.10.14%2B20240814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "159c456bb4a3802bafbce065ff54b99ddb16422500d75c1315573ee3b673af17", + "size": 20510166 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.10.14%2B20240814-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "e5982ec53bed31a3f9c1f063cb5fb031089ae436a8541cd471fe660836dddbbb", + "size": 35898857 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.10.14%2B20240814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "61ad1abcaca639eecb5bd0b129ac0315d79f7b90cf0aca8e9fb85c9e7269c26b", + "size": 22021047 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.10.14%2B20240814-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "e5982ec53bed31a3f9c1f063cb5fb031089ae436a8541cd471fe660836dddbbb", + "size": 35898857 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.10.14%2B20240814-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "61ad1abcaca639eecb5bd0b129ac0315d79f7b90cf0aca8e9fb85c9e7269c26b", + "size": 22021047 + } + } + }, + "3.10.15": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20241016", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.15%2B20241016-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "f64776f455a44c24d50f947c813738cfb7b9ac43732c44891bc831fa7940a33c", + "size": 17201517 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.10.15%2B20241016-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "fa79bd909bfeb627ffe66a8b023153495ece659e5e3b2ff56268535024db851c", + "size": 17068674 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.15%2B20241016-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "90b46dfb1abd98d45663c7a2a8c45d3047a59391d8586d71b459cec7b75f662b", + "size": 17531054 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.10.15%2B20241016-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "0d952fa2342794523ea7beee6a58e79e62045d0f018314ce282e9f2f1427ee2c", + "size": 17416153 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.15%2B20241016-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "eb58581f85fde83d1f3e8e1f8c6f5a15c7ae4fdbe3b1d1083931f9167fdd8dbc", + "size": 24780433 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.10.15%2B20241016-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "6008b42df79a0c8a4efe3aa88c2aea1471116aa66881a8ed15f04d66438cb7f5", + "size": 19395962 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.15%2B20241016-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "3db2171e03c1a7acdc599fba583c1b92306d3788b375c9323077367af1e9d9de", + "size": 27454538 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.10.15%2B20241016-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "25fb8e23cd3b82b748075a04fd18f3183cc7316c11d6f59eb4b0326843892600", + "size": 20518101 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.10.15%2B20241016-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "e48952619796c66ec9719867b87be97edca791c2ef7fbf87d42c417c3331609e", + "size": 35916053 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.10.15%2B20241016-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "45a95225c659f9b988f444d985df347140ecc71c0297c6857febf5ef440d689a", + "size": 22026683 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.10.15%2B20241016-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "e48952619796c66ec9719867b87be97edca791c2ef7fbf87d42c417c3331609e", + "size": 35916053 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.10.15%2B20241016-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "45a95225c659f9b988f444d985df347140ecc71c0297c6857febf5ef440d689a", + "size": 22026683 + } + } + }, + "3.10.16": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250317", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.16%2B20250317-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "e99f8457d9c79592c036489c5cfa78df76e4762d170665e499833e045d82608f", + "size": 17496258 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.10.16%2B20250317-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "2d3f07542882f08534e9d4e533a4c73d1707ee355cf7b2a204d4b8d6f8fd8f8e", + "size": 17451258 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.16%2B20250317-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "e03e62dbe95afa2f56b7344ff3bd061b180a0b690ff77f9a1d7e6601935e05ca", + "size": 17734640 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.10.16%2B20250317-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "ecd1c0e2d74f27128251f2c5f41cb107d4efd93cc7aecf76a585d75244ac4f07", + "size": 17695432 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.16%2B20250317-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "76d0f04d2444e77200fdc70d1c57480e29cca78cb7420d713bc1c523709c198d", + "size": 25036916 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.10.16%2B20250317-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "5dcd0486c403d07e27a1f2c256810122ac5d153f921433d77116df5c7d09b4c9", + "size": 19650207 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.16%2B20250317-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "b350c7e63956ca8edb856b91316328e0fd003a840cbd63d08253af43b2c63643", + "size": 27774189 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.10.16%2B20250317-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e8d7ed8c6f8c6f85cd083d5051cafd8c6c01d09eca340d1da74d0c00ff1cb897", + "size": 20769814 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.10.16%2B20250317-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "c7e0eb0ff5b36758b7a8cacd42eb223c056b9c4d36eded9bf5b9fe0c0b9aeb08", + "size": 39040468 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.10.16%2B20250317-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "685f24e15d926a45097e805982dc52a279985f952515b9c95604ec881b40b51e", + "size": 22438969 + } + } + }, + "3.10.17": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250529", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.17%2B20250529-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "4811ea0a7c192cb11745de29025a64efbae801c4f84a74c31fc2e8d3fdd69c07", + "size": 17498765 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.10.17%2B20250529-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "366d037181b1cea0a7a8b1457874a0cdfbb795815d07ae25c55ef1461aa487ef", + "size": 17452158 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.17%2B20250529-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "22f43d26ec2bbf1a2bc21d80710031aa30fbadef6ec5b2b6fbde3375bde1fea8", + "size": 17739229 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.10.17%2B20250529-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "cb45a45bcbdf00a4808f48fbf344f597a01e66c5ed83a7e388883c86844bd2f1", + "size": 17696145 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.17%2B20250529-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "3021c4306e90eb30bce8868bb87ff4fdce73d442e4bb4b799532368572e56f2a", + "size": 38787857 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.10.17%2B20250529-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "aaf1fd370ab3ae31807c8d7d5aff0b8d2abb370c12d17a561178ceb842314f2a", + "size": 28081550 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.17%2B20250529-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "6634b2d2504fa07ec62655dc8a0266c2705a3920a1e08519e5c6730a79f8a2c1", + "size": 44152102 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.10.17%2B20250529-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e3abc6e300ccdfe5e8faf220d0682dc8eae4d438b96b7d312b32d50a4e536d21", + "size": 30240751 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.10.17%2B20250529-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "4d2eb31500ec365520a899a040172c678b4ae9cfbbcb71075bc095a151809d10", + "size": 39059929 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.10.17%2B20250529-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "775558a9ad71d42afb168ff76f6db6fa4bf512e933b888e55c8f0557860eac83", + "size": 22446301 + } + } + }, + "3.10.18": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251007", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.18%2B20251007-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "a8e406f87baeb122149212267061941f84b7279c127f1a4c2c8972166b157491", + "size": 18437231 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.10.18%2B20251007-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "897ea4858f462ff6cc574e9e32d36e38e77b354d758ecb96e540a5732439c7b5", + "size": 18391459 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.18%2B20251007-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "d4c8a43048ca557e4c956a6372922ae30af54cfad142ebb1f8b5a0d3d841fcae", + "size": 18263751 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.10.18%2B20251007-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "4fc2d788345c5786ec79d478def38073871502e88746e31d31211fe0c0d282a6", + "size": 18223923 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.18%2B20251007-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "5503fc72bcb8590a2822fc75deb9a1fd165f3f4c8f2584b56fb6fbaf2dbb0296", + "size": 43147375 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.10.18%2B20251007-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "d8653909f2446651aacf24b0ea2b05dbcb2a3f4c49284efc5a3ab750f7c6181e", + "size": 29404675 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.18%2B20251007-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "033fab7c275e2f3c2cfaac5971c3f8e1ac318832661d651e125b0ffb0ee6f950", + "size": 42677960 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.10.18%2B20251007-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7b1d02e28b0d36c4b0de044aaf8099cb0395ac3d6826c96ddd158241fcdc6f06", + "size": 28836654 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.10.18%2B20251007-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "aedc79c0472c7c1f25b546bc84821616d607a8ab875f0bb7a42abbbb81b7b778", + "size": 39529470 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.10.18%2B20251007-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "9404193fe6c3d22f6065d6a2700df103b307b27012e85504ca032f3cc0e244ed", + "size": 22415221 + } + } + }, + "3.10.19": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251217", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.19%2B20251217-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "e8a86c0b91ac8e053dfb5b4a9a8dd6323eef4c185c98ae75ff3518e15958a5ed", + "size": 18866297 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.10.19%2B20251217-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "13e1ae7be3763e3f24a515053b80a2f38a6291748a33e5bce959ddd48dc7d37c", + "size": 18817686 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.19%2B20251217-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "919d5351c41209aa89237c95a165457bd1264cd4ee1033f1fe71d1d4d252ffde", + "size": 18756605 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.10.19%2B20251217-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "ae06d10d077aeff65c1b01baffef8023ada32b2f58ef6378ba4a341dbf92653a", + "size": 18716346 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.19%2B20251217-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "d14ff02a7a1abd9bb132f2f1b42a72678506e6d98ca43cba69ba4bbbeb3d5f9d", + "size": 43383878 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.10.19%2B20251217-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "950d4453e6db4c020b83d49f1092c6267b2b43cd96d2edc8ce6935b3c2324f7c", + "size": 29538578 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.19%2B20251217-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "b501791dd2a2752f3f35329c4af736983cc5ef5e12e50776c13c752cbfa9cbf8", + "size": 43215542 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.10.19%2B20251217-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "41ba93eaf91208dcdcdf3eba230e67031c5c9735aa0f6eca7f522efac4d082a6", + "size": 29189524 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.10.19%2B20251217-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "dce20bb2b5c2dadf4abe5dbd2e9d8c1b9249130b4a090f7b3407204ed89c4729", + "size": 39891469 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.10.19%2B20251217-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "bb7547a32b55d0b773f0b5da488c5895f5742e9792a851ffea7a1e6c8766da6e", + "size": 22530448 + } + } + }, + "3.10.2": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20220227", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.2%2B20220227-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "1409acd9a506e2d1d3b65c1488db4e40d8f19d09a7df099667c87a506f71c0ef", + "size": 42503895 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.2%2B20220227-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "8146ad4390710ec69b316a5649912df0247d35f4a42e2aa9615bffd87b3e235a", + "size": 42785626 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.2%2B20220227-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "8f351a8cc348bb45c0f95b8634c8345ec6e749e483384188ad865b7428342703", + "size": 88747017 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.2%2B20220227-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "9b64eca2a94f7aff9409ad70bdaa7fbbf8148692662e764401883957943620dd", + "size": 53642545 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.10.2%2B20220227-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "a1d9a594cd3103baa24937ad9150c1a389544b4350e859200b3e5c036ac352bd", + "size": 40008220 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.10.2%2B20220227-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "a293c5838dd9c8438a84372fb95dda9752df63928a8a2ae516438f187f89567d", + "size": 44390551 + } + } + }, + "3.10.3": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20220318", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.3%2B20220318-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "db46dadfccc407aa1f66ed607eefbf12f781e343adcb1edee0a3883d081292ce", + "size": 43030480 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.3%2B20220318-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "ec2e90b6a589db7ef9f74358b1436558167629f9e4d725c8150496f9cb08a9d4", + "size": 43289051 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.3%2B20220318-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "f52ee68c13c4f9356eb78a5305d3178af2cb90c38a8ce8ce9990a7cf6ff06144", + "size": 89386559 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.3%2B20220318-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "b9989411bed71ba4867538c991f20b55f549dd9131905733f0df9f3fde81ad1d", + "size": 54110057 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.10.3%2B20220318-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "ba593370742ed8a7bc70ce563dd6a53e30ece1f6881e3888d334c1b485b0d9d0", + "size": 40650322 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.10.3%2B20220318-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "3c3e6212fc983640bbe85b9cc60514f80d885892e072d43017b73e1b50a7ad02", + "size": 45047761 + } + } + }, + "3.10.4": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20220528", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.4%2B20220528-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "6d2e4e6b1c403bce84cfb846400754017f525fe8017f186e8e7072fcaaf3aa71", + "size": 42984466 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.4%2B20220528-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "c4a57a13b084d49ce8c2eb5b2662ee45b0c55b08ddd696f473233b0787f03988", + "size": 43233147 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.4%2B20220528-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "7a8989392dc9b41d85959a752448c60852cf0061de565e98445c27f6bbdf63be", + "size": 89292900 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.4%2B20220528-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "1f8423808ad84c0e56c8e14c32685cbfbc1159e0d9f943ac946f29e84cf1b5ee", + "size": 54186539 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.10.4%2B20220528-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "7231ba2af9525cae620a5f4ae3bf89a939fdc053ba0cc64ee3dead8f13188005", + "size": 41002870 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.10.4%2B20220528-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "c8c8497409148f4976fed8e666c951282dbc83ffa1789e1e59fd9ad77e04cd04", + "size": 45399718 + } + } + }, + "3.10.5": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20220630", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.5%2B20220630-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "19d1aa4a6d9ddb0094fc36961b129de9abe1673bce66c86cd97b582795c496a8", + "size": 43000441 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.5%2B20220630-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "eca0584397d9a3ef6f7bb32b0476318b01c89b7b0a031ef97a0dcaa5ba5127a8", + "size": 43257698 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.5%2B20220630-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "012fa37c12d2647d76d004dc003302563864d2f1cd0731b71eeafad63d28b3f0", + "size": 89356775 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.5%2B20220630-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "460f87a389be28c953c24c6f942f172f9ce7f331367b4daf89cb450baedd51d7", + "size": 54207014 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.10.5%2B20220630-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "c830ab2a3a488f9cf95e4e81c581d9ef73e483c2e6546136379443e9bb725119", + "size": 41018615 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.10.5%2B20220630-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "3e6588d51379ad4903f49b2dbd0056d0e2569cf2eefeb3f13df493a2cf9f2e6b", + "size": 45413238 + } + } + }, + "3.10.6": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20220802", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.6%2B20220802-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "efaf66acdb9a4eb33d57702607d2e667b1a319d58c167a43c96896b97419b8b7", + "size": 17138410 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.6%2B20220802-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "7718411adf3ea1480f3f018a643eb0550282aefe39e5ecb3f363a4a566a9398c", + "size": 17544359 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.6%2B20220802-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "81625f5c97f61e2e3d7e9f62c484b1aa5311f21bd6545451714b949a29da5435", + "size": 24921808 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.6%2B20220802-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "55aa2190d28dcfdf414d96dc5dcea9fe048fadcd583dc3981fec020869826111", + "size": 27536682 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.10.6%2B20220802-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "91889a7dbdceea585ff4d3b7856a6bb8f8a4eca83a0ff52a73542c2e67220eaa", + "size": 36526347 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.10.6%2B20220802-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "fea6a68dfbd90b1cd5828cbd896a9305cd7c70a6ea1ec524dcd39ae4e32c1a56", + "size": 41023002 + } + } + }, + "3.10.7": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20221002", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.7%2B20221002-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "70f6ca1da8e6fce832ad0b7f9fdaba0b84ba0ac0a4c626127acb6d49df4b8f91", + "size": 17142891 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.7%2B20221002-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "6101f580434544d28d5590543029a7c6bdf07efa4bcdb5e4cbedb3cd83241922", + "size": 17554388 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.7%2B20221002-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "dfeec186a62a6068259d90e8d77e7d30eaf9c2b4ae7b205ff8caab7cb21f277c", + "size": 24938179 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.7%2B20221002-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "c12c9ad2b2c75464541d897c0528013adecd8be5b30acf4411f7759729841711", + "size": 27546579 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.10.7%2B20221002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "b464352f8cbf06ab4c041b7559c9bda7e9f6001a94f67ab0a342cba078f3805f", + "size": 36543530 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.10.7%2B20221002-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "d1257c7df79b24c669f21d6da81038b8cafddba24e3760bf6ea584e1088be766", + "size": 41083093 + } + } + }, + "3.10.8": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20221106", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.8%2B20221106-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "d52b03817bd245d28e0a8b2f715716cd0fcd112820ccff745636932c76afa20a", + "size": 17150191 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.8%2B20221106-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "525b79c7ce5de90ab66bd07b0ac1008bafa147ddc8a41bef15ffb7c9c1e9e7c5", + "size": 17560650 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.8%2B20221106-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "33170bef18c811906b738be530f934640491b065bf16c4d276c6515321918132", + "size": 24947106 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.8%2B20221106-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "6c8db44ae0e18e320320bbaaafd2d69cde8bfea171ae2d651b7993d1396260b7", + "size": 27555856 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.10.8%2B20221106-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "f2b6d2f77118f06dd2ca04dae1175e44aaa5077a5ed8ddc63333c15347182bfe", + "size": 36525648 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.10.8%2B20221106-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "305bf6cc808e4510300924b769619577448f8280dd882cff9792af375708fd77", + "size": 41074672 + } + } + }, + "3.10.9": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20230116", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.10.9%2B20230116-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "018d05a779b2de7a476f3b3ff2d10f503d69d14efcedd0774e6dab8c22ef84ff", + "size": 17509509 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.10.9%2B20230116-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "0e685f98dce0e5bc8da93c7081f4e6c10219792e223e4b5886730fd73a7ba4c6", + "size": 17474702 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.10.9%2B20230116-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "2003750f40cd09d4bf7a850342613992f8d9454f03b3c067989911fb37e7a4d1", + "size": 24505626 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.10.9%2B20230116-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "d196347aeb701a53fe2bb2b095abec38d27d0fa0443f8a1c2023a1bed6e18cdf", + "size": 27081922 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.10.9%2B20230116-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "59c6970cecb357dc1d8554bd0540eb81ee7f6d16a07acf3d14ed294ece02c035", + "size": 36521148 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.10.9%2B20230116-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "86f18f44cf407c24ad34f63c902e3b20764de321aa5f0a19ddffc179b8af9635", + "size": 41066422 + } + } + }, + "3.11.1": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20230116", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.11.1%2B20230116-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "4918cdf1cab742a90f85318f88b8122aeaa2d04705803c7b6e78e81a3dd40f80", + "size": 18024309 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.11.1%2B20230116-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "20a4203d069dc9b710f70b09e7da2ce6f473d6b1110f9535fb6f4c469ed54733", + "size": 18042424 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.11.1%2B20230116-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "debf15783bdcb5530504f533d33fda75a7b905cec5361ae8f33da5ba6599f8b4", + "size": 25579178 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.11.1%2B20230116-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "02a551fefab3750effd0e156c25446547c238688a32fabde2995c941c03a6423", + "size": 29321007 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.11.1%2B20230116-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "edc08979cb0666a597466176511529c049a6f0bba8adf70df441708f766de5bf", + "size": 39657709 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.11.1%2B20230116-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "9ee05b99500614f5cd2f975bc9fab84f53435beaaad7f09df8771276ebe94744", + "size": 44741429 + } + } + }, + "3.11.10": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20241016", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.11.10%2B20241016-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "5a69382da99c4620690643517ca1f1f53772331b347e75f536088c42a4cf6620", + "size": 17793306 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.11.10%2B20241016-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "a5a224138a526acecfd17210953d76a28487968a767204902e2bde809bb0e759", + "size": 17629090 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.11.10%2B20241016-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "1e23ffe5bc473e1323ab8f51464da62d77399afb423babf67f8e13c82b69c674", + "size": 18165899 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.11.10%2B20241016-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "575b49a7aa64e97b06de605b7e947033bf2310b5bc5f9aedb9859d4745033d91", + "size": 18011078 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.11.10%2B20241016-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "803e49259280af0f5466d32829cd9d65a302b0226e424b3f0b261f9daf6aee8f", + "size": 25925656 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.11.10%2B20241016-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9d124604ffdea4fbaabb10b343c5a36b636a3e7b94dfc1cccd4531f33fceae5e", + "size": 19963074 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.11.10%2B20241016-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "8b50a442b04724a24c1eebb65a36a0c0e833d35374dbdf9c9470d8a97b164cd9", + "size": 29718798 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.11.10%2B20241016-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "03f15e19e2452641b6375b59ba094ff6cf2fc118315d24a6ca63ce60e4d4a6e0", + "size": 21205300 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.11.10%2B20241016-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "647b66ff4552e70aec3bf634dd470891b4a2b291e8e8715b3bdb162f577d4c55", + "size": 40957796 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.11.10%2B20241016-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "ea770ebabc620ff46f1d0f905c774a9b8aa5834620e89617ad5e01f90d36b3ee", + "size": 25075645 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.11.10%2B20241016-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "647b66ff4552e70aec3bf634dd470891b4a2b291e8e8715b3bdb162f577d4c55", + "size": 40957796 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.11.10%2B20241016-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "ea770ebabc620ff46f1d0f905c774a9b8aa5834620e89617ad5e01f90d36b3ee", + "size": 25075645 + } + } + }, + "3.11.11": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250317", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.11.11%2B20250317-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "19b147c7e4b742656da4cb6ba35bc3ea2f15aa5f4d1bbbc38d09e2e85551e927", + "size": 18079842 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.11.11%2B20250317-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "f8df308b918750a466bf585b14befd4349e69d5135b83090254e6f2ee65b1bf1", + "size": 18018813 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.11.11%2B20250317-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "a870cd965e7dded5100d13b1d34cab1c32a92811e000d10fbfe9bbdb36cdaa0e", + "size": 18392447 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.11.11%2B20250317-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "7b7b393c53c8371509087a60d858ee2567bc439bda13c81564647f79f3905224", + "size": 18337876 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.11.11%2B20250317-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "7d52b5206afe617de2899af477f5a1d275ecbce80fb8300301b254ebf1da5a90", + "size": 26200898 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.11.11%2B20250317-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3a81577e9b1cddf3d91796d7c22657aadccada8f78fb9e3e64ce8d2bbb95c73a", + "size": 20231270 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.11.11%2B20250317-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "51e47bc0d1b9f4bf68dd395f7a39f60c58a87cde854cab47264a859eb666bb69", + "size": 30030843 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.11.11%2B20250317-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b0736ee681f88bf487687b5248ca9fb15fe5f7d584f7d1ed2bc9c9578300b097", + "size": 21468148 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.11.11%2B20250317-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "1cf5760eea0a9df3308ca2c4111b5cc18fd638b2a912dbe07606193e3f9aa123", + "size": 44945295 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.11.11%2B20250317-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "4a9409fedb95312eabbe2285c836f688742a0ea4e6ccab7f0d68cb0e0cbb2fe4", + "size": 25525175 + } + } + }, + "3.11.12": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250529", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.11.12%2B20250529-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "77d16e24444fa12096818064fcc3c12b041b0746f4481e3652fc60ee027a7fb5", + "size": 18080683 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.11.12%2B20250529-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "9279f496a7f16fa4da822337819ead9fafd68f1c16e15cef26bf32ed57f24e0b", + "size": 18019494 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.11.12%2B20250529-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "1fc7ee75b37a309443d5a214b83733cfda5ae7597559fb39ab8906f09c997c93", + "size": 18398845 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.11.12%2B20250529-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "6f944e7797f0fca3859db19a6344f01527616eadb088b5b4c3974682f144f9db", + "size": 18344687 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.11.12%2B20250529-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "099085938a916e36c830ad2d74c78d760757d7f3218906394d49fff20c34b4d0", + "size": 40964674 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.11.12%2B20250529-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ae5bed638f9116faf1b488b9058e478835538f3a748b50fa2919e832fb3dda28", + "size": 29091524 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.11.12%2B20250529-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "0c05bfc1e1f0f0b084af9e8b19f778cad6b431201a3169587291a6c19264eb81", + "size": 48597594 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.11.12%2B20250529-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a8c2320f1df72d02d58f3f7ab0899bff7946e5efcc723ae2ff3113ad8bb645c0", + "size": 31555161 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.11.12%2B20250529-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "3258b902130179f72a3086ad87deccfa2f111faff54de444535d7b72d99f2b20", + "size": 44883333 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.11.12%2B20250529-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "f794b1e74349a989d1b9a466d2207527d4e16a36c92a1277802458293bc4be31", + "size": 25527767 + } + } + }, + "3.11.13": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251007", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.11.13%2B20251007-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "33aba1fa339241457329b7bf99fe63b1a6856dadf0705c1c786182cbcee91b01", + "size": 19012005 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.11.13%2B20251007-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "78bc6defdc1dac5bf6765c8f938e6849383dbed831ea1e2d11576a4683fb1e8c", + "size": 18949778 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.11.13%2B20251007-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "82e3529dcd2b9bdc50bfaf7fe620fb23ccd1855c883246c40c3542ae6453ad78", + "size": 18918115 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.11.13%2B20251007-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "af2e93876eb643e71df9ca9210d23ff30127ef01f3213aab7d7b326c17df7e12", + "size": 18862316 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.11.13%2B20251007-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "9e399423248aeae4f4355ea3f5f6d75585bcdd4bee7254c0d29e3db91eb98580", + "size": 47493555 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.11.13%2B20251007-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "5b08be545d415a562caeab61255e5583935b33caf21c6eafabb4ea7db87239b2", + "size": 30641841 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.11.13%2B20251007-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "c102eddacd398aeebb7c17664cf47c46c1b82402a5a63f1503c454b7d3d3471c", + "size": 47161632 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.11.13%2B20251007-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "43bfc42529843ecd1d9c08c4a239ede348f96ff0acaef2ec24b28dc059f4f0c3", + "size": 30157215 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.11.13%2B20251007-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "672168d9e767010985a006907cfce5b90e3b87e67aae39e3ff520ed951695c96", + "size": 45350228 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.11.13%2B20251007-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "b8504192706a89e51f9cb7f52a368c8a1b4c7cfa991abf0a01f7ddbec301d5c9", + "size": 24776272 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.11.13%2B20251007-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "716b6c2a7fc0dbf92f2c417435d1a119d6d0b577125096d8d204a24aa181c89a", + "size": 48205603 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.11.13%2B20251007-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "cde5153f59a67d9e108f2ed964526e9aed100eba180f54bee0496b4fd73a8b29", + "size": 25990147 + } + } + }, + "3.11.14": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251217", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.11.14%2B20251217-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "de2a1ceb6917d7350e95c67b2adc07ba209252172ca91f1151777710dad660ed", + "size": 19451555 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.11.14%2B20251217-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "a5a4bb1dc3c1beff4a9c07fd35e07e031498b71a5a63d19de383d445d43181c6", + "size": 19383842 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.11.14%2B20251217-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "7a461ba811ca0d337343f7343d2f4f94c067f47ce026410ee5d20202d74e2f00", + "size": 19417248 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.11.14%2B20251217-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "fbb5162daf2ed112dd4c3267cc2e0951eb1cc0346c877df17a046870ebd70498", + "size": 19355981 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.11.14%2B20251217-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "1aa263c0d76e37058c0d1ef92d4ed184cbf97106f149bf4efd354084c190f0d2", + "size": 47801542 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.11.14%2B20251217-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "63dee58dfe74e8841d0c4eb83a87d89ff3a926cfc3af2186b7d8889ffb79dfb7", + "size": 30793573 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.11.14%2B20251217-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "b562a6168df10500120b195119b12df8eef7c34d14fa76199487d06397f25359", + "size": 47731088 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.11.14%2B20251217-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "49e99461d9c4ea3ee80ff0e5d00afa197f9d4c00ebf5fab51e70e507f330003a", + "size": 30507253 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.11.14%2B20251217-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "cbed6b750bd8af23269f08f00f88e1e4e8dc95fc59a033482851b8559fbfeb5f", + "size": 45511025 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.11.14%2B20251217-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "87784b367fe29d8250848e717a77d0571fbf9c2f405d21f26fbd80f1b78d0c53", + "size": 24757136 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.11.14%2B20251217-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "ae3ca2c560aad0b8eeae3d29db5e82f583b168101ba69d90ef4a081f5e1a4801", + "size": 48726831 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.11.14%2B20251217-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "4ca2caa73f20bbc3fcd9645de4c8e43aac5119760e446920b41e2f5637a03869", + "size": 26023591 + } + } + }, + "3.11.3": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20230507", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.11.3%2B20230507-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "09e412506a8d63edbb6901742b54da9aa7faf120b8dbdce56c57b303fc892c86", + "size": 17585902 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.11.3%2B20230507-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "f710b8d60621308149c100d5175fec39274ed0b9c99645484fd93d1716ef4310", + "size": 17912223 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.11.3%2B20230507-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "8190accbbbbcf7620f1ff6d668e4dd090c639665d11188ce864b62554d40e5ab", + "size": 25301609 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.11.3%2B20230507-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "da50b87d1ec42b3cb577dfd22a3655e43a53150f4f98a4bfb40757c9d7839ab5", + "size": 28906807 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.11.3%2B20230507-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "24741066da6f35a7ff67bee65ce82eae870d84e1181843e64a7076d1571e95af", + "size": 39289141 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.11.3%2B20230507-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "42998e05f97d1ac10b52b0880ddad649c4ca92510555aa8538c97a2dce3fa76a", + "size": 44354263 + } + } + }, + "3.11.4": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20230726", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.11.4%2B20230726-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "cb6d2948384a857321f2aa40fa67744cd9676a330f08b6dad7070bda0b6120a4", + "size": 17343022 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.11.4%2B20230726-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "47e1557d93a42585972772e82661047ca5f608293158acb2778dccf120eabb00", + "size": 17677396 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.11.4%2B20230726-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "2e84fc53f4e90e11963281c5c871f593abcb24fc796a50337fa516be99af02fb", + "size": 25061612 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.11.4%2B20230726-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "e26247302bc8e9083a43ce9e8dd94905b40d464745b1603041f7bc9a93c65d05", + "size": 28658858 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.11.4%2B20230726-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "878614c03ea38538ae2f758e36c85d2c0eb1eaaca86cd400ff8c76693ee0b3e1", + "size": 39110038 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.11.4%2B20230726-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "b24a934e045a1c670cbf8111afb9c79a0d5cb2cd3c651958f7140ff37607f109", + "size": 44236483 + } + } + }, + "3.11.5": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20230826", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.11.5%2B20230826-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "dab64b3580118ad2073babd7c29fd2053b616479df5c107d31fe2af1f45e948b", + "size": 17379941 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.11.5%2B20230826-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "4a4efa7378c72f1dd8ebcce1afb99b24c01b07023aa6b8fea50eaedb50bf2bfc", + "size": 17702502 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.11.5%2B20230826-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "bb5c5d1ea0f199fe2d3f0996fff4b48ca6ddc415a3dbd98f50bff7fce48aac80", + "size": 25085307 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.11.5%2B20230826-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "fbed6f7694b2faae5d7c401a856219c945397f772eea5ca50c6eb825cbc9d1e1", + "size": 28705307 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.11.5%2B20230826-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "00f002263efc8aea896bcfaaf906b1f4dab3e5cd3db53e2b69ab9a10ba220b97", + "size": 39162835 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.11.5%2B20230826-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "378b09b039a1c8c7d0a7dfdc32eacf77b6bc14ae916b13729229298cff478984", + "size": 44270987 + } + } + }, + "3.11.6": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20231002", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.11.6%2B20231002-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "916c35125b5d8323a21526d7a9154ca626453f63d0878e95b9f613a95006c990", + "size": 17456917 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.11.6%2B20231002-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "178cb1716c2abc25cb56ae915096c1a083e60abeba57af001996e8bc6ce1a371", + "size": 18090352 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.11.6%2B20231002-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "3e26a672df17708c4dc928475a5974c3fb3a34a9b45c65fb4bd1e50504cc84ec", + "size": 25547472 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.11.6%2B20231002-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "ee37a7eae6e80148c7e3abc56e48a397c1664f044920463ad0df0fc706eacea8", + "size": 29257565 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.11.6%2B20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "3933545e6d41462dd6a47e44133ea40995bc6efeed8c2e4cbdf1a699303e95ea", + "size": 40902210 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.11.6%2B20231002-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "35458ef3163a2705cd0952ba1df6012acb42b043349dcb31ab49afec341369cf", + "size": 45827424 + } + } + }, + "3.11.7": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240107", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.11.7%2B20240107-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "b042c966920cf8465385ca3522986b12d745151a72c060991088977ca36d3883", + "size": 17492365 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.11.7%2B20240107-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "a0e615eef1fafdc742da0008425a9030b7ea68a4ae4e73ac557ef27b112836d4", + "size": 18136686 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.11.7%2B20240107-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "b102eaf865eb715aa98a8a2ef19037b6cc3ae7dfd4a632802650f29de635aa13", + "size": 25593238 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.11.7%2B20240107-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "4a51ce60007a6facf64e5495f4cf322e311ba9f39a8cd3f3e4c026eae488e140", + "size": 29335084 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.11.7%2B20240107-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "67077e6fa918e4f4fd60ba169820b00be7c390c497bf9bc9cab2c255ea8e6f3e", + "size": 41193988 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.11.7%2B20240107-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "36a8584ef16277f84a7fea81fca7fb941a8b8fa1defee28100afb39e90385926", + "size": 46208113 + } + } + }, + "3.11.8": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240224", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.11.8%2B20240224-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "389a51139f5abe071a0d70091ca5df3e7a3dfcfcbe3e0ba6ad85fb4c5638421e", + "size": 17908564 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.11.8%2B20240224-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "097f467b0c36706bfec13f199a2eaf924e668f70c6e2bd1f1366806962f7e86e", + "size": 18294281 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.11.8%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "389b9005fb78dd5a6f68df5ea45ab7b30d9a4b3222af96999e94fd20d4ad0c6a", + "size": 26053684 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.11.8%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "94e13d0e5ad417035b80580f3e893a72e094b0900d5d64e7e34ab08e95439987", + "size": 29798726 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.11.8%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "b618f1f047349770ee1ef11d1b05899840abd53884b820fd25c7dfe2ec1664d4", + "size": 41311672 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.11.8%2B20240224-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "e95edb8880f17b5132fa699019e4c7d5bec03f59096bf00d546e72774607073a", + "size": 46353321 + } + } + }, + "3.11.9": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240814", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.11.9%2B20240814-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "8760e908f25fdc8a01f4d1b101854ac047b4eacb723fb2593a168fb989c86eef", + "size": 17789907 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.11.9%2B20240814-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "c4e2f7774421bcb381245945e132419b529399dfa4a56059acda1493751fa377", + "size": 17618881 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.11.9%2B20240814-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "76073305812c093ce840df9c4c17068aa69da8d951e7376ef48f43376986a13e", + "size": 18162034 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.11.9%2B20240814-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "c8680f90137e36b54b3631271ccdfe5de363e7d563d8df87c53e11b956a00e04", + "size": 18009651 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.11.9%2B20240814-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "c74a3313e081dda8360d1824baa4eae5303333555bf8440ec2de521731e39165", + "size": 25922353 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.11.9%2B20240814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "364cf099524fff92c31b8ff5ae3f7b32b0fa6cf1d380c6e37cf56140d08dfc87", + "size": 19962482 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.11.9%2B20240814-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "9a332ba354f3b4e8a96a15db6b2805a7a31dcc1b6b9c1b7b93e5246949fbb50f", + "size": 29712505 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.11.9%2B20240814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "daa487c7e73005c4426ac393273117cf0e2dc4ab9b2eeda366e04cd00eea00c9", + "size": 21200862 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.11.9%2B20240814-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "4c71d25731214b8a960d1d87510f24179d819249c5b434aaf7135818421b6215", + "size": 40957587 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.11.9%2B20240814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "8ac54a8d711ef0d49b62a2c3521c2d0403f1b221dc9d84c5f85fe48903e82523", + "size": 25072044 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.11.9%2B20240814-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "4c71d25731214b8a960d1d87510f24179d819249c5b434aaf7135818421b6215", + "size": 40957587 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.11.9%2B20240814-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "8ac54a8d711ef0d49b62a2c3521c2d0403f1b221dc9d84c5f85fe48903e82523", + "size": 25072044 + } + } + }, + "3.12.0": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20231002", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.12.0%2B20231002-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "4734a2be2becb813830112c780c9879ac3aff111a0b0cd590e65ec7465774d02", + "size": 16380983 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.12.0%2B20231002-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "5a9e88c8aa52b609d556777b52ebde464ae4b4f77e4aac4eb693af57395c9abf", + "size": 16985354 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.12.0%2B20231002-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "bccfe67cf5465a3dfb0336f053966e2613a9bc85a6588c2fcf1366ef930c4f88", + "size": 25109984 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.12.0%2B20231002-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "e51a5293f214053ddb4645b2c9f84542e2ef86870b8655704367bd4b29d39fe9", + "size": 67780633 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.12.0%2B20231002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "facfaa1fbc8653f95057f3c4a0f8aa833dab0e0b316e24ee8686bc761d4b4f8d", + "size": 40728525 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.12.0%2B20231002-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "5b0dbdc1c1dcc0103367b4c7298da421bf22aacac420c01d03c8da200c036ffb", + "size": 46015738 + } + } + }, + "3.12.1": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240107", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.12.1%2B20240107-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "f93f8375ca6ac0a35d58ff007043cbd3a88d9609113f1cb59cf7c8d215f064af", + "size": 16428491 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.12.1%2B20240107-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "eca96158c1568dedd9a0b3425375637a83764d1fa74446438293089a8bfac1f8", + "size": 17020244 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.12.1%2B20240107-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "236533ef20e665007a111c2f36efb59c87ae195ad7dca223b6dc03fb07064f0b", + "size": 25167638 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.12.1%2B20240107-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "74e330b8212ca22fd4d9a2003b9eec14892155566738febc8e5e572f267b9472", + "size": 67786580 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.12.1%2B20240107-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "fd5a9e0f41959d0341246d3643f2b8794f638adc0cec8dd5e1b6465198eae08a", + "size": 41069105 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.12.1%2B20240107-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "7dde51352511697579d86c968881f431acff9e8b1eb3f42c54de27341fa5b7b2", + "size": 46534376 + } + } + }, + "3.12.10": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250529", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.12.10%2B20250529-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "896af337e29993b52fecea36e5db371e6569affb7ef5de9ebb166045a0b158eb", + "size": 15674220 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.12.10%2B20250529-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "2579c9ffcbb0a745f1de3d645612c7466269eb9a23316a54ece49d50c1c9122f", + "size": 15600165 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.12.10%2B20250529-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "736bd4f3a422564c32ecc1a4999ef83ea7635a255318c0f9029bc9b0e258035f", + "size": 15876640 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.12.10%2B20250529-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "f171af578ecdbbcf63cbb0abd08f382df3e0d091e98c3161b3f84cd9d0acc0fd", + "size": 15802682 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.12.10%2B20250529-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "3a3b056810dfeab40e2b9ec91db1b928707e2a6ac4457a8be8a8b82eccb49070", + "size": 40218855 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.12.10%2B20250529-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "235d0666528a8e948896f68c08964dc8dd14a54c68e5cbd4924594d2e20f81b9", + "size": 27092008 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.12.10%2B20250529-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "3b7de567f4a8a1c3e5be2d51af3023dd3c1075cfacb6cd8efb511e0ef4b31fa8", + "size": 105518704 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.12.10%2B20250529-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f50d08e0304b15d752b22932047135c1dc02f81977b284e51288be9d90627bca", + "size": 34392187 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.12.10%2B20250529-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "3e0fd10e4bd60e66baad8be2a0e60775bb389e97e4f4a07138c34599c356aaed", + "size": 41889389 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.12.10%2B20250529-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "ca22a9a9e64ecab6d0b5de7cdf8b679ccaa41e9def6aaa2b4aaa6bb23ec7aaba", + "size": 21172386 + } + } + }, + "3.12.11": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251007", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.12.11%2B20251007-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "bcc44db7dce409d082d3a0a7032cd90d57f7c7b59fec868d36bf1fb28bf63989", + "size": 16627283 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.12.11%2B20251007-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "407fa242942a7ba5d91899abc562fc9897f7a0376f8d2060285e8c0560323f19", + "size": 16553113 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.12.11%2B20251007-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "f66c1a051514a10f694ff4e1d0d5faca01bf74bbb40e7ef4c10207a119e63e24", + "size": 16419419 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.12.11%2B20251007-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "e0578395f02bb6ac61a0df0f9e280f0317bbb46d8bed66232b3aa9a1477256f8", + "size": 16352184 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.12.11%2B20251007-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "acb7db76d010d3430695f8c6618088c3ba3cffdf78aa30a6fb26900b499244dc", + "size": 80111614 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.12.11%2B20251007-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "05d9207e41c36fbc2d80a51d9bd5d574ed3eb1c620ead49e7fcd57cc2b64edfd", + "size": 28537200 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.12.11%2B20251007-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "e91f024fd30007a7e25f4e151ec65e19600f915fd5ed36eafe30c56c4056c323", + "size": 103916679 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.12.11%2B20251007-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f98121eb1fb2b05a25c1f3d2fe7cf08c3a2468c350785df3d84c2516e7280d3f", + "size": 32648621 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.12.11%2B20251007-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "c45c6918bc7573874fa1f1ce03839bd1352e0afef027b5d01299b463a4ca108a", + "size": 42527031 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.12.11%2B20251007-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "4d7547ef9c97f52996ca2e9cb5c84aedda65a6972967c83ca382cba1ffe41aa5", + "size": 20509199 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.12.11%2B20251007-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "ada8701d4771525fcaccd41922e58f821c01e6cb40d53e7f21cb0c5e1ede156c", + "size": 45273490 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.12.11%2B20251007-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "d2877f74b01871c82d140a9eefe57b18185c7a84128727cbedcae45dc5c9e54f", + "size": 21741677 + } + } + }, + "3.12.12": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251217", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.12.12%2B20251217-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "127ccc1cbc164592c4460c92ce8b4b61da31b0eaa4dc28946f76ef2b9d274d76", + "size": 17084119 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.12.12%2B20251217-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "341f9fa5d92a019475ac88a90b5a06746245848d3852ed8609751cd2bb8d2b3b", + "size": 17000329 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.12.12%2B20251217-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "4deed4e744a8be424bc991bec2aa24373b30807a73659f5741a4352e58487b7c", + "size": 16937546 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.12.12%2B20251217-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "ad40de018063d74bdd77d969d780c98613694efed8f3d9e92f0ec78e1a190042", + "size": 16865632 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.12.12%2B20251217-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "b66b26e16c5ab7546b0868b4c79ab671bdc7346d4954beebf96ab33ebbfa47b1", + "size": 81214974 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.12.12%2B20251217-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "6b9dfd582900666c42baef1ad495fa68948964a8bff0dc3bccd0393febc2de7b", + "size": 28705212 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.12.12%2B20251217-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "e065aa467072547dfcc1ebc58d5302b02e89e40bfc972e179d2bfce204e03450", + "size": 105701839 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.12.12%2B20251217-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9f5474351378aeca746ee8a2ff3b187edec71d791ef92827eca14ab5b0e15441", + "size": 33198297 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.12.12%2B20251217-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "10ebb001834eabbbd929cba4cb5c1532e03d59b0e6afbfc9f5cf49ff857fb906", + "size": 42666763 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.12.12%2B20251217-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "261b6760ce99bec8f5b548ca4bde788d4410069740744d9c0b5cbdbba46580d0", + "size": 20506670 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.12.12%2B20251217-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "d1833b7d59a60e30ff44e80a5f2b9852d04b962425f91cf7f680cc9621c108b0", + "size": 45813134 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.12.12%2B20251217-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "5a5977e76858ecf5c5191d554e0072a0269335891c22034ff9624dc5b4b5ff6b", + "size": 21799889 + } + } + }, + "3.12.2": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240224", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.12.2%2B20240224-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "01c064c00013b0175c7858b159989819ead53f4746d40580b5b0b35b6e80fba6", + "size": 16840399 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.12.2%2B20240224-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "a53a6670a202c96fec0b8c55ccc780ea3af5307eb89268d5b41a9775b109c094", + "size": 17177894 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.12.2%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "e52550379e7c4ac27a87de832d172658bc04150e4e27d4e858e6d8cbb96fd709", + "size": 25620958 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.12.2%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "57a37b57f8243caa4cdac016176189573ad7620f0b6da5941c5e40660f9468ab", + "size": 68023315 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.12.2%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "1e5655a6ccb1a64a78460e4e3ee21036c70246800f176a6c91043a3fe3654a3b", + "size": 41212623 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.12.2%2B20240224-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "8341585c74e44dcb6a188d675033c63a94fdd4d5a7cdddf02f1dbcfb374eb2b7", + "size": 46648821 + } + } + }, + "3.12.3": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240415", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.12.3%2B20240415-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "ccc40e5af329ef2af81350db2a88bbd6c17b56676e82d62048c15d548401519e", + "size": 16814925 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.12.3%2B20240415-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "c37a22fca8f57d4471e3708de6d13097668c5f160067f264bb2b18f524c890c8", + "size": 17151699 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.12.3%2B20240415-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "ec8126de97945e629cca9aedc80a29c4ae2992c9d69f2655e27ae73906ba187d", + "size": 25626233 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.12.3%2B20240415-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "a73ba777b5d55ca89edef709e6b8521e3f3d4289581f174c8699adfb608d09d6", + "size": 67368051 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.12.3%2B20240415-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "f7cfa4ad072feb4578c8afca5ba9a54ad591d665a441dd0d63aa366edbe19279", + "size": 41019882 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.12.3%2B20240415-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "f7cfa4ad072feb4578c8afca5ba9a54ad591d665a441dd0d63aa366edbe19279", + "size": 41019882 + } + } + }, + "3.12.4": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240726", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.12.4%2B20240726-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "1801025e825c04b3907e4ef6220a13607bc0397628c9485897073110ef7fde15", + "size": 16681307 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.12.4%2B20240726-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "ef6948e836f531bd7a58ffbe602803ff1c83c65f99d1da19be369ea61f136c93", + "size": 16490253 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.12.4%2B20240726-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "4c325838c1b0ed13698506fcd515be25c73dcbe195f8522cf98f9148a97601ed", + "size": 17015324 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.12.4%2B20240726-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "9d68cbdd12d1d6f98d35cc76add232c12db75c6b7f49733bffc88e7b1c025a79", + "size": 16838842 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.12.4%2B20240726-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "a098b18b7e9fea0c66867b76c0124fce9465765017572b2e7b522154c87c78d7", + "size": 25484008 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.12.4%2B20240726-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "6c9cf13644edc7250525ab1b2529ba1c0fff56c0c5a5c2242d84b6d4889d2bea", + "size": 18953227 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.12.4%2B20240726-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "e133dd6fc6a2d0033e2658637cc22e9c95f9d7073b80115037ee1f16417a54ac", + "size": 63434864 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.12.4%2B20240726-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ca076aee4329f53f988346eb0521ad2a2cf7f723b6296088d03b98d8f22f5420", + "size": 22253274 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.12.4%2B20240726-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "74309b0f322716409883d38c621743ea7fa0376eb00927b8ee1e1671d3aff450", + "size": 40733745 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.12.4%2B20240726-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "6dd7b4607f8a25f0f5f68e745f4c572b1a20c3bbfa86accfa45b52ab93b18ece", + "size": 23862235 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.12.4%2B20240726-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "74309b0f322716409883d38c621743ea7fa0376eb00927b8ee1e1671d3aff450", + "size": 40733745 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.12.4%2B20240726-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "6dd7b4607f8a25f0f5f68e745f4c572b1a20c3bbfa86accfa45b52ab93b18ece", + "size": 23862235 + } + } + }, + "3.12.5": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240814", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.12.5%2B20240814-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "6943873ffcede238280a8fc0dbd4916c9bff54cf6a759352f86077c556c0c3a5", + "size": 16414525 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.12.5%2B20240814-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "90715cdab075e5a2680acf2695572d165b6269bdb5d1942ab577491478aea55f", + "size": 16223961 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.12.5%2B20240814-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "4c7619c25c037d377eebe8c7b98e6c818276b55714536ea82be2325d5e8ad572", + "size": 16738059 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.12.5%2B20240814-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "49a9f7ad41d62e0ece9e664ca5ae95f022e7b68eef48e8a6f11620ec9247c686", + "size": 16564547 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.12.5%2B20240814-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "36181612a3467f5e7c322c69fa85e12baf8370ae33456fca7cc821cfbe4df5f7", + "size": 25210985 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.12.5%2B20240814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "06e512178cb513658a01c054b3eafc649ca362ccbeb02a6ae8a55b02c1ba75ca", + "size": 18670032 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.12.5%2B20240814-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "3be3bd9b7bd11f4a71bcaf16813fe61f93812cdb814da3fb0d7298f1086752ef", + "size": 63458815 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.12.5%2B20240814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "10680b593b5e31833218fd83104dee74af970a3463403a22bae613b952a34e8d", + "size": 21984711 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.12.5%2B20240814-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "ba89687d1a1b68e662ca40bdbcbfb2457903553329ba3020f4c96365fda4a254", + "size": 40380365 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.12.5%2B20240814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "6eb0398795e8875575934cf21cdc9c7c7acddb46f9a52f91fdad509723f2f0e9", + "size": 23569213 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.12.5%2B20240814-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "ba89687d1a1b68e662ca40bdbcbfb2457903553329ba3020f4c96365fda4a254", + "size": 40380365 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.12.5%2B20240814-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "6eb0398795e8875575934cf21cdc9c7c7acddb46f9a52f91fdad509723f2f0e9", + "size": 23569213 + } + } + }, + "3.12.6": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240909", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.12.6%2B20240909-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "899f46eb592fcac4e834c064e4c901e8a4a6b5864e80b18efd2f0b7c3c050584", + "size": 15613933 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.12.6%2B20240909-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "0419bafa4444a5aa0c554197bce0679e7cc0f28edc7ee8cfbe0ccea860bdb904", + "size": 15421366 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.12.6%2B20240909-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "8c56da91436bee158b0d592aed3393c1fe3da3694ca35950ee1c52935ba8bfd5", + "size": 15955055 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.12.6%2B20240909-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "b10d19eb5548a3b3b0a5e6f9109834d7ecfc139bc15754f81a94d39eaa5bdd26", + "size": 15780284 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.12.6%2B20240909-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "caac1033f68f69d8978dc8c6b6964cfb9d8a111abc55c03403bd4ece63f331f3", + "size": 24403561 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.12.6%2B20240909-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "22d119ac7df7f0bddfd4dfd075bcc4eb2532ed3df0bdba0579106835d49ef9cd", + "size": 17862875 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.12.6%2B20240909-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "68ff386c923c59a33a272bd984b8a33fe8117c56ad7f7552e0c2b21937ee3c0b", + "size": 66702546 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.12.6%2B20240909-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b080463e4f0c452e592cdac1ca97936a6a19bb3d9a64da669a50ca843fce0108", + "size": 21243376 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.12.6%2B20240909-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "6280ce84c87ebaca2c4b42040bad48e7efbfd1b3f323579378ecf043e9fb023d", + "size": 38178975 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.12.6%2B20240909-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "fe9898060f52c2171c2aa074f470f91339bdcf9896dae6709021c914f58aa863", + "size": 21332727 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.12.6%2B20240909-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "6280ce84c87ebaca2c4b42040bad48e7efbfd1b3f323579378ecf043e9fb023d", + "size": 38178975 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.12.6%2B20240909-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "fe9898060f52c2171c2aa074f470f91339bdcf9896dae6709021c914f58aa863", + "size": 21332727 + } + } + }, + "3.12.7": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20241016", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.12.7%2B20241016-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "4c18852bf9c1a11b56f21bcf0df1946f7e98ee43e9e4c0c5374b2b3765cf9508", + "size": 15641754 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.12.7%2B20241016-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "95dd397e3aef4cc1846867cf20be704bdd74edd16ea8032caf01e48f0c53d65d", + "size": 15457017 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.12.7%2B20241016-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "60c5271e7edc3c2ab47440b7abf4ed50fbc693880b474f74f05768f5b657045a", + "size": 15992091 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.12.7%2B20241016-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "848405b92bda20fad1f9bba99234c7d3f11e0b31e46f89835d1cb3d735e932aa", + "size": 15817311 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.12.7%2B20241016-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "bba3c6be6153f715f2941da34f3a6a69c2d0035c9c5396bc5bb68c6d2bd1065a", + "size": 24425205 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.12.7%2B20241016-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c8f5ed70ee3c19da72d117f7b306adc6ca1eaf26afcbe1cc1be57d1e18df184c", + "size": 17865102 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.12.7%2B20241016-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "43576f7db1033dd57b900307f09c2e86f371152ac8a2607133afa51cbfc36064", + "size": 66940594 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.12.7%2B20241016-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3a4d53a7ba3916c0c1f35cbbe57068e2571b138389f29cf5c35367fec8f4c617", + "size": 21276148 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.12.7%2B20241016-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "f05531bff16fa77b53be0776587b97b466070e768e6d5920894de988bdcd547a", + "size": 38116958 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.12.7%2B20241016-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "fa8ac308a7cd1774d599ad9a29f1e374fbdc11453b12a8c50cc4afdb5c4bfd1a", + "size": 21335315 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.12.7%2B20241016-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "f05531bff16fa77b53be0776587b97b466070e768e6d5920894de988bdcd547a", + "size": 38116958 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.12.7%2B20241016-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "fa8ac308a7cd1774d599ad9a29f1e374fbdc11453b12a8c50cc4afdb5c4bfd1a", + "size": 21335315 + } + } + }, + "3.12.8": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250115", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.12.8%2B20250115-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "e29003b69465c33692830032d9d237d84ea43a2e8461db9134641640fb49f040", + "size": 15781321 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.12.8%2B20250115-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "dfb8a4c87116538717105ef3dec3668ae07590a5b5532109fec3ccad90be2fbc", + "size": 15591571 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.12.8%2B20250115-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "b81ae8ea17fce6e173649120fcc4eda123bb8df54890894bbec432f527fbe75c", + "size": 15997134 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.12.8%2B20250115-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "9f5d4a71b9157a160cf8a8e93158404553b588ddd4a94e6c54753c26b03fee5e", + "size": 15819246 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.12.8%2B20250115-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "2e08c1d4de239290b9fc3bef90f121349819b473149083470d16081dd293050c", + "size": 24470519 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.12.8%2B20250115-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "300e8098cfc305b329a258128afa1d6366ced039f16c70ec93ab4ff18f86c8ff", + "size": 17911606 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.12.8%2B20250115-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "e5435e717c934ed30d4066f64e858497c27f37c1ba547f403b050d9221e50ea4", + "size": 67116858 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.12.8%2B20250115-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "34a5a1619aba16544ec8d6f225be59b333d650f58983eeca25193722dc9016fd", + "size": 21288771 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.12.8%2B20250115-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "86ee8267900240c96369adb2cbc1af8f543f860d2e22be5adb7362f3cbe61059", + "size": 41820929 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.12.8%2B20250115-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "8a4e9e748eeee7ae71048a108a55a9bac48f8bedf9dff413a7c87744f0408ef1", + "size": 21159982 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.12.8%2B20250115-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "86ee8267900240c96369adb2cbc1af8f543f860d2e22be5adb7362f3cbe61059", + "size": 41820929 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.12.8%2B20250115-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "8a4e9e748eeee7ae71048a108a55a9bac48f8bedf9dff413a7c87744f0408ef1", + "size": 21159982 + } + } + }, + "3.12.9": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250317", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.12.9%2B20250317-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "7c7fd9809da0382a601a79287b5d62d61ce0b15f5a5ee836233727a516e85381", + "size": 15696044 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.12.9%2B20250317-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "0a4647b7df3c8eca11071d6cea68a14a4b102bd6fc6afae314e9852510654b7d", + "size": 15615620 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.12.9%2B20250317-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "1ee1b1bb9fbce5c145c4bec9a3c98d7a4fa22543e09a7c1d932bc8599283c2dc", + "size": 15894631 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.12.9%2B20250317-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "1a414bf392a7afe08c742502a82edd41893a1144ccbceb184dc5ee6ee9c069c0", + "size": 15829490 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.12.9%2B20250317-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "00c6bf9acef21ac741fea24dc449d0149834d30e9113429e50a95cce4b00bb80", + "size": 24473413 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.12.9%2B20250317-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0354f70e7d3e2d0c36308edc1815c563d9bae1a3221830f7e222f6bb0a7e1a3a", + "size": 17911103 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.12.9%2B20250317-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "ef382fb88cbb41a3b0801690bd716b8a1aec07a6c6471010bcc6bd14cd575226", + "size": 66756265 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.12.9%2B20250317-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a36bc60c38fe146e908e2e71fc21266c8558b24a9407226b1d887212839437ef", + "size": 21295465 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.12.9%2B20250317-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "d15361fd202dd74ae9c3eece1abdab7655f1eba90bf6255cad1d7c53d463ed4d", + "size": 42031613 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.12.9%2B20250317-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "ee338839315bdd8af5fc935f9595eca20ebebdd250726c5816b2d0cf94d1e661", + "size": 21175541 + } + } + }, + "3.13.0": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20241016", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.13.0%2B20241016-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "31397953849d275aa2506580f3fa1cb5a85b6a3d392e495f8030e8b6412f5556", + "size": 15609275 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.13.0%2B20241016-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "e94fafbac07da52c965cb6a7ffc51ce779bd253cd98af801347aac791b96499f", + "size": 15440316 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.13.0%2B20241016-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "cff1b7e7cd26f2d47acac1ad6590e27d29829776f77e8afa067e9419f2f6ce77", + "size": 16023075 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.13.0%2B20241016-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "406664681bd44af35756ad08f5304f1ec57070bb76fae8ff357ff177f229b224", + "size": 15874759 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.13.0%2B20241016-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "e8378c0162b2e0e4cc1f62b29443a3305d116d09583304dbb0149fecaff6347b", + "size": 24545734 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.13.0%2B20241016-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "06e633164cb0133685a2ce14af88df0dbcaea4b0b2c5d3348d6b81393307481a", + "size": 17577968 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.13.0%2B20241016-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "2c8cb15c6a2caadaa98af51df6fe78a8155b8471cb3dd7b9836038e0d3657fb4", + "size": 48591281 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.13.0%2B20241016-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b5e74d1e16402b633c6f04519618231fc0dbae7d2f9e4b1ac17c294cc3d3d076", + "size": 19024698 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.13.0%2B20241016-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "b25926e8ce4164cf103bacc4f4d154894ea53e07dd3fdd5ebb16fb1a82a7b1a0", + "size": 38660063 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.13.0%2B20241016-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "c8134287496727922a5c47896b4f2b1623e3aab91cbb7c1ca64542db7593f3f1", + "size": 21276825 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.13.0%2B20241016-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "b25926e8ce4164cf103bacc4f4d154894ea53e07dd3fdd5ebb16fb1a82a7b1a0", + "size": 38660063 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.13.0%2B20241016-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "c8134287496727922a5c47896b4f2b1623e3aab91cbb7c1ca64542db7593f3f1", + "size": 21276825 + } + } + }, + "3.13.0rc2": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240909", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.13.0rc2%2B20240909-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "5d38ca1e6b030b004714e10813903e906c6b8f2a6361770df4512a838f4a4a9f", + "size": 15610096 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.13.0rc2%2B20240909-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "9e17f9fcc314a5dd489089a7502a525c4dd08af862f9cf33b52161a752f2a5b7", + "size": 15445592 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.13.0rc2%2B20240909-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "c3dcd4314324159945dc19342c73b9deb8de0f2d1709171427dd52f1a05eecca", + "size": 16028913 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.13.0rc2%2B20240909-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "971668ac7f3168efc4d2b589e9d36247ab8ca9f9525c56c8aa7bfd374060105b", + "size": 15873381 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.13.0rc2%2B20240909-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "85e103fc81a1fcf94a93180f6df42e39a7dc15d4b711705e133dc2ec847552e7", + "size": 24563596 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.13.0rc2%2B20240909-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "d99a663d3b9f8792a659e366372e685550045cad12aef11645c06a9b6edcd071", + "size": 17577277 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.13.0rc2%2B20240909-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "028581cce5004c66775a3ae8b3ed65681ab4b289608dfd1aec3354d169216099", + "size": 48486047 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.13.0rc2%2B20240909-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "1893a218709d3664b7a2b80f5598b5f25c0c3fe2bcc8d0a1c75eec6bbb93d602", + "size": 19026674 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.13.0rc2%2B20240909-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "31282f912e984d399c56925dfb69a4f3ce76226dfb4806b09f37e3b4a15e5a30", + "size": 38721655 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.13.0rc2%2B20240909-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "c883205751c714bd0519592673a88f160a55d34344cc1368353ad34a679eb94a", + "size": 21283423 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.13.0rc2%2B20240909-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "31282f912e984d399c56925dfb69a4f3ce76226dfb4806b09f37e3b4a15e5a30", + "size": 38721655 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.13.0rc2%2B20240909-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "c883205751c714bd0519592673a88f160a55d34344cc1368353ad34a679eb94a", + "size": 21283423 + } + } + }, + "3.13.0rc3": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20241002", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.13.0rc3%2B20241002-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "577e846eaf0a27342b3f80b5f292eb292b0eb6a9df3a0fa0c5925e86cfe046a3", + "size": 15608950 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.13.0rc3%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "685ef71882f16eabab0bc838094727978370f0ad95c29f7f5c244ffa31316aeb", + "size": 15439870 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.13.0rc3%2B20241002-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "81a01923b25a4b2b68f517b07a4a4a9186d873e6518f060e4a004e77d7094687", + "size": 16028886 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.13.0rc3%2B20241002-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "0f5f9fcf82093c428b80c552165544439f4adcdbe5129ecf721d619e532e9b5e", + "size": 15879142 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.13.0rc3%2B20241002-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "48974ab9b4a6990b17e6740393492fc5557e6acc4ab02be9d9543e0e506e48a6", + "size": 24553424 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.13.0rc3%2B20241002-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "1414c6b37f37e8fd9d14e48d81e313eb9c965cb0330747d5d2d689dd7e0c7043", + "size": 17578602 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.13.0rc3%2B20241002-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "a4b4e0a79b53938db050daf7f78dd09faf49d1e29dbba6cac87cf857c0a51fb4", + "size": 48619305 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.13.0rc3%2B20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "445156c61e1cc167f7b8777ad08cc36e5598e12cd27e07453f6e6dc0f62e421e", + "size": 19024187 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.13.0rc3%2B20241002-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "319ecb727317868c4f3bfecd480f216b38275f5373f72c022d255318b650ebe2", + "size": 38750643 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.13.0rc3%2B20241002-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "b59317828ef88f138ee122d420b60f2705bc72ae846ff69562e79e6c5cbc3177", + "size": 21273002 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.13.0rc3%2B20241002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "319ecb727317868c4f3bfecd480f216b38275f5373f72c022d255318b650ebe2", + "size": 38750643 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.13.0rc3%2B20241002-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "b59317828ef88f138ee122d420b60f2705bc72ae846ff69562e79e6c5cbc3177", + "size": 21273002 + } + } + }, + "3.13.1": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250115", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.13.1%2B20250115-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "d00ac75b15a05da2f6bc0057fe36f6944f9d513239f1c7af12766e03f288fe65", + "size": 15763344 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.13.1%2B20250115-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "650f1d3242667c64959391105525469e0fe1502a6aab9f5db3b0bfefe7dcbabd", + "size": 15599693 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.13.1%2B20250115-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "994c233cf86487b61283db63363ee969cf69dc551fba389c6d6d4e8534d4735f", + "size": 16078711 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.13.1%2B20250115-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "26e0d5320bff7d141531e09849f0735c634bba31003ed6b089b9bf434312a773", + "size": 15924893 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.13.1%2B20250115-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "df7f2a1ff4af907c16349b2a1554becfd3675b94289c7f978fd6e10faa6af4ab", + "size": 24619971 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.13.1%2B20250115-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "852b909cf77f84814d66fe9a373447c57371edbe88a531781e02a2163247572a", + "size": 17624695 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.13.1%2B20250115-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "5e7d73b3144d51f15fca492c0f8afd9f690fd3e7bfa61e259beb3650d0336e1b", + "size": 75196874 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.13.1%2B20250115-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "56817aa976e4886bec1677699c136cb01c1cdfe0495104c0d8ef546541864bbb", + "size": 21298278 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.13.1%2B20250115-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "92021e1151d118db94689a38407248de96d8a2b8ffa9c4ae31b990441fda0aa0", + "size": 42684952 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.13.1%2B20250115-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "8ccd98ae4a4f36a72195ec4063c749f17e39a5f7923fa672757fc69e91892572", + "size": 21117774 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.13.1%2B20250115-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "92021e1151d118db94689a38407248de96d8a2b8ffa9c4ae31b990441fda0aa0", + "size": 42684952 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.13.1%2B20250115-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "8ccd98ae4a4f36a72195ec4063c749f17e39a5f7923fa672757fc69e91892572", + "size": 21117774 + } + } + }, + "3.13.10": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251202", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.13.10%2B20251202-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "37afe4e77ab62ac50f197b1cb1f3bc02c82735c6be893da0996afcde5dc41048", + "size": 17080406 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.13.10%2B20251202-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "799a3b76240496e4472dd60ed0cd5197e04637bea7fa16af68caeb989fadcb3a", + "size": 17001366 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.13.10%2B20251202-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "a02761a4f189f71c0512e88df7ca2843696d61da659e47f8a5c8a9bd2c0d16f4", + "size": 17004291 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.13.10%2B20251202-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "705b39dd74490c3e9b4beb1c4f40bf802b50ba40fe085bdca635506a944d5e74", + "size": 16941588 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.13.10%2B20251202-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "c68280591cda1c9515a04809fa6926020177e8e5892300206e0496ea1d10290e", + "size": 93136457 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.13.10%2B20251202-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "69991105baa964ceeb080c4ddd961f62c933e2538fe1015023fad3971a2f6e08", + "size": 28661775 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.13.10%2B20251202-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "0cac1495fff920219904b1d573aaec0df54d549c226cb45f5c60cb6d2c72727a", + "size": 123473451 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.13.10%2B20251202-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ecf304c757c9de6aef849ca451ec08a72f6827c0fc8819d39fd341f29d00ccd6", + "size": 34341351 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.13.10%2B20251202-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "9060d644bd32ac0e0af970d0b21e207e6ff416b7c4dc26ffc4f9b043fb45b463", + "size": 43456761 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.13.10%2B20251202-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "f8d043f17a576d63c0658d2535e88a9faf45ef16ce8bc4197eb60e85ef633586", + "size": 20361439 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.13.10%2B20251202-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "8b00014c7c35f9ad4cb1c565f067500bacc4125c8bc30e4389ee0be9fd6ffa3d", + "size": 46794126 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.13.10%2B20251202-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "adcc33311d1f839c030ef2288b2926a7d966bdd2fbe5a056d4b5e7ac8f593e8f", + "size": 21661329 + } + } + }, + "3.13.11": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251217", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.13.11%2B20251217-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "7a4782de058c014842060d6aafe7cf9532c30943a5fe1eb24b8ec682d542e5d4", + "size": 17083330 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.13.11%2B20251217-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "324b24ebd50c16cf3a88360fc0e85ced38b04abcf580bc73cf95def4852e0c29", + "size": 17003961 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.13.11%2B20251217-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "fc8c4f2b52eee203f670fb9fb4139319ce789370406f4f90915475cf68d9ee15", + "size": 17005323 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.13.11%2B20251217-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "70f76d40609999213b44a37e947dc0fe0b975f48d206f8931992892870bd4026", + "size": 16941517 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.13.11%2B20251217-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "1a6f41728ce7e49207ab85de83095e8ba0e1b24e9ff9db0bd2c1c5e0c366af66", + "size": 92319054 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.13.11%2B20251217-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "43a75dd1176eabeee05e6554c6e8db43aafd347cc7416ac3c4ab09c871d61a22", + "size": 28626138 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.13.11%2B20251217-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "7b72e75c98d76d0b575840f9913289354b8e95c865248c5df2877214525b0990", + "size": 122302174 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.13.11%2B20251217-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f1377f34f94c043b64b0dc1fed592619e36b0dbd737c1e733467826e4698d9d3", + "size": 34267480 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.13.11%2B20251217-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "db935d308f6b8ef17dd54640293328a68afd67ed9b78159e46d3ec92478a28c9", + "size": 43474014 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.13.11%2B20251217-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "bb58bbc2e956448ca77201a1e505f23db73564c629af98dc65398c97c521926b", + "size": 20364925 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.13.11%2B20251217-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "1fc6f07e075da66babb806802db8c86eecf1e9d29cbcb7f00227a87947b3735a", + "size": 46737856 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.13.11%2B20251217-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "1380be188042a34052d7207bc733ec0037289d37855b6c314a56596da8d3f611", + "size": 21654331 + } + } + }, + "3.13.2": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250317", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.13.2%2B20250317-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "faa44274a331eb39786362818b21b3a4e74514e8805000b20b0e55c590cecb94", + "size": 15696120 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.13.2%2B20250317-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "9002e620e4113e7439b2de0db5ff9b2dc914cde4fba2f10134cce3a5cdebac81", + "size": 15625062 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.13.2%2B20250317-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "ee4526e84b5ce5b11141c50060b385320f2773616249a741f90c96d460ce8e8f", + "size": 15986220 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.13.2%2B20250317-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "e5a904ecfb4061389773dd655d3b5665447c80cbf2948fcb1c07e92716eed955", + "size": 15926630 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.13.2%2B20250317-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "9c67260446fee6ea706dad577a0b32936c63f449c25d66e4383d5846b2ab2e36", + "size": 24630955 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.13.2%2B20250317-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "106bf4f243a2d02a1e3995ad0551bf15bdfb33abfc18f85138e6b8dd0d1923fc", + "size": 17628079 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.13.2%2B20250317-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "db011f0cd29cab2291584958f4e2eb001b0e6051848d89b38a2dc23c5c54e512", + "size": 75422950 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.13.2%2B20250317-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ee909a9d0fb16375ebd3b3ca0322fd9cac0904b304e28ad68527036858e301e9", + "size": 21361912 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.13.2%2B20250317-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "84d7b52f3558c8e35c670a4fa14080c75e3ec584adfae49fec8b51008b75b21e", + "size": 42799056 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.13.2%2B20250317-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "1d948860b5ddeff67a6fece81cc3354f9144161968d2128186d4e1a3562a4e76", + "size": 21127800 + } + } + }, + "3.13.3": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250529", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.13.3%2B20250529-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "f6a8b64ded398d60b2a10dcef8d81df6d53a87baf24bee79d413cc6f35d3874d", + "size": 15719292 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.13.3%2B20250529-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "ad2afb2c030665b3a9617a30b1fe60024ddcaf97042ba37f8d14e9e1c0bd4aa9", + "size": 15646824 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.13.3%2B20250529-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "155ae058c171f2080fd0a17e66d01f2ce4a146e112a44f9b39dc497d88ec2909", + "size": 15954514 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.13.3%2B20250529-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "84f0a473dca76e1fd0f5906256be47ae6798021536a8f5f99031354e6c7ea08e", + "size": 15895263 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.13.3%2B20250529-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "ee001fa47bf5e2405713ba663ae681078394ae64ab04e1981acb927570a36b58", + "size": 40512134 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.13.3%2B20250529-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ac752f8f26f96fc58944e2c3b6528b5ed8964e745ff86e2aebf0ee07f0c9d11d", + "size": 26456909 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.13.3%2B20250529-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "2a9fec6a0100d440c23c9d78f6d89a1a5b2a01e780abd7f5ec25ceb6ea0af985", + "size": 120555807 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.13.3%2B20250529-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "532a6db30bcf8f8609a0c4e0a7c985315572364c19868bc9650a64df534954e7", + "size": 35346222 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.13.3%2B20250529-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "a5513362690343663aeb51ae0c3989eb4e187ecdba0497aea6b72931472e245d", + "size": 42806163 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.13.3%2B20250529-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "35ca5521b4e634f64075ca5b5002b72e63a1cb27f9f87bcd60a289348aea61a3", + "size": 21129750 + } + } + }, + "3.13.4": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250610", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.13.4%2B20250610-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "c2ce6601b2668c7bd1f799986af5ddfbff36e88795741864aba6e578cb02ed7f", + "size": 15748305 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.13.4%2B20250610-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "59183bd70b0facb184fc5e7bd18488595d90ac5eb010ac1ee63eea7db4e230a1", + "size": 15667715 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.13.4%2B20250610-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "79feb6ca68f3921d07af52d9db06cf134e6f36916941ea850ab0bc20f5ff638b", + "size": 16011731 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.13.4%2B20250610-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "b219b93c37ffefe46495feff6d996048706832307de454efa60a09b392f887db", + "size": 15942296 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.13.4%2B20250610-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "3c2596ece08ffe17e11bc1f27aeb4ce1195d2490a83d695d36ef4933d5c5ca53", + "size": 40532263 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.13.4%2B20250610-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "fef6c8c96260eff9258aeb38bbfc90a9d662e683f8fe6946a941c954053bdb71", + "size": 26450173 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.13.4%2B20250610-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "44e5477333ebca298a7a0a316985c6c3533b8645f92a83f7f73c44033832bf32", + "size": 121663496 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.13.4%2B20250610-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "ffd0b484e40e4fffdfcac265560e109456f802485f1f27e3cd314763b2b1587c", + "size": 35447894 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.13.4%2B20250610-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "29ac3585cc2dcfd79e3fe380c272d00e9d34351fc456e149403c86d3fea34057", + "size": 42849409 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.13.4%2B20250610-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "1e6348c237d4665c5327683030594b4f93405ae19ad7dabfb1951d12bb266944", + "size": 21122324 + } + } + }, + "3.13.5": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250723", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.13.5%2B20250723-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "734c2f197cf2464d18308458c32679f38f22f31a911f86193e0f04dd56400f33", + "size": 15751738 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.13.5%2B20250723-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "a2c25baa13d271b730744d7e8684d7db54a003dfeeb5ae3b0ae86af7d81d44ae", + "size": 15669708 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.13.5%2B20250723-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "6b508822f5238451a5dcc52f07310b74aaa701ed963bba923cc7f4d24010cc21", + "size": 16012720 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.13.5%2B20250723-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "c198610998092c42650600ef250be388ba27ab307ade4d8684c27af9a0b5569a", + "size": 15947186 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.13.5%2B20250723-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "a6f558daf7d4e0e7017ad827a4f6bcc9e8fc4be0e42ddbbb3d13540c7a58d829", + "size": 90959745 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.13.5%2B20250723-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "65412f4d99966ab9e0218211c1408497b5ecd2a40e1bd12183a37937c25cce4e", + "size": 29007390 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.13.5%2B20250723-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "56bf8099cfcc3aac8dadcf2be53c48e5998d74cf5da600691dbf16be3f0b8f76", + "size": 121604855 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.13.5%2B20250723-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f7ac16748be2674ec14df532a3e48d0c6f215017b537f14ca3feb837dbe86292", + "size": 35452245 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.13.5%2B20250723-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "4c2a578f472ec4bb34289b4afc5b930f97224b8cb351c9fa3722b8d736895ca3", + "size": 40338078 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.13.5%2B20250723-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "73a16356d1f21ad15467f14f5c53a6ab9ff717f8f9dc62cf865098d893338c95", + "size": 19974879 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.13.5%2B20250723-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "191aaa9c725afad7570d24940268038823df7d14e0afbd25d1e23af0b99190a7", + "size": 42922998 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.13.5%2B20250723-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "4d88fdda4a59e0d6d45953c37c2fcb4e114dd5a2d41cb5b24b75b42ab8328ff8", + "size": 21135412 + } + } + }, + "3.13.6": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250814", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.13.6%2B20250814-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "88c9c0c7ec29c93493084f25d78a4b4595f7b662578904c3b26e8c7f70a35e9b", + "size": 15708824 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.13.6%2B20250814-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "03bc8f2baef0f3cffa1f59715ffe755e0422e348bba810dcc705760fa5bd031d", + "size": 15624144 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.13.6%2B20250814-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "84552fc6e372022c5c92f2931fda0d53a70f6a9203e2db94cb40961fa9e01d68", + "size": 16020149 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.13.6%2B20250814-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "3db02afa562070998b5dd293065a3e5bf6c4bc1cf4c30a679d900405bfbe2ae8", + "size": 15950981 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.13.6%2B20250814-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "e4ac95cb074ba70115f180fd9bea720534af0cd479ee22522ba9cfe304ab522c", + "size": 88641919 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.13.6%2B20250814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "605953cfc262101cff5c64d0f97caad80e43ad275adf2d9eb310bf6e5b08edcf", + "size": 26502758 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.13.6%2B20250814-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "3b31f0c51bfdcde4cb597f1d0d3a523c0eeb4640a5ed0b5fcc114bab34a893ad", + "size": 118799004 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.13.6%2B20250814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "d847458546425b6fc28d5fa9238f12f8c3a7ac41f28b5882dc249d0fc595f359", + "size": 32327269 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.13.6%2B20250814-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "21f256b31e0eabedc817f25af0051424152906b0a04663154b9a1f6bd9965b46", + "size": 41167538 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.13.6%2B20250814-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "e8b107bd9ff5b071f699dda3585e3fc60a89cb5f9800eb94bbfb5bcb408449a4", + "size": 19906826 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.13.6%2B20250814-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "f15b01f8956f65f3d4682221e8539a61159b6ce4117133c28e9252d0796565ce", + "size": 42749559 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.13.6%2B20250814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "09e489d2e7123cbd8111293e91af08efd203c9229c9e761ba3995bb263a6fa92", + "size": 21069469 + } + } + }, + "3.13.7": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250918", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.13.7%2B20250918-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "7f5ec658219bdb1d1142c6abab89680322166c78350a017fb0af3c869dceee41", + "size": 16617745 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.13.7%2B20250918-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "10f26ea5fc7c4d9c012faf372898a801f34b8fd7730c677576263224d56835fe", + "size": 16544693 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.13.7%2B20250918-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "4fb42ffc8aad2a42ca7646715b8926bc6b2e0d31f13d2fec25943dc236a6fd60", + "size": 16479033 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.13.7%2B20250918-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "e640143d4dde2edff1865c3e88e44b046478b790af4f8b3ac4d228e53952dda0", + "size": 16415146 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.13.7%2B20250918-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "7d4448fe9b6fa37d8d16635d52d1d8ffb42576d85e23331ae345a07cfa60bc61", + "size": 90786031 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.13.7%2B20250918-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0af6c9a17df4d5afdef37825d0cc703264c1a0a94dad825b2b6b6975b524c0d5", + "size": 28500601 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.13.7%2B20250918-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "0a01bad99fd4a165a11335c29eb43015dfdb8bd5ba8e305538ebb54f3bf3146d", + "size": 120060763 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.13.7%2B20250918-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "888fb7e192caafdb33e028f865b7da68dce5e924cfe58fcef96323e175a70e3e", + "size": 33602096 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.13.7%2B20250918-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "1f1f9941838bb294b3cfb6462b25506e60c5ed4f30a45d40a55d98f69e49d6ee", + "size": 43186021 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.13.7%2B20250918-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "0442aee25951ee16af3ac2227cf4a8a6e3d5b6530030fbf6cd1305ff3d3fc0d3", + "size": 20353564 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.13.7%2B20250918-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "005cb2abf4cfa4aaa48fb10ce4e33fe4335ea4d1f55202dbe4e20c852e45e0f9", + "size": 46083099 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.13.7%2B20250918-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "bc229e5364699a8456b6ceabde8348c75e62312ffd62631dd1e494a1755a45ed", + "size": 21614108 + } + } + }, + "3.13.8": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251010", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.13.8%2B20251010-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "0dc9061b9d8e02a9344aa569eecb795f41f16ac8bb215f973d8db9179700e296", + "size": 16616160 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.13.8%2B20251010-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "b26b90eabc607e75598e8e8ea31545eb97b72505473dec0b8fde080253038294", + "size": 16546015 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.13.8%2B20251010-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "e3b692599bb3c247a2f95c21577f8b85b70924a5f2d672c9e0005608d7b9c907", + "size": 16466744 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.13.8%2B20251010-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "126cf411159415b0d8c0865fef7d6d7aff7ecb7586b41bd013fc77aec7dcb4d6", + "size": 16402754 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.13.8%2B20251010-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "1b9af3c628cdbef3b9eb4c61df628bbc54f4684ace147f3dc9850c336c44c125", + "size": 90784364 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.13.8%2B20251010-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "6a4d99e2325978f063b8ad89cca6a1034081415f65590b115475778d09b71a1d", + "size": 28499871 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.13.8%2B20251010-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "12dd8995e8ec2df68cd1301b053f415c7884b8aae9d3459a2ac1448f781dbbbc", + "size": 120370499 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.13.8%2B20251010-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "1ac8691d25271f99491e276d0564ccfc20b6650df0be053396af5a539698463e", + "size": 33629731 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.13.8%2B20251010-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "430d9073f22c744d6ea8c224d7b458a6be3620f9bd2389068908c214ea4423f8", + "size": 43313764 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.13.8%2B20251010-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "5714f7f527b5544922f679f52ef9c3161c39eda6816dde1ee06e41793958c8ec", + "size": 20357967 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.13.8%2B20251010-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "1dde7aab47a52e81b6bd3f7d1bc5fa2f3c9e428eb5f54f51e8b92ae0a3e2409f", + "size": 46029376 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.13.8%2B20251010-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "7f8917ab153db0e280eb639621b8d38819afacfe7bfed55856ad53bf17ea5960", + "size": 21616805 + } + } + }, + "3.13.9": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251120", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.13.9%2B20251120-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "f8a42a4e68357f06c2890778c5b6540cafba432072dd1f5954b8ef621e85ca5f", + "size": 17041929 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.13.9%2B20251120-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "29d38bd5fbe267b37cecac990015c662796edcf51c2f5e321d59ec06a1850816", + "size": 16962215 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.13.9%2B20251120-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "3140da5a59668581b889e28e666a5f1eabe773c7ebd92509aeb5549a79d0fc01", + "size": 16946600 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.13.9%2B20251120-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "9da51afb00480a8ccd4909bd302c80d9c104d633c5e865cf37dd5e587185e125", + "size": 16876233 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.13.9%2B20251120-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "62a0fe87a2996f13631dad8a7e2b93998d3507e30a2b5e56901d3b3f3eafb25f", + "size": 92195269 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.13.9%2B20251120-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "fdc5d80488b64fcef3b99f297618f68e5374c13d89e888a66abeff4967afd308", + "size": 28619403 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.13.9%2B20251120-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "d0917c3a3270e6bde46852dbdc6af1f9245dd528bd7c255981d8f49ffdc913dc", + "size": 121884791 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.13.9%2B20251120-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "1bc9a94ab2425fb6ae58e45f69cab61647759b57895649701390822d976fa923", + "size": 34257108 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.13.9%2B20251120-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "ece65df55f0b850229e8b0ee6928f14418b37b662421665263b87c14291c8926", + "size": 43420784 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.13.9%2B20251120-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "3a7068f8a0bc197d6a62908f62a3e2c177ef4ed8d63b78bcbff7a24996638aba", + "size": 20329463 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.13.9%2B20251120-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "b722ffec99bb16db6e4d4a54f0bbc81b6bd6f31c966a18a96bada15057471880", + "size": 46649636 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.13.9%2B20251120-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "f4c22b31ddbf8d7824cbcba2d8707621c2c8fab1fb6d2c1810c2bb0304d8e9a8", + "size": 21638637 + } + } + }, + "3.14.0": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251120", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.14.0%2B20251120-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "0c508b08257a31d047d36ea75e9af53acce2b17e225c3c35c168781041073643", + "size": 17545399 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0%2B20251120-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "5995f024e9c95ccbc87b9bb97152ad1ec9efcf9d76e0574cd37dd946afbea3c6", + "size": 17467351 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.14.0%2B20251120-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "29abec6d22562b9e0fcadc987ef178f72ae092aea79bc252f400dbff03de0257", + "size": 17569426 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0%2B20251120-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "eb0a5915f58c3dcb64d587e951f8942e6c9c6af63fab8bbb5596d1ce5738c2a4", + "size": 17500924 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.14.0%2B20251120-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "415d2ad7590b50b7c83927d5dde984bc76818ba5d876a274fe5a83a5001b45ad", + "size": 96853565 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0%2B20251120-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7603b1905e5d280191b2146599474fe35da0833c81a09512c28b9b77e651812d", + "size": 29567910 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.14.0%2B20251120-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "4c4eb8334f3dc0199459dbaf84edf888f166b21ae96ed5d75aeb662a6decb51f", + "size": 127448074 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0%2B20251120-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "66f385305ae0eefd6b65e2cf942bc91d943a61e26fb7581e029f760a2b04f393", + "size": 35376131 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.14.0%2B20251120-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "a45b06b0ff76b505da8acd6995696ebf4bcf67e742a62a02eb6be136690ce8af", + "size": 45687905 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0%2B20251120-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "ad86d66d74b0cd3a5e9a9a4b68067ac5f46bc130df49c42f5488b13b847132bd", + "size": 21203702 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.14.0%2B20251120-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "e0d21d27d6ba3260876e1d267501e4520b0869ad24ba8e0afdb7dfb7294224fc", + "size": 48718904 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0%2B20251120-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "42a45020cff6a33e0d4f38f63d68c81240f0efa765e1e5a23693fb4c4a9f5e9a", + "size": 22226793 + } + } + }, + "3.14.0a3": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250106", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.14.0a3%2B20250106-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "22cff9a78f2fb1fdb80c12629879053171678e6c73ff85241d686fcb7a615fb0", + "size": 15776409 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0a3%2B20250106-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "4d474a12d52c316f0b68790fc1f8d747cc665f3d9a4d44c8f6757b8e4aeed497", + "size": 15609256 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.14.0a3%2B20250106-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "829122a26bbc8bda124e4c29a11519b9795b6152ad771d756ed3e9758340c981", + "size": 16077585 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0a3%2B20250106-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "f5da54ddec8866113cb2809e1dec0ec0f4617a149b21945da7b8c8f5ccab8e5e", + "size": 15923079 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.14.0a3%2B20250106-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "151a1c4bf509a9a2335783162a419d22ea4b55a266ca2525183a3b934905d8f5", + "size": 25134639 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0a3%2B20250106-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2796c6573a5359c88cb8c8b2daa561c7377ad7876f2fe45983fb977b1d10e208", + "size": 17734830 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.14.0a3%2B20250106-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "a2fd96adbcbcf046b708892012790bdac5508f426510057f20f5049d9036a187", + "size": 76105261 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0a3%2B20250106-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f143d48894e9c865a520982f28a575f138623b5ca14ca082f126171ccba46696", + "size": 21453371 + } + } + }, + "3.14.0a4": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250205", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.14.0a4%2B20250205-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "27e75e3048692276e3062e507f4536244f6d0f9a4d71a80954de3a427133b11b", + "size": 15903560 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0a4%2B20250205-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "a1b45b628959e057bbad22967b2b194b451851d6a8ac2c024825305b329270c0", + "size": 15732368 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.14.0a4%2B20250205-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "2c06da46122f17f1354a73f1974ad4ed879ba96e94fe55f45752d8cfee550d86", + "size": 16230991 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0a4%2B20250205-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "a26980d24e27080022b83590dd071aebf3a696b8c4221ea8af4656e39dc933f2", + "size": 16077173 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.14.0a4%2B20250205-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "8673b089d42fc0f36ecb57640c2b443ef86919c0462243d54fb46682b00d72ef", + "size": 25170098 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0a4%2B20250205-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2108c6adcf2a3cd5e19ce004e8b01fb3a64ce222c0f94e54edf1f1e878414d2d", + "size": 17763522 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.14.0a4%2B20250205-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "8beb9d7a0997276966a4bd50bc7562a49e0158ece05bfa8243d84a82007bdc7f", + "size": 79166874 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0a4%2B20250205-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "d03192f54aceca869ace598c44a8fa8535f858bd7e847f23a164355f37772a14", + "size": 21474298 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.14.0a4%2B20250205-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "5ce8511d4c0928cb87a5f9b518a72234bb89d87398b9bb82e55ca16f8adc1276", + "size": 42999630 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0a4%2B20250205-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "e92c796ae7ddc5c0ecea86d1499c128720e7f15cc232f33f96b60b0d067aa6d2", + "size": 21172093 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.14.0a4%2B20250205-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "5ce8511d4c0928cb87a5f9b518a72234bb89d87398b9bb82e55ca16f8adc1276", + "size": 42999630 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.14.0a4%2B20250205-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "e92c796ae7ddc5c0ecea86d1499c128720e7f15cc232f33f96b60b0d067aa6d2", + "size": 21172093 + } + } + }, + "3.14.0a5": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250311", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.14.0a5%2B20250311-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "a8feb44b7d6a6986b747e96de1b0685da7f2cdde20a62fd726361d1442963378", + "size": 15764834 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0a5%2B20250311-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "e72f07c8680415508d3c442456808985273029a6019c2301654ff6da7ddbaf4e", + "size": 15689680 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.14.0a5%2B20250311-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "8ba93ad0e9b4ad49f7c7fb05bf7ef994e4a81a31d79a799687aa8645daa8a68b", + "size": 16160543 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0a5%2B20250311-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "93a07669b666af72ec251ca3fca803ce1464e33811da4d3474e2dc87e159774b", + "size": 16100259 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.14.0a5%2B20250311-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "37f8442f67995b80fb3f73776800a6261e69f1031b4e5020dba92d4f0efcf0be", + "size": 25254054 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0a5%2B20250311-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9def69e5a37566f210dec3f4c3326d272a012efe8671931a0c2d9a27df7bcab3", + "size": 17782097 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.14.0a5%2B20250311-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "e4a721c94f296b0dd35bc88b464c27d3982c0d02d943c6998f2ec5745fd94fbe", + "size": 78662833 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0a5%2B20250311-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e1fab4f860fa9c9533f05d7cae431463d74350adcdd2195e877d31aa370daf74", + "size": 21460363 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.14.0a5%2B20250311-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "165cfe7b2f1aff131d4c0d9e8bd365811f81a41bf2ba526242ac48858e1df933", + "size": 43224236 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0a5%2B20250311-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "a2d920b3b42a48e379d1214fd4b6ba6336b4ca80346155844e49423e81211711", + "size": 21245722 + } + } + }, + "3.14.0a6": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250409", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.14.0a6%2B20250409-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "fd34267a9923a09c03ae7e3626b5681b58bccecaaf3c1cfec6d770c8b110a8be", + "size": 15788392 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0a6%2B20250409-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "ee5a7b630874554bae30b5f11bee55ef7ffd093f691cb7161cae34874c00ecf0", + "size": 15710073 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.14.0a6%2B20250409-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "15b366906fda248928434e44fa3d2a9b7928944165512537d778a5e98b12ec96", + "size": 16182544 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0a6%2B20250409-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "a187d2c50f43095c8f51b34babae1c291ee1a42d6777ce8d8bcd58517652f987", + "size": 16118156 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.14.0a6%2B20250409-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "03fd176e1e14f21a50c970b883500226919a566ca5b1d27dec06a2dd68102d3e", + "size": 25375979 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0a6%2B20250409-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "498849c92f28b6f1b1c5dfe83587660eff5ac574dc7974bada9ce68634ded6f1", + "size": 17833018 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.14.0a6%2B20250409-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "d47becae984d63578bf04abfb8f0a545a5682c2b7b64ff32b90c3662bec2b06d", + "size": 77118769 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0a6%2B20250409-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a777bbf17e251cec96d14c3ab70f67cce752930a5542b8e04ce22e892470c594", + "size": 21492623 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.14.0a6%2B20250409-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "72f40ada379c9c480fb429105017b44266c1dc6679b73cc0c5159d5dc1f0b8a6", + "size": 43468026 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0a6%2B20250409-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "1efb04f1441cab2f848cada6aad59ec396d5721dc5c426f2f4823df0345ca96a", + "size": 21296477 + } + } + }, + "3.14.0a7": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250529", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.14.0a7%2B20250529-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "e4a245bcb9c15432bcf9fd55120bf1b2ac8d1fbab9c3c5729ed4a30e5bcc6c8b", + "size": 15887315 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0a7%2B20250529-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "c0a9a1f6357ebaa1dac4c95b030e28e4661c7290712ffe45283b012548cfc7e8", + "size": 15811317 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.14.0a7%2B20250529-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "8cb7e9a65fdcf96c1a81b8339185d3f1bc540295f2d8770fcbfa21c1d099374a", + "size": 16237023 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0a7%2B20250529-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "40723e37765edfc2f674b7c9d32eba49cf92d76ed23f1179c98df467f1ed9f87", + "size": 16172001 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.14.0a7%2B20250529-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "2980d0e0869e5e2e59474f8707195da16cf6d92d88b13f04881d3144ec917628", + "size": 42357506 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0a7%2B20250529-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b43063632403f8f788e6257efa98e94fb322e44aea3937de61900f4e912809bf", + "size": 26900872 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.14.0a7%2B20250529-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "ea36f88904b6b8f1a8d0421f2a2f8ae053b4477c9412b4b8d3723aa7a7b34e55", + "size": 122545314 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0a7%2B20250529-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b9ab2566b1da8bc048e92332c33439cd57b251bd3bef19f3f7408cb9297b2b06", + "size": 35618376 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.14.0a7%2B20250529-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "856def39a636cb723f2b3bac94c2c3979d91923b134a68c2a0ae3d1a6a9a6071", + "size": 43462906 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0a7%2B20250529-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "afd79938fd5ad5e53d10999ec0cb5961cce901c08684ce053f9d7c4753fac93e", + "size": 21322056 + } + } + }, + "3.14.0b1": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250604", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.14.0b1%2B20250604-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "59d9deff2eadecaedc67d0da6f51ef6a3110267393beac6ecbf04330d56df09c", + "size": 16004953 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0b1%2B20250604-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "1d118780ec1b610ca7520379f5b6c68314f0672145348e06bb29668fe2967657", + "size": 15924246 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.14.0b1%2B20250604-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "b69c38b6218b739e8bf34819bb9babd6bce111ec873a1a87c39b0e4332589b34", + "size": 16364062 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0b1%2B20250604-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "bedcafacb966f0259b32944388039ad906e649588f91c9fa8892c8c58eb10ed8", + "size": 16298569 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.14.0b1%2B20250604-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "8efcb7eb03e762cf321c91ecd5e4f446dc435cc5410e436f8a22c04a4fb6d7cd", + "size": 44398897 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0b1%2B20250604-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a0ddc9b714fa7236869190308852d0ce3a9292b6dcfa14544cceef6b2e9a478a", + "size": 27023530 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.14.0b1%2B20250604-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "2e1aa15e67f839d1faac26fedc9f80463477d3bf65d28b62bd225ea582cb3646", + "size": 127945733 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0b1%2B20250604-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7239666ecd4c205dbbb9f2defa52f91cf2a59d2e1cd5a041b5e9e0c8c07af1a0", + "size": 35979959 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.14.0b1%2B20250604-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "69feefb7f31574582352974a5a7dee40700ff09754c979dda137fde7c6481b56", + "size": 44790184 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0b1%2B20250604-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "ad78cd205c5b71bd8790e4eee5a8775b5cbf5b6f9593b5e4f09dc0ed0591b216", + "size": 21652324 + } + } + }, + "3.14.0b2": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250612", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.14.0b2%2B20250612-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "35c02e465af605eafd29d5931daadce724eeb8a3e7cc7156ac046991cb24f1c1", + "size": 16041447 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0b2%2B20250612-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "af7fb49f2c00905ce822fde97d9437d34a4dd3786575a1daaad1e51a44fb21dd", + "size": 15965259 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.14.0b2%2B20250612-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "5144890b991e63fb73e2714c162c901c3b6f289ae0ef742df3673ab9824c844a", + "size": 16417430 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0b2%2B20250612-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "dea974e250afcf4c28936e35af5025072295a711d72fa65eed0679170c70c278", + "size": 16347282 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.14.0b2%2B20250612-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "8c877a1b50eb2a9b34ddac5d52d50867f11ddc817f257eba4cbbc999a9edf2ea", + "size": 44466910 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0b2%2B20250612-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "6cbb5d50fa2c34b76b98ce68e38b5ae1fee4712faf4dc7c9db0127bba91269f1", + "size": 27036872 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.14.0b2%2B20250612-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "1c73b90a8febbd36fc973d7361a1be562e88437d95570721b701f03e59835600", + "size": 132349175 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0b2%2B20250612-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "b922eda4390cc5cb737e32ea8d466efeaee2d693a5f2e8a3269ccc2b5710c328", + "size": 36069561 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.14.0b2%2B20250612-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "903cfb0ae1766a572dcf62835ef24d3250a512974dcf785738ac0d6c06c9db5b", + "size": 45034006 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0b2%2B20250612-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "4d7e811323a839d9ce3a6a38b36422f89bb2c0f136a040055aa6002bade12b79", + "size": 21673879 + } + } + }, + "3.14.0b3": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250702", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.14.0b3%2B20250702-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "14af7a0c0a50f82cf75f79f4c02dc31c73c74032930a8337f83f3ae3bee4660f", + "size": 16068244 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0b3%2B20250702-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "0b948f37363193fcf5e20c2e887183467907f1b6d04420fc5a0c0c7c421e7b12", + "size": 15989822 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.14.0b3%2B20250702-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "8e9d640e5e7c49f8c67dfd2330bdd814f4c5de685abefbe91c639c0e0844c2bd", + "size": 16443801 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0b3%2B20250702-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "47f21cf35481e5ba8e4e6b35c4dd549b0463d0f1dc24134d6e7fcc832a292869", + "size": 16367281 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.14.0b3%2B20250702-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "013e2081c3e7e61932210ede84c9f05a4f6533f807287bab141d8abe77087ffd", + "size": 99576184 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0b3%2B20250702-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2935079dd417d8940955f0b083be698ae27a1d65f947614c36ce5e4ea509c812", + "size": 29594193 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.14.0b3%2B20250702-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "00328c48cc07076a5b083575654761cdb07bc8b3bba864d3a225062722485bac", + "size": 132640287 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0b3%2B20250702-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "17643efc55b6b68b4fa7b3a5e43abb0ea31b4f03942e2d17bd04c5cd5be52c52", + "size": 36129230 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.14.0b3%2B20250702-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "000fbc010e844bcd64330badb295da7b5b08b427357f463afc7e600988f7ecc6", + "size": 42492178 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0b3%2B20250702-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "62dc6ff21cbbf2c216f1b9f573ed8e0433c0f7185280a13b2b2f3a81ac862b90", + "size": 20812639 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.14.0b3%2B20250702-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "cdab7856e2495ab4ed666354e9391435c8e45512e841ef8452da69a6e96caa96", + "size": 44964576 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0b3%2B20250702-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "5b5ef4c03b4e2aaab389f10b973914780d76bd82eeaeb3c305239a57aba2e367", + "size": 21704920 + } + } + }, + "3.14.0b4": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250712", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.14.0b4%2B20250712-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "4abb473411aa6868c04b300d6ff1fb8ad2e92214de2a2bc9129b677a2619ff40", + "size": 16277800 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0b4%2B20250712-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "94b80254a7e50dd2d82d323a0bffdc59772b2f04b0f0c044bc4d56d696249eb2", + "size": 16204141 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.14.0b4%2B20250712-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "ea6f6f51df63f6fb46f367d889f6a47dd8db0db7a3bc7dc3e7f41c99cde4ee75", + "size": 16591601 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0b4%2B20250712-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "2155f60b2a8a1448b2c4852a27887be2e9fe8e910bac1a75b342e44884a191b5", + "size": 16525805 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.14.0b4%2B20250712-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "9e582dd6396440eeebd288ed7afc031aba318c9e21d4a938b41c94c6c335422c", + "size": 93107060 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0b4%2B20250712-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "f76fb1a88e722f9cae8b82b9851b736968582527d8a1212ab3b918b2012ce0a6", + "size": 30034804 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.14.0b4%2B20250712-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "8435b6fccee66fe899058ecd72855ccd07aec799b46dadbc3687108120fe45eb", + "size": 123326124 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0b4%2B20250712-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3d07868b329c7c9b7ae5a52af35c27d0b20b5a7f6f574a3bedb5836b4bb337d7", + "size": 36488556 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.14.0b4%2B20250712-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "61bbbf730ad0a73e7f1756af150b64cf3f3b35c80ab9d795d003e4b8684a1f00", + "size": 42548304 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0b4%2B20250712-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "61bef0ff22c3117795c55d5e8e2c87956a94fbb4725e03231f360b7c68ba5358", + "size": 20817831 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.14.0b4%2B20250712-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "78c6a95f3e282c19d1d7355fa6cf623fc88d5655131577ba8ae5a1a26f93d569", + "size": 45110190 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0b4%2B20250712-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "8255b31a40867eb52ff1a2e476f56c697a717e6193d313413c788b0fbdd28a3c", + "size": 21710496 + } + } + }, + "3.14.0rc1": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250808", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.14.0rc1%2B20250808-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "016b9eb7c6c41d358a095f52203297812a566376b1e4372571b850f621dc720d", + "size": 16340701 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0rc1%2B20250808-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "6182dc909a7879ac64e2211edab996d4a0d9ec2d7754b75180704b8d82acaf1f", + "size": 16254506 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.14.0rc1%2B20250808-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "a74da55830354eb13c5e1fd12bb9b0b624ed0daeafec48444eda86b21476e4c8", + "size": 16693984 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0rc1%2B20250808-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "82ff72095cf6b9e918b2c4e7f8d7dd6c859b5edc4b6f8f1e54ff95799ff295c5", + "size": 16622366 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.14.0rc1%2B20250808-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "bfa5cb2f56032f4ed2c105f5b3b59ea1809672cb74b453e4450399517a594137", + "size": 93344926 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0rc1%2B20250808-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "7f43179ca8ee501d856c0d87a8d29b1e7a7d38ba52e9c5068cf3ce9bda592fbf", + "size": 27679053 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.14.0rc1%2B20250808-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "644028c49cdd9d082274f7265857bc5b5bb4eea8c3e58187e5b3ebb74de9ad3a", + "size": 124166726 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0rc1%2B20250808-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "88d26f52a5e47d5c2a10dc7b8aca49444f2dfeef16fa7ad4247b1b6f49065e3a", + "size": 33571460 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.14.0rc1%2B20250808-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "631c007e1b90dfc9f22d05d4f4e5ef9f70bfa01b675a2bd8590994369746f852", + "size": 43414195 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0rc1%2B20250808-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "479e4e344ca06c65bd2e099b37f8f057d24bb8f5228b6509d8938b7998b74060", + "size": 20835095 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.14.0rc1%2B20250808-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "71d7fe086604835e5ebd38b45829c1e7ce38fb8f5399d287d219f930cd6efdc1", + "size": 45163245 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0rc1%2B20250808-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "10defe39436d1aa0b7b741756cdcb6f70833e941682f2cce33c53b293ecac21e", + "size": 21716545 + } + } + }, + "3.14.0rc2": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250902", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.14.0rc2%2B20250902-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "5ef545d4c35114ed324f701fc49eac64122ef9287b59983dfb0a7564c3b2324e", + "size": 16171103 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0rc2%2B20250902-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "f58bd726e160260f5cc4701a423c5187ad7c4442d83f1b20f199e375dba3f27f", + "size": 16082180 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.14.0rc2%2B20250902-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "916438561d62f4d79e446295a91b7d3c386e7cdf161946981da1250015e8422e", + "size": 16593698 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0rc2%2B20250902-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "98b07bb662cda43f85a77347c1e61eed5fa782f54e4730969a46a0e3512e5ea2", + "size": 16520635 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.14.0rc2%2B20250902-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "973a718fefbdde88f647911d9f20da39c66186342e4fda97d55e348bcc44bc45", + "size": 93268268 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0rc2%2B20250902-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "000ba302cc47c03347ca9a06f8cc0b4537a649bc9f8211391ddf30ceaf4500b4", + "size": 27385267 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.14.0rc2%2B20250902-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "d55642f84bf9f5ab9a36bc97bccf94c1eea223d6ae16b54b8265b374ab0d7019", + "size": 123831808 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0rc2%2B20250902-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "fefb6884a6a676c2c215d63e121a829c27d8f09e1309f603ec35ac0b79cc3272", + "size": 33385272 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.14.0rc2%2B20250902-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "a560a1dfe571d747c233b23c6c62e06b7d0a764ea26d690f95409ac992f6f430", + "size": 43462939 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0rc2%2B20250902-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "ca42b771aac146b72088a247387c7508dc13252ea805b5694f027227238e3d8b", + "size": 20762926 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.14.0rc2%2B20250902-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "0fb2fe31aab52ca7a7f5104540ccfed7af438195a8cc6941940d3e60882e40ae", + "size": 45194171 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0rc2%2B20250902-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "eb605a9d5f167b6b727b41b0aca37bc145bec8c3020ce822c501f0f4be3ac2dc", + "size": 21646888 + } + } + }, + "3.14.0rc3": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250918", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.14.0rc3%2B20250918-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "82dec28092291c2b6795fba87d60e6a91960965678d18566bc4cc033f559f553", + "size": 17092287 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0rc3%2B20250918-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "d7909e7182861cbf9e9c7af534f2fd7da7cfd7a2b807c9cf164e0c4d3197238c", + "size": 17010009 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.14.0rc3%2B20250918-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "e5babbd616a3effc6af346a0706a84b2177b9be273908da884b3f255f5b31af9", + "size": 17064217 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.14.0rc3%2B20250918-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "42c2b9ffdf350d0bcffd1d379885994d713fa48f7f34562433d7eaf9efbf9c8b", + "size": 16997351 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.14.0rc3%2B20250918-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "8a3379f2d712b4ddebc1687ec13f8139543eb8a8577740c7b540f2c339b8ff7c", + "size": 95199007 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0rc3%2B20250918-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "388004c4f6d13a7f250e3207c99d501e9d39aac3fac1bd553288baf6aa458653", + "size": 29405455 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.14.0rc3%2B20250918-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "d8cffac5b150dad22b9b5553dccebedb7a4e276f482fd21411d298dbd85bca8e", + "size": 125183332 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.14.0rc3%2B20250918-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "9273705486ea0be509b78f2a555b9c2942a32b93f2a1a5532e7340194a2ce41e", + "size": 34660231 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.14.0rc3%2B20250918-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "8a2fbec726f393417fff03d47dfd8b6a696c74fcf5afe12caf9fcb6284321c60", + "size": 45628545 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0rc3%2B20250918-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "119b5c4ce04dafce3c7347ba90584be441d8b7a6cadd5f45b91ad0a8dd56ffaf", + "size": 21215727 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.14.0rc3%2B20250918-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "23ad8fc0196211e30bee7025481f146f90092a05bbbf046479d7d0281aa1d6d7", + "size": 48325394 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.14.0rc3%2B20250918-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "05e593a278c1b6d3c872d909e8031eaedf7bd8e7df9de2bfea857b7230f0b4c5", + "size": 22206291 + } + } + }, + "3.14.1": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251202", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.14.1%2B20251202-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "cdf1ba0789f529fa34bb5b5619c5da9757ac1067d6b8dd0ee8b78e50078fc561", + "size": 17568420 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.14.1%2B20251202-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "c811f87c17fd95712ffaa42b967b36aeffe465449c3255758bddf344505f5d9b", + "size": 17490911 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.14.1%2B20251202-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "f25ce050e1d370f9c05c9623b769ffa4b269a6ae17e611b435fd2b8b09972a88", + "size": 17575431 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.14.1%2B20251202-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "b2f7f169f1637c93b26b17131b56da21f066079a7f65d6a4369ef8d11e800572", + "size": 17503766 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.14.1%2B20251202-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "5dde7dba0b8ef34c0d5cb8a721254b1e11028bfc09ff06664879c245fe8df73f", + "size": 94954844 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.14.1%2B20251202-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a089ab3c84441d0b1b18c550a83f65f7aff4c0a43143b877b440b3491c6f33cd", + "size": 29564107 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.14.1%2B20251202-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "a72f313bad49846e5e9671af2be7476033a877c80831cf47f431400ccb520090", + "size": 123290757 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.14.1%2B20251202-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "8e0f6383e07eb331c67a57632de8c00d5709423491f105ff9aa802a5610d59e4", + "size": 35270850 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.14.1%2B20251202-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "19129cf8b4d68c4e64c25bae43bca139d871267b59cf7f02b9dcf25f0bf59497", + "size": 45776287 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.14.1%2B20251202-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "7874ad24a40d9319178fe540adcf9c76660eabe7dd29b83df80b8b3eaf178794", + "size": 21248589 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.14.1%2B20251202-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "cb478a5a37eb93ce4d3c27ae64d211d6a5a42475ae53f666a8d1570e71fcf409", + "size": 48805532 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.14.1%2B20251202-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "34bcfc74686ccc84b6dc8b232fc13a6c09d284510f3332fc757fe7ea3c804229", + "size": 22266506 + } + } + }, + "3.14.2": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251217", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.14.2%2B20251217-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "a603229a773a65a049492bb3a6e037c8e68e45624d937454cd90971d9f9fc96a", + "size": 17570114 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.14.2%2B20251217-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "c5b986f976444eed5846470bbeffe07c2d349739efbf12ba48fbff0d098eba82", + "size": 17490633 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.14.2%2B20251217-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "03c7fb7f4e7ea48d4bd5b1b9ffea87480f7013fafd20ba010b1d953e23d52fcc", + "size": 17572279 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.14.2%2B20251217-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "4faeb2339a3b0de6d0b0a31e3fd71d362adcafd17e92dd6fdba5988aab924f88", + "size": 17504814 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.14.2%2B20251217-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "dab8067b899e6095a896420a611dbfcf86ec160b9048e11e090d2fe1bcddf2ab", + "size": 94476572 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.14.2%2B20251217-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "676bf42aefcbd8b5d0120956b0c3084d106c7b412b1924252d11daa8853b3e9e", + "size": 29567155 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.14.2%2B20251217-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "5770ce51ee3010cc93951c4f78a84a6d284a129484d040ac23cb762f4005da44", + "size": 125033185 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.14.2%2B20251217-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "4eb8731e2dbc9644dca24e71a9e7615f7fb04720342ea65f3c30ea3a6a00b792", + "size": 35317052 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.14.2%2B20251217-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "c5dbbb302912418e35f66d66f92ad9709b23031e9520f2d4189da740049d079c", + "size": 45739426 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.14.2%2B20251217-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "d6925bf93834c9aee9af23fd90bb0aa7d3c3bb9410aa94b1442915b74b24fe67", + "size": 21247964 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.14.2%2B20251217-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "544d24ba19fff2b8d3dc968ed02487f226ef1898605774232f75004a6976b9d1", + "size": 48916660 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.14.2%2B20251217-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "7b277f5eb5c3846a5034d7e7f492fa92d8dcc8283e0d1bc6e70d4476229e3763", + "size": 22266313 + } + } + }, + "3.15.0a1": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251031", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.15.0a1%2B20251031-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "3acf7aa3559b746498b18929456c5cacb84bae4e09249834cbc818970d71de87", + "size": 17493830 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.15.0a1%2B20251031-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "882ebb86b9ae501edd6fa27836a903fc324fd16ee90116ab0c14e1a99e89bd20", + "size": 17412840 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.15.0a1%2B20251031-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "0ab19d3ac25f99da438b088751e5ec2421f9f6aa4292fd2dc0f8e49eb3e16bdf", + "size": 17385910 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.15.0a1%2B20251031-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "ecb616e8f5ca771a0c4292fc906c04781286a7ae55b96dc06ec0f631f20f42c1", + "size": 17311516 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.15.0a1%2B20251031-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "d55c2aeece827e6bec83fd18515ee281d9ea0efaa3e2d20130db8f1c7cbb71c6", + "size": 98084017 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.15.0a1%2B20251031-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a19b9b1fa2ff57508570c35ac8110a64d43732d330038c1f50fee1b9ae9579cc", + "size": 29424187 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.15.0a1%2B20251031-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "1f356288c2b2713619cb7a4e453d33bf8882f812af2987e21e01e7ae382fefba", + "size": 128548840 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.15.0a1%2B20251031-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "bb83de5324e51b0701f9fc81f511f3b15f6cece1f151d00d0aa76836e193f448", + "size": 34682250 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.15.0a1%2B20251031-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "1508bcd7195008479ed156aad3afbb3a3793097ed530690f0304a8107f0e53e8", + "size": 46323663 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.15.0a1%2B20251031-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "467dfa34e3b0a2c5f4180317eb46065ee772a56e16150f516211e399f647b546", + "size": 21526022 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.15.0a1%2B20251031-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "5f5d6bec2b381cfc771c49972d2a6f7b7e7ab6a1651d8fb6ef3983f3571722b3", + "size": 49484920 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.15.0a1%2B20251031-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "f465e6df3f5cc0495a15cb60a416658bd4d82ca2210ca8f1981640722ca720aa", + "size": 22545864 + } + } + }, + "3.15.0a2": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251209", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.15.0a2%2B20251209-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "5851f3744fbd39e3e323844cf4f68d7763fb25546aa5ffbb71b1b5ab69c56616", + "size": 17843363 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.15.0a2%2B20251209-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "2be41b1dc476feeaab9fe5590727aa39c0d2834af30fe40691f17be2dd2ea518", + "size": 17754576 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.15.0a2%2B20251209-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "cee576de4919cd422dbc31eb85d3c145ee82acec84f651daaf32dc669b5149c9", + "size": 17861052 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.15.0a2%2B20251209-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "a541b308fee6c48a0f634ba6062d0199112dd68c44b476862a01a8dcc05a32cf", + "size": 17786663 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.15.0a2%2B20251209-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "17ba65d669be3052524e03b4d1426c072ef38df2a9065ff4525d1f4d1bc9f82c", + "size": 97392886 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.15.0a2%2B20251209-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "dbf308295cb6c863cc1802790885254d372a14f7dd14c53abbffc5c6e6e64b15", + "size": 29909140 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.15.0a2%2B20251209-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "58addaabfab2de422180d32543fb3878ffc984c8a2e4005ff658a5cd83b31fc7", + "size": 130628750 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.15.0a2%2B20251209-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "43f43b5a2ddec148a107ee1d28b6212643da34b2f9ff449eb599b852fd86414e", + "size": 35714106 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.15.0a2%2B20251209-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "39bc2fcac13aeba7d650f76badf63350a81c86167a62174cb092eab7a749f4a5", + "size": 46496821 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.15.0a2%2B20251209-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "0e6a84e7c7a394662745231db029d2dcb8b525bb592842e35521219c6146ac3e", + "size": 21596520 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.15.0a2%2B20251209-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "e538475ee249eacf63bfdae0e70af73e9c47360e6dd3d6825e7a35107e177de5", + "size": 49589348 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.15.0a2%2B20251209-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "27bab29b09730af8bb8a8164abb2c984885fc69966482e8a9b36ea555aa23ab6", + "size": 22632415 + } + } + }, + "3.15.0a3": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251217", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.15.0a3%2B20251217-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "801eb94d6f6be385be1e6b53796959036c8775a8d5d8806cd7a12da8c7161545", + "size": 18134734 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.15.0a3%2B20251217-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "aba4f29a8828d4a86d64d38034bd30cc9afd07783026da386c7333e505bde049", + "size": 18045308 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.15.0a3%2B20251217-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "203c945cda1549e18909ec4b86fba1ed9c30d719f3b43b48a9d0fa9060d68e49", + "size": 18096239 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.15.0a3%2B20251217-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "daf5418864d2ac1054cd7315be625c41a74c2b30693fdd448e44d378a4a6f246", + "size": 18020553 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.15.0a3%2B20251217-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "75b12c630fd8baf7029b2f3cafd94caa33cec31a1e80697b1463ab41d4c86d91", + "size": 98957882 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.15.0a3%2B20251217-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3ac0cb800759a359f830c2a1e035355262e0ac7d5bfe48b4c524285b1a3f542b", + "size": 30249999 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.15.0a3%2B20251217-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "f843c007b44149d5f1245f5a80680fd5573dbcaadb753651e531b2535c00738e", + "size": 131598345 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.15.0a3%2B20251217-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c6d1bf590f1f50705ed42c5ec27da93654ddc24542080989ea18a9b8468aceb0", + "size": 36081970 + }, + "windows-aarch64-msvc-install_only": { + "filename": "cpython-3.15.0a3%2B20251217-aarch64-pc-windows-msvc-install_only.tar.gz", + "sha256": "8c84625154ded858a287e55b8f293cf48297ae0d09a418a75e2e0638934552cd", + "size": 46804118 + }, + "windows-aarch64-msvc-install_only_stripped": { + "filename": "cpython-3.15.0a3%2B20251217-aarch64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "ea0f1bd6a9b0129297e2e609116948b594b01cc1793e1aca056a7b9743b3cdd4", + "size": 21849272 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.15.0a3%2B20251217-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "82c5a5a0a0e76825df9012c9386952618af9956bffaa9d67cf6388d2a29e47ed", + "size": 50005713 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.15.0a3%2B20251217-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "4cac37170050bb402eaabad84e7ceb4679ec8ffaf43fbea71d62651ca761dcd7", + "size": 22886272 + } + } + }, + "3.8.12": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20220227", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.8.12%2B20220227-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "f9a3cbb81e0463d6615125964762d133387d561b226a30199f5b039b20f1d944", + "size": 40042198 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.8.12%2B20220227-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "f323fbc558035c13a85ce2267d0fad9e89282268ecb810e364fff1d0a079d525", + "size": 40287093 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.8.12%2B20220227-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "5be9c6d61e238b90dfd94755051c0d3a2d8023ebffdb4b0fa4e8fedd09a6cab6", + "size": 49945411 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.8.12%2B20220227-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "4658e08a00d60b1e01559b74d58ff4dd04da6df935d55f6268a15d6d0a679d74", + "size": 40692045 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.8.12%2B20220227-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "924f9fd51ff6ccc533ed8e96c5461768da5781eb3dfc11d846f9e300fab44eda", + "size": 46170503 + } + } + }, + "3.8.13": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20220802", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.8.13%2B20220802-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "ae4131253d890b013171cb5f7b03cadc585ae263719506f7b7e063a7cf6fde76", + "size": 17586341 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.8.13%2B20220802-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "cd6e7c0a27daf7df00f6882eaba01490dd963f698e99aeee9706877333e0df69", + "size": 17973220 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.8.13%2B20220802-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "8dc7814bf3425bbf78c6e6e5a6529ded6ae463fa6a4b79c025b343bae4fd955a", + "size": 25074929 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.8.13%2B20220802-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "fb566629ccb5f76ef56d275a3f8017d683f1c20c5beb5d5f38b155ed11e16187", + "size": 26896334 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.8.13%2B20220802-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "f20643f1b3e263a56287319aea5c3888530c09ad9de3a5629b1a5d207807e6b9", + "size": 37139659 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.8.13%2B20220802-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "cdf90eb39e9e13d2c61c27a7e5431444dbd1943ab6d7b186d593b1d9d7db6d71", + "size": 42748614 + } + } + }, + "3.8.14": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20221002", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.8.14%2B20221002-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "6c17f6dcda59de5d8eee922ef7eede403a540dae05423ef2c2a042d8d4f22467", + "size": 17594484 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.8.14%2B20221002-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "3ed4db8d0308c584196d97c629058ea69bbd8b7f9a034cf8c2c701ebb286c091", + "size": 17976638 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.8.14%2B20221002-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "c45e42deee43e3ebc4ca5b019c37d8ae25fb5b5f1ba5f602098a81b99d2bc804", + "size": 25070295 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.8.14%2B20221002-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "4eb53bce831bf52682067579c09ccaccb6524dd44bd4b8047454c69b4817f4f0", + "size": 26912560 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.8.14%2B20221002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "1af39953b4c8324ed0608e316bc763006f27e76643155d92eae18e4db6fc162f", + "size": 37216052 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.8.14%2B20221002-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "afb6faa0bd70d31ef4a27c8c89bafc5f754f7ba23d1f65a29e0d25626234c55b", + "size": 42767928 + } + } + }, + "3.8.15": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20221106", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.8.15%2B20221106-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "1e0a92d1a4f5e6d4a99f86b1cbf9773d703fe7fd032590f3e9c285c7a5eeb00a", + "size": 17603392 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.8.15%2B20221106-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "70b57f28c2b5e1e3dd89f0d30edd5bc414e8b20195766cf328e1b26bed7890e1", + "size": 17985182 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.8.15%2B20221106-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "886ab33ced13c84bf59ce8ff79eba6448365bfcafea1bf415bd1d75e21b690aa", + "size": 25080793 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.8.15%2B20221106-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "e47edfb2ceaf43fc699e20c179ec428b6f3e497cf8e2dcd8e9c936d4b96b1e56", + "size": 26922922 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.8.15%2B20221106-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "2fdc3fa1c95f982179bbbaedae2b328197658638799b6dcb63f9f494b0de59e2", + "size": 37214265 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.8.15%2B20221106-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "f9c799b74f258fb520f7afae548f87b65c535ee14d885a64cc935a291436ae50", + "size": 42772349 + } + } + }, + "3.8.16": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20230726", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.8.16%2B20230726-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "7e484eb6de40d6f6bdfd5099eaa9647f65e45fb6d846ccfc56b1cb1e38b5ab02", + "size": 17227282 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.8.16%2B20230726-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "28506e509646c11cb2f57a7203bd1b08b6e8e5b159ae308bd5bb93b0d334bdaf", + "size": 17491676 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.8.16%2B20230726-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "9c6615931fd1045bf9f2148aa7dd9ce1ece8575ed68a5483a0b615322a43d54c", + "size": 23873980 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.8.16%2B20230726-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "b1f1502c3a13b899724dbd32bd77a973fa9733b932c5700d747fe33d5de9ac4f", + "size": 25685510 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.8.16%2B20230726-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "120b3312fa79bac2ace45641171c2bc590c4e4462d7ad124d64597e124a36ae7", + "size": 36676861 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.8.16%2B20230726-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "2deae224ceea75a4c799aac0e63601fed4cfb1eae25c3998e645e88637e90a3e", + "size": 42319962 + } + } + }, + "3.8.17": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20230826", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.8.17%2B20230826-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "c6f7a130d0044a78e39648f4dae56dcff5a41eba91888a99f6e560507162e6a1", + "size": 19247729 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.8.17%2B20230826-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "155b06821607bae1a58ecc60a7d036b358c766f19e493b8876190765c883a5c2", + "size": 19505723 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.8.17%2B20230826-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "9f6d585091fe26906ff1dbb80437a3fe37a1e3db34d6ecc0098f3d6a78356682", + "size": 25883841 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.8.17%2B20230826-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "8d3e1826c0bb7821ec63288038644808a2d45553245af106c685ef5892fabcd8", + "size": 27685259 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.8.17%2B20230826-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "6428e1b4e0b4482d390828de7d4c82815257443416cb786abe10cb2466ca68cd", + "size": 38708990 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.8.17%2B20230826-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "50b201a74e7a8b683ddb0dd368b6fe98665024e826c85d3c6d0e272e6051dd5e", + "size": 44340648 + } + } + }, + "3.8.18": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240224", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.8.18%2B20240224-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "4d493a1792bf211f37f98404cc1468f09bd781adc2602dea0df82ad264c11abc", + "size": 19717855 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.8.18%2B20240224-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "7d2cd8d289d5e3cdd0a8c06c028c7c621d3d00ce44b7e2f08c1724ae0471c626", + "size": 20040915 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.8.18%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "6588c9eed93833d9483d01fe40ac8935f691a1af8e583d404ec7666631b52487", + "size": 26807467 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.8.18%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "5ae36825492372554c02708bdd26b8dcd57e3dbf34b3d6d599ad91d93540b2b7", + "size": 28681831 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.8.18%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "dba923ee5df8f99db04f599e826be92880746c02247c8d8e4d955d4bc711af11", + "size": 39011065 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.8.18%2B20240224-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "488de51568399d64116c30abc497cd5a905f74582af139078ffd3d511d152a13", + "size": 44911815 + } + } + }, + "3.8.19": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240814", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.8.19%2B20240814-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "f01a9d293e2bff8c4c62478d826a8bb46a197fabe21b26cb4db7a098e23dc9f1", + "size": 17531867 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.8.19%2B20240814-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "6a15ee2b507aed4d5b15fd1b66fc570aa49183f15aa6c412eccd065446f17d8e", + "size": 17417243 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.8.19%2B20240814-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "e13c7afca48e8ef64b38261567bb3b496276d097435d1404636f335447c992c3", + "size": 17840971 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.8.19%2B20240814-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "1a24263b039c1172bd42d74a5694492f3e3dbe4d3e52a1e7cc2856fee7dbee4a", + "size": 17735922 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.8.19%2B20240814-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "cfa765bf74d034ec45624e3a56f9560d09ca2d4f306aeabaa165c96a1d93c7cb", + "size": 24598278 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.8.19%2B20240814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "202211923850303f521146ee1831642aaf357ffeeadbe13a0a91884317227528", + "size": 19759196 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.8.19%2B20240814-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "9ebb4d3ff993e977c5f2c043369024be8429447cee67a16e7d4a84f03064116a", + "size": 26552908 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.8.19%2B20240814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "0f1579dbb01c98af7a12fef4c9aa8a99d45b91393f64431f5de712f892bc5c0b", + "size": 20850693 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.8.19%2B20240814-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "5775cfcfa009c47b6d9b7029d349b5c5b7acc03658319a768e70c5387465a864", + "size": 36683770 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.8.19%2B20240814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "89d238b125cd7546b7d0cbd7f484a438d2c2f239c15c9b38ec3c62b1f343a6ca", + "size": 21556017 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.8.19%2B20240814-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "5775cfcfa009c47b6d9b7029d349b5c5b7acc03658319a768e70c5387465a864", + "size": 36683770 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.8.19%2B20240814-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "89d238b125cd7546b7d0cbd7f484a438d2c2f239c15c9b38ec3c62b1f343a6ca", + "size": 21556017 + } + } + }, + "3.8.20": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20241002", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.8.20%2B20241002-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "2ddfc04bdb3e240f30fb782fa1deec6323799d0e857e0b63fa299218658fd3d4", + "size": 17542119 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.8.20%2B20241002-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "30ba44af64e599bde7307908393374bdcd99e185bf9b3c9de3f697f3fbe6bf8f", + "size": 17424339 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.8.20%2B20241002-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "68d060cd373255d2ca5b8b3441363d5aa7cc45b0c11bbccf52b1717c2b5aa8bb", + "size": 17845858 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.8.20%2B20241002-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "375b6eead6c852cabbf3ccfd43dc4f6dd4c36381bf74c9a7910acb839fd5c57f", + "size": 17738861 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.8.20%2B20241002-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "9d8798f9e79e0fc0f36fcb95bfa28a1023407d51a8ea5944b4da711f1f75f1ed", + "size": 24599691 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.8.20%2B20241002-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "75a187ebfab81096e3f3d91d70c1349e64defbdfb0e8a067cb5233d017655e31", + "size": 19762577 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.8.20%2B20241002-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "285e141c36f88b2e9357654c5f77d1f8fb29cc25132698fe35bb30d787f38e87", + "size": 26553578 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.8.20%2B20241002-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "a3a75094545912d4e9413673441b3f0d2e58ce9b264477f910800148801ccf11", + "size": 20852474 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.8.20%2B20241002-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "41b6709fec9c56419b7de1940d1f87fa62045aff81734480672dcb807eedc47e", + "size": 36657361 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.8.20%2B20241002-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "ec2f723dcfbf09581578a716c05cc67823a43d77111e6dd9e0d1557ccc6dcbf3", + "size": 21562036 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.8.20%2B20241002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "41b6709fec9c56419b7de1940d1f87fa62045aff81734480672dcb807eedc47e", + "size": 36657361 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.8.20%2B20241002-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "ec2f723dcfbf09581578a716c05cc67823a43d77111e6dd9e0d1557ccc6dcbf3", + "size": 21562036 + } + } + }, + "3.9.10": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20220227", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.10%2B20220227-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "ad66c2a3e7263147e046a32694de7b897a46fb0124409d29d3a93ede631c8aee", + "size": 40999185 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.10%2B20220227-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "fdaf594142446029e314a9beb91f1ac75af866320b50b8b968181e592550cd68", + "size": 41228694 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.10%2B20220227-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "12dd1f125762f47975990ec744532a1cf3db74ad60f4dfb476ca42deb7f78ca4", + "size": 85992370 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.10%2B20220227-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "455089cc576bd9a58db45e919d1fc867ecdbb0208067dffc845cc9bbf0701b70", + "size": 51624807 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.9.10%2B20220227-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "c145d9d8143ce163670af124b623d7a2405143a3708b033b4d33eed355e61b24", + "size": 41982133 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.9.10%2B20220227-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "5bc65ce023614bf496a6748e41dca934b70fc5fac6dfacc46aa8dbcad772afc2", + "size": 47621344 + } + } + }, + "3.9.11": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20220318", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.11%2B20220318-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "cf92a28f98c8d884df0937bf19d5f1a40caa25a6a211a237b7e9b592b2b71c2b", + "size": 41570037 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.11%2B20220318-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "43889d1a424c84fb155e1619f062adb6984fbde80b6043611790f22bcbeec300", + "size": 41767821 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.11%2B20220318-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "0e50f099409c5e651b5fddd16124af1d830d11653e786a93c28e5b8f8aa470c4", + "size": 86614999 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.11%2B20220318-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "0429d5ceb095d5e24c292bf1a39208b88ae236a680ef8fa3e1830e3a1a7e8882", + "size": 52181358 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.9.11%2B20220318-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "0c529a511f7a03908fc126c4a8467b47e24a4d98812147e8e786cf59e86febf0", + "size": 42635110 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.9.11%2B20220318-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "bc47190f082e24fc6f122d5492b23a4868261fdca3941843541632d64039e809", + "size": 48245546 + } + } + }, + "3.9.12": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20220502", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.12%2B20220502-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "8dee06c07cc6429df34b6abe091a4684a86f7cec76f5d1ccc1c3ce2bd11168df", + "size": 41443679 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.12%2B20220502-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "2453ba7f76b3df3310353b48c881d6cff622ba06e30d2b6ae91588b2bc9e481a", + "size": 41592297 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.12%2B20220502-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "2ee1426c181e65133e57dc55c6a685cb1fb5e63ef02d684b8a667d5c031c4203", + "size": 86402456 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.12%2B20220502-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "ccca12f698b3b810d79c52f007078f520d588232a36bc12ede944ec3ea417816", + "size": 52175226 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.9.12%2B20220502-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "3024147fd987d9e1b064a3d94932178ff8e0fe98cfea955704213c0762fee8df", + "size": 42780309 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.9.12%2B20220502-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "15cefdc5c13fbb40823dbc71a3adc2eb727f86ab53d29d084de05e046658f365", + "size": 48375599 + } + } + }, + "3.9.13": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20220802", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.13%2B20220802-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "d9603edc296a2dcbc59d7ada780fd12527f05c3e0b99f7545112daf11636d6e5", + "size": 16730332 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.13%2B20220802-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "9540a7efb7c8a54a48aff1cb9480e49588d9c0a3f934ad53f5b167338174afa3", + "size": 17132546 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.13%2B20220802-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "80415aac1b96255b9211f6a4c300f31e9940c7e07a23d0dec12b53aa52c0d25e", + "size": 24521230 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.13%2B20220802-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "ce1cfca2715e7e646dd618a8cb9baff93000e345ccc979b801fc6ccde7ce97df", + "size": 26747628 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.9.13%2B20220802-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "b538127025a467c64b3351babca2e4d2ea7bdfb7867d5febb3529c34456cdcd4", + "size": 38273299 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.9.13%2B20220802-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "1bf930e9f2a9f15bcdb84807639f4935097526048ea963b0588fab2e5ffb902e", + "size": 44039924 + } + } + }, + "3.9.14": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20221002", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.14%2B20221002-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "e38df7f230979ce6c53a5bafb3a81287838e5f3892c40cd1b98a0c961c444713", + "size": 16734921 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.14%2B20221002-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "b7d3a1f4b57e9350571ccee49c82f503133de0d113a2dbaebc8ccf108fb3fe1b", + "size": 17138819 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.14%2B20221002-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "fe538201559ca37f44cd5f66c42a65fe7272cb4f1f63edd698b6f306771db1e9", + "size": 24525886 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.14%2B20221002-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "e63d0c00a499e0202ba7a0f53ce69fca6d30237af39af9bc3c76bce6c7bf14d7", + "size": 26761725 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.9.14%2B20221002-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "f111c3c129f4a5a171d25350ce58dad4c7e58fbe664e9b4f7c275345c9fe18a6", + "size": 38369373 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.9.14%2B20221002-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "299048f971fb7a5d3890cc25cca65abc10c1354c40ea26412eadb6e9c81912ca", + "size": 44044600 + } + } + }, + "3.9.15": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20221106", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.15%2B20221106-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "64dc7e1013481c9864152c3dd806c41144c79d5e9cd3140e185c6a5060bdc9ab", + "size": 16748283 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.15%2B20221106-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "f2bcade6fc976c472f18f2b3204d67202d43ae55cf6f9e670f95e488f780da08", + "size": 17148635 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.15%2B20221106-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "52a8c0a67fb919f80962d992da1bddb511cdf92faf382701ce7673e10a8ff98f", + "size": 24524876 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.15%2B20221106-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "cdc3a4cfddcd63b6cebdd75b14970e02d8ef0ac5be4d350e57ab5df56c19e85e", + "size": 26765574 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.9.15%2B20221106-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "022daacab215679b87f0d200d08b9068a721605fa4721ebeda38220fc641ccf6", + "size": 38364128 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.9.15%2B20221106-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "f8cff8da3e370a083c234a718c46733a07c923f944951b041c97243b09c01972", + "size": 44056888 + } + } + }, + "3.9.16": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20230507", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.16%2B20230507-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "c1de1d854717a6245f45262ef1bb17b09e2c587590e7e3f406593c143ff875bd", + "size": 16634170 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.16%2B20230507-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "3abc4d5fbbc80f5f848f280927ac5d13de8dc03aabb6ae65d8247cbb68e6f6bf", + "size": 16908516 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.16%2B20230507-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "f629b75ebfcafe9ceee2e796b7e4df5cf8dbd14f3c021afca078d159ab797acf", + "size": 23583066 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.16%2B20230507-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "2b6e146234a4ef2a8946081fc3fbfffe0765b80b690425a49ebe40b47c33445b", + "size": 25738357 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.9.16%2B20230507-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "cdabb47204e96ce7ea31fbd0b5ed586114dd7d8f8eddf60a509a7f70b48a1c5e", + "size": 38085449 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.9.16%2B20230507-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "77a1ea772f8705104d9b3f80dc50b67715940ffa666691ee5d294eba96d4c1b2", + "size": 43765597 + } + } + }, + "3.9.17": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20230726", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.17%2B20230726-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "73dbe2d702210b566221da9265acc274ba15275c5d0d1fa327f44ad86cde9aa1", + "size": 16312686 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.17%2B20230726-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "dfe1bea92c94b9cb779288b0b06e39157c5ff7e465cdd24032ac147c2af485c0", + "size": 16579635 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.17%2B20230726-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "b77012ddaf7e0673e4aa4b1c5085275a06eee2d66f33442b5c54a12b62b96cbe", + "size": 23259487 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.17%2B20230726-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "26c4a712b4b8e11ed5c027db5654eb12927c02da4857b777afb98f7a930ce637", + "size": 25409877 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.9.17%2B20230726-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "9b9a1e21eff29dcf043cea38180cf8ca3604b90117d00062a7b31605d4157714", + "size": 37751111 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.9.17%2B20230726-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "2ae31c9f2289298e2200af3883c034dd67da2b62b7d1179532cee0b1675dedc5", + "size": 43451815 + } + } + }, + "3.9.18": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240224", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.18%2B20240224-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "2548f911a6e316575c303ba42bb51540dc9b47a9f76a06a2a37460d93b177aa2", + "size": 16799720 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.18%2B20240224-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "171d8b472fce0295be0e28bb702c43d5a2a39feccb3e72efe620ac3843c3e402", + "size": 17132069 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.18%2B20240224-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "e5bc5196baa603d635ee6b0cd141e359752ad3e8ea76127eb9141a3155c51200", + "size": 24171241 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.18%2B20240224-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "0e5663025121186bd17d331538a44f48b41baff247891d014f3f962cbe2716b4", + "size": 26338278 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.9.18%2B20240224-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "a9bdbd728ed4c353a4157ecf74386117fb2a2769a9353f491c528371cfe7f6cd", + "size": 38127445 + }, + "windows-x86_64-msvc-static-install_only": { + "filename": "cpython-3.9.18%2B20240224-x86_64-pc-windows-msvc-static-install_only.tar.gz", + "sha256": "f0f835c6e15aec5eec2767876d1cc80d601521332e1be58cd599c0f67b718e85", + "size": 44088222 + } + } + }, + "3.9.19": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20240814", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.19%2B20240814-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "ddd57774886a66a71283559cdd39bd2cbfd756cbd996b19b0b3bdb842a2a4a81", + "size": 16689179 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.9.19%2B20240814-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "451582f8a6a8c15ef35a327afcdbf8d03b1ebba7192e90d850d092dac91f91c6", + "size": 16567269 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.19%2B20240814-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "9a5059869b715f8ae3c9fac215865275f1d0cbd075f506d7ca29faedda4e0533", + "size": 17013991 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.9.19%2B20240814-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "ebaf4336d0cbff4466c994d5bcaa92a38c91d06694d0cd675ec663259d5f37c7", + "size": 16899149 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.19%2B20240814-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "ebe8ce15d3617e69219e27dc2aa39932aee9df8bf85d2ad46b157d309e3fa5d3", + "size": 24043842 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.9.19%2B20240814-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "23d08f7f0bf151c2ea54b2c9c143bc710faf166ff74225b0f967fab1e2d7a151", + "size": 18910563 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.19%2B20240814-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "d21cf7bab25e2a8a1c873395bb7aa4f4b7ae533d2437d92f6496500c1e49625d", + "size": 26319893 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.9.19%2B20240814-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3b7d574b6bbf8303789a1d26b96a81dcca907381441ce15818c784e18d1db299", + "size": 19992626 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.9.19%2B20240814-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "cc0c5b42a227f594342c20a66c80f69940dd0f267c85292cc10040132a3161f1", + "size": 37831829 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.9.19%2B20240814-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "426da4d31e665b77dacf15cd89494a995ed634a9b97324bbef9cf36fcda4c8a9", + "size": 21846519 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.9.19%2B20240814-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "cc0c5b42a227f594342c20a66c80f69940dd0f267c85292cc10040132a3161f1", + "size": 37831829 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.9.19%2B20240814-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "426da4d31e665b77dacf15cd89494a995ed634a9b97324bbef9cf36fcda4c8a9", + "size": 21846519 + } + } + }, + "3.9.20": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20241016", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.20%2B20241016-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "34ab2bc4c51502145e1a624b4e4ea06877e3d1934a88cc73ac2e0fd5fd439b75", + "size": 16693658 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.9.20%2B20241016-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "41e9bb2d45e1a0467e534dafc6691b3d3c2b79fd9a564562f4c0c41eb343d30a", + "size": 16572425 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.20%2B20241016-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "193dc7f0284e4917d52b17a077924474882ee172872f2257cfe3375d6d468ed9", + "size": 17022883 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.9.20%2B20241016-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "440f4ebc651e707ed24d5dc68d3b0b2197e7fb369bb77685b1b539dbf30ab1e5", + "size": 16906013 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.20%2B20241016-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "1e486c054a4e86666cf24e04f5e29456324ba9c2b95bf1cae1805be90d3da154", + "size": 24050136 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.9.20%2B20241016-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "3742c9d6563527a003b12ac689c07e6965911ff89fd9cbbd3c17ac7bfb037d4a", + "size": 18915696 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.20%2B20241016-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "c20ee831f7f46c58fa57919b75a40eb2b6a31e03fd29aaa4e8dab4b9c4b60d5d", + "size": 26327299 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.9.20%2B20241016-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "44d9d016f9820f39e5bb542782557d46876b69d23d0a204eb2f367739da623e0", + "size": 19996785 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.9.20%2B20241016-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "5069008a237b90f6f7a86956903f2a0221b90d471daa6e4a94831eaa399e3993", + "size": 37826690 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.9.20%2B20241016-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "ce3779065ab824333e8d6d0a3d055d4073cdcc9a6e60abe24929023369f91512", + "size": 21848081 + }, + "windows-x86_64-msvc-shared-install_only": { + "filename": "cpython-3.9.20%2B20241016-x86_64-pc-windows-msvc-shared-install_only.tar.gz", + "sha256": "5069008a237b90f6f7a86956903f2a0221b90d471daa6e4a94831eaa399e3993", + "size": 37826690 + }, + "windows-x86_64-msvc-shared-install_only_stripped": { + "filename": "cpython-3.9.20%2B20241016-x86_64-pc-windows-msvc-shared-install_only_stripped.tar.gz", + "sha256": "ce3779065ab824333e8d6d0a3d055d4073cdcc9a6e60abe24929023369f91512", + "size": 21848081 + } + } + }, + "3.9.21": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250317", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.21%2B20250317-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "2a7d83db10c082ce59e9c4b8bd6c5790310198fb759a7c94aceebac1d93676d3", + "size": 16991255 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.9.21%2B20250317-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "8d1f0245ad4e8f4051fd0e54b438a749935dc697c1f10069781fb3d2cf38d670", + "size": 16946738 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.21%2B20250317-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "786ebd91e4dd0920acf60aa3428a627a937342d2455f7eb5e9a491517c32db3d", + "size": 17236532 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.9.21%2B20250317-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "92a6c2eb3ac2a38c5d8112acaf2163a42480ee2a378150bb2430fecd60a60543", + "size": 17203818 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.21%2B20250317-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "758ebbc4d60b3ca26cf21720232043ad626373fbeb6632122e5db622a1f55465", + "size": 24306633 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.9.21%2B20250317-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "d77520c53d108a8f2d0247f4da90f933916e964442c95f3a8d429bea6d874678", + "size": 19170159 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.21%2B20250317-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "6f426b5494e90701ffa2753e229252e8b3ac61151a09c8cd6c0a649512df8ab2", + "size": 26636486 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.9.21%2B20250317-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "837e50734904eba4f5a385c66c31b21d880582eba7912b3ea3d71e60e56f5a60", + "size": 20246006 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.9.21%2B20250317-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "5392cee2ef7cd20b34128384d0b31864fb3c02bdb7a8ae6995cfec621bb657bc", + "size": 38824029 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.9.21%2B20250317-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "749f706985170981ae95f9227b1b412ae8acb33d8455cf34df427b50b0edd882", + "size": 22206549 + } + } + }, + "3.9.22": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20250529", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.22%2B20250529-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "29a81900ec2f964538ddd404536da57c2aa816901713e4c03bca3678cae25cb2", + "size": 16992800 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.9.22%2B20250529-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "165cb2574669b3df82e40db8cb01bf4bbc9bb594dc09f9ae34d313ecd27ec7b8", + "size": 16947647 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.22%2B20250529-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "8a8276c460f98987d10595d6f3d1bbe6438a28ec4e989d8b02af1f8a7741497b", + "size": 17235666 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.9.22%2B20250529-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "fbb4a50fffb19d5e0d33fcd53474c6e70b578d20d80b4841bceb22df596208fa", + "size": 17200241 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.22%2B20250529-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "4020998db84d5400c1ee68fba1601d37678bf8bdbdf637b4eff5729e7cedda82", + "size": 37849306 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.9.22%2B20250529-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "42772f60d3c73be63e8b5abaa08ffc23e56458893fe5764ad7586e116a8f6acd", + "size": 27587772 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.22%2B20250529-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "b92e31b14691b0db0ad9a27951cf49da070e1cc25f11ce2c2c13d19f6862445a", + "size": 42383145 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.9.22%2B20250529-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "4aa03eb60fc6934e40d94ed9166f74ada85d851434667afcd152d46e1d995dbf", + "size": 29665497 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.9.22%2B20250529-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "e54811a7377c54f12c9e86b9e1efad412d2bc5b99c2f080ee2dda287d9c2b730", + "size": 38790345 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.9.22%2B20250529-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "10cb302c27a5e58ecd7aa2684a731e32924677114728626664c1cecce6e960a0", + "size": 22207026 + } + } + }, + "3.9.23": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251007", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.23%2B20251007-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "32e4b31e693c2830e20f80d115388328cd6a7cff21aa79777e0f2ada6c754ea6", + "size": 17918249 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.9.23%2B20251007-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "6e96402f4215867872f82c71c618909bc0ab89125ac6de361b8943f5c469d8cb", + "size": 17873904 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.23%2B20251007-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "fa5a920cc43e756c5fdd280f64058ea6454a560cafaa1301ab227b636dfe0066", + "size": 17760937 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.9.23%2B20251007-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "d6d55841cdac0f478845ada4c06361291a15c702c9cfb615fc9c6e72eb4358cb", + "size": 17723866 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.23%2B20251007-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "a6bc4ffe655ab01454a91b4d63c4eef298fe73b1b0bbafcdb31001f10070265e", + "size": 41355060 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.9.23%2B20251007-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "08d0955c5893fe12accc2bbe4ca3f60ec4fb2f82fbda4526ab53086f6257ca41", + "size": 28827545 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.23%2B20251007-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "c479843a04b0504bc6c571a44219f25e98b7e83463a75aee241b51b1cb2aee27", + "size": 40922764 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.9.23%2B20251007-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "c5024a3113844fb17e6d5ba85bc9b47435458212613c9c8da257131b0ffd4253", + "size": 28270548 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.9.23%2B20251007-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "921fc4d4d4c039dc824f270a9ae0f81d031f665efa16314b773df3223e72921e", + "size": 39482383 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.9.23%2B20251007-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "e4296336dd1ce338f79266ae4da78c8d330d7baeced3c1f4615e6810a9e8fd8f", + "size": 22214849 + } + } + }, + "3.9.24": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251028", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.24%2B20251028-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "73a95cd45a2c1fb95ea4e4e9b9bb5bdec5e1533ce7e34b606c2428e31847901b", + "size": 18471539 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.9.24%2B20251028-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "f7f88f7274041d24bb504d0a6ea906d5780f550ccc7b5b5e114df31252916395", + "size": 18430981 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.24%2B20251028-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "7fc066d92c3670175badd5e2c0733e440a8ed6ffb03e79c2e30fadc8608d5ef3", + "size": 18267560 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.9.24%2B20251028-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "0e19e2b9e3a781c9faa3c70718cae87d8ede0d03133229846b28a6500748d861", + "size": 18235779 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.24%2B20251028-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "fd4a3c806d8d100a96fc32f96d7f5f90d2ee0e0e021eb445f5c10ea52928a601", + "size": 42389953 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.9.24%2B20251028-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "e4d67722d13c955b3068ddf845c49b5c3cea9fcfa32a45fb1d1bc1aa6c87244d", + "size": 29790103 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.24%2B20251028-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "7e90b7e04edf1130f8302165376e04b5fc99f6f27381baddc3254514e338e5a8", + "size": 41905269 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.9.24%2B20251028-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "572c58cceb0f9a41a19ecf7199d90242b70fdc9369ee09e7d84cf6faf9bebff4", + "size": 29130973 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.9.24%2B20251028-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "f5e7d7ea8a817eb5d7a48169b46044bbaadaf29b5d883248a97d61e89296cdc6", + "size": 40269880 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.9.24%2B20251028-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "8a2cc61f90c28b69fcf5ad9c6bab6f8bf7182be716249751cd90741201b45bc2", + "size": 22722894 + } + } + }, + "3.9.25": { + "baseUrl": "https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20251031", + "files": { + "darwin-aarch64-unknown-install_only": { + "filename": "cpython-3.9.25%2B20251031-aarch64-apple-darwin-install_only.tar.gz", + "sha256": "87275619c2706affa4d1090d2ca3dad354b6d69f8b85dbfafe38785870751b9a", + "size": 18471356 + }, + "darwin-aarch64-unknown-install_only_stripped": { + "filename": "cpython-3.9.25%2B20251031-aarch64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "1dca0e37d56b7da3ec6a7d2f75cc72a2df3bd05751606300934b2dc6fd7026ea", + "size": 18428350 + }, + "darwin-x86_64-unknown-install_only": { + "filename": "cpython-3.9.25%2B20251031-x86_64-apple-darwin-install_only.tar.gz", + "sha256": "ace63cfe27a9487c4d72e1cb518be01c1d985271da0b2158e813801f7d3e5503", + "size": 18272672 + }, + "darwin-x86_64-unknown-install_only_stripped": { + "filename": "cpython-3.9.25%2B20251031-x86_64-apple-darwin-install_only_stripped.tar.gz", + "sha256": "ae1f63f9597a9c29c83a550bc34a6a0f6e1e4d380244557d803e6d3aace31069", + "size": 18236578 + }, + "linux-aarch64-gnu-install_only": { + "filename": "cpython-3.9.25%2B20251031-aarch64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "6112d46355857680b81849764a6cf9f38cc4cd0d1cf29d432bc12fe5aeedf9d0", + "size": 41536085 + }, + "linux-aarch64-gnu-install_only_stripped": { + "filename": "cpython-3.9.25%2B20251031-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "2f17406709afd0f07674dd3ce92a4b23c6adf8baac3f402555d192e0f3333bef", + "size": 28950748 + }, + "linux-x86_64-gnu-install_only": { + "filename": "cpython-3.9.25%2B20251031-x86_64-unknown-linux-gnu-install_only.tar.gz", + "sha256": "42834f61eb6df43432c3dd6ab9ca3fdf8c06d10a404ebdb53d6902e6b9570b08", + "size": 41108075 + }, + "linux-x86_64-gnu-install_only_stripped": { + "filename": "cpython-3.9.25%2B20251031-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz", + "sha256": "fe04e8b27bd69ca2144fc542428f8b9b5287b6a2e45516a4acfe2c2bc3102773", + "size": 28338050 + }, + "windows-x86_64-msvc-install_only": { + "filename": "cpython-3.9.25%2B20251031-x86_64-pc-windows-msvc-install_only.tar.gz", + "sha256": "4fb1b416482ce94d73cfa140317a670c596c830671d137b07c26afe8c461768a", + "size": 40204064 + }, + "windows-x86_64-msvc-install_only_stripped": { + "filename": "cpython-3.9.25%2B20251031-x86_64-pc-windows-msvc-install_only_stripped.tar.gz", + "sha256": "6898722ab4242e304daf1490e2545bee7e7befacef82a9362f9496c0ee616ece", + "size": 22722151 + } + } + } +} \ No newline at end of file diff --git a/python/python-test-env/core/intellij.python.test.env.core.iml b/python/python-test-env/core/intellij.python.test.env.core.iml new file mode 100644 index 000000000000..db46777e79f0 --- /dev/null +++ b/python/python-test-env/core/intellij.python.test.env.core.iml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/python/python-test-env/core/resources/intellij.python.test.env.core.xml b/python/python-test-env/core/resources/intellij.python.test.env.core.xml new file mode 100644 index 000000000000..a021f5e4bfee --- /dev/null +++ b/python/python-test-env/core/resources/intellij.python.test.env.core.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/python/python-test-env/core/scripts/generate_python_version_mapping.py b/python/python-test-env/core/scripts/generate_python_version_mapping.py new file mode 100755 index 000000000000..4b43685ef62b --- /dev/null +++ b/python/python-test-env/core/scripts/generate_python_version_mapping.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python3 +# Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. + +""" +Generator script for Python version to platform mapping. + +This script fetches releases from: +- https://github.com/astral-sh/python-build-standalone (Python 3.x) +- https://github.com/qnox/python-2.7 (Python 2.7) + +For each Python version, it generates a JSON file with: +- baseUrl: Base URL for downloads +- platforms: Available platforms (darwin, linux, windows) +- architectures: Available architectures (x86_64, aarch64, i686) +- libc: Available libc variants (gnu, musl) +- variants: Available variants (install_only, install_only_stripped, etc.) +- sha256: SHA256 checksum for each file + +The output is written to python-version-mapping.json file. +""" + +import hashlib +import json +import sys +from pathlib import Path +from typing import Dict, List, Optional, Set +from urllib.request import Request, urlopen +from urllib.error import HTTPError + + +class GitHubReleasesFetcher: + """Fetches and processes GitHub releases.""" + + def __init__(self, github_token: Optional[str] = None): + """ + Initialize the fetcher. + + Args: + github_token: Optional GitHub personal access token to avoid rate limiting + """ + self.github_token = github_token + self.headers = { + 'Accept': 'application/vnd.github+json', + 'User-Agent': 'Python-Version-Mapping-Generator' + } + if github_token: + self.headers['Authorization'] = f'Bearer {github_token}' + + def fetch_sha256(self, url: str) -> str: + """ + Fetch SHA256 checksum from a .sha256 file. + + Args: + url: URL to the .sha256 file + + Returns: + SHA256 hash string + + Raises: + Exception if the file cannot be fetched or parsed + """ + request = Request(url, headers=self.headers) + with urlopen(request, timeout=30) as response: + content = response.read().decode('utf-8').strip() + # Format is usually: "hash filename" or just "hash" + return content.split()[0] + + def fetch_releases(self, repo: str, max_pages: int = 10) -> List[Dict]: + """ + Fetch releases from a GitHub repository. + + Args: + repo: Repository in format "owner/repo" + max_pages: Maximum number of pages to fetch + + Returns: + List of release objects + """ + all_releases = [] + for page in range(1, max_pages + 1): + url = f'https://api.github.com/repos/{repo}/releases?per_page=10&page={page}' + print(f'Fetching {url}...', file=sys.stderr) + + try: + request = Request(url, headers=self.headers) + with urlopen(request, timeout=60) as response: + releases = json.loads(response.read().decode('utf-8')) + if not releases: + break + all_releases.extend(releases) + print(f'Fetched {len(releases)} releases from page {page}', file=sys.stderr) + except HTTPError as e: + if e.code == 403: + print('Rate limit exceeded. Consider using a GitHub token.', file=sys.stderr) + raise + + return all_releases + + +class AssetInfo: + """Information about a Python build asset.""" + + def __init__(self, asset_name: str, download_url: str, size: int, sha256: Optional[str], + version: str, release_tag: str, arch: str, vendor: str, os: str, abi: str, variant: str): + """Private constructor. Use create() factory method instead.""" + self.asset_name = asset_name + self.download_url = download_url + self.size = size + self.sha256 = sha256 + self.version = version + self.release_tag = release_tag + self.arch = arch + self.vendor = vendor + self.os = os + self.abi = abi + self.variant = variant + + @classmethod + def from_asset(cls, asset_name: str, download_url: str, size: int, digest: Optional[str] = None) -> Optional['AssetInfo']: + """ + Factory method to create an AssetInfo instance. + + Returns: + AssetInfo instance if parsing succeeds, None otherwise + """ + # Validate size + if size <= 0: + print(f'Skipping {asset_name}: Invalid size {size}', file=sys.stderr) + return None + + # Extract SHA256 from digest format: "sha256:hash" + sha256 = digest.split(':', 1)[1] if digest and digest.startswith('sha256:') else None + + # Parse asset name using split + # Expected formats: + # cpython-3.12.12+20251209-x86_64-unknown-linux-gnu-install_only.tar.gz + # cpython-3.10.19+20251209-aarch64-apple-darwin-install_only.tar.gz + + # Remove extension first + name_without_ext = asset_name + if name_without_ext.endswith('.tar.gz'): + name_without_ext = name_without_ext[:-7] + elif name_without_ext.endswith('.zip'): + name_without_ext = name_without_ext[:-4] + else: + print(f'Skipping {asset_name}: Unsupported file extension', file=sys.stderr) + return None + + # Split by '-' delimiter + parts = name_without_ext.split('-') + + if len(parts) < 6: + print(f'Skipping {asset_name}: Not enough parts to parse', file=sys.stderr) + return None + + # First part should be 'cpython' + if parts[0] != 'cpython': + print(f'Skipping {asset_name}: Does not start with cpython', file=sys.stderr) + return None + + # Second part is version+release_tag (e.g., "3.12.12+20251209") + version_tag = parts[1] + if '+' not in version_tag: + print(f'Skipping {asset_name}: Cannot parse version+release_tag', file=sys.stderr) + return None + version, release_tag = version_tag.split('+', 1) + + # Next parts: arch, vendor, os + arch = parts[2] + vendor = parts[3] + os = parts[4] + + # Remaining parts determine if libc/abi is present + # If we have exactly 6 parts: cpython, version+tag, arch, vendor, os, variant (no libc) + # If we have 7+ parts: cpython, version+tag, arch, vendor, os, abi, variant... + if len(parts) == 6: + # No libc/abi (macOS case) + abi = 'unknown' + variant = parts[5] + else: + # Has libc/abi + abi = parts[5] + # Variant is everything after abi (joined back with '-') + variant = '-'.join(parts[6:]) + + return cls(asset_name, download_url, size, sha256, version, release_tag, arch, vendor, os, abi, variant) + + @property + def platform(self) -> str: + """Normalize platform name.""" + return self.os + + @property + def libc(self) -> str: + """Normalize libc name.""" + if self.abi in ('gnu', 'musl'): + return self.abi + elif self.abi == 'msvc': + return 'msvc' + else: + return 'unknown' + + +class PythonVersionMapper: + """Maps Python versions to available build options.""" + + def __init__(self, fetcher: GitHubReleasesFetcher): + self.fetcher = fetcher + # version -> release_tag -> list of AssetInfo + self.version_map: Dict[str, Dict[str, List[AssetInfo]]] = {} + + def should_include_asset(self, asset_info: AssetInfo) -> bool: + """ + Check if an asset should be included in the mapping. + + Args: + asset_info: Asset information to check + + Returns: + True if the asset should be included, False otherwise + """ + # Exclude musl libc variants + if asset_info.libc == 'musl': + return False + + # Only include x86_64 and aarch64 (arm64) architectures + if asset_info.arch not in ('x86_64', 'aarch64', 'arm64'): + return False + + return True + + def process_releases(self, repo: str, max_pages: int = 50) -> None: + """Process releases from a repository.""" + print(f'Processing {repo} releases...', file=sys.stderr) + releases = self.fetcher.fetch_releases(repo, max_pages) + + for release in releases: + assets = release.get('assets', []) + + for asset in assets: + asset_name = asset.get('name', '') + + # Skip non-archive files (only process .tar.gz and .zip) + if not (asset_name.endswith('.tar.gz') or asset_name.endswith('.zip')): + continue + + size = asset.get('size', 0) + digest = asset.get('digest') + download_url = asset.get('browser_download_url') + + # Don't fetch SHA256 yet - will fetch only for latest releases in generate_mapping() + asset_info = AssetInfo.from_asset(asset_name, download_url, size, digest) + + # Skip if asset parsing failed + if asset_info is None: + continue + + # Check if asset should be included + if not self.should_include_asset(asset_info): + continue + + # Add to version map + if asset_info.version not in self.version_map: + self.version_map[asset_info.version] = {} + + if asset_info.release_tag not in self.version_map[asset_info.version]: + self.version_map[asset_info.version][asset_info.release_tag] = [] + + self.version_map[asset_info.version][asset_info.release_tag].append(asset_info) + + def generate_mapping(self) -> Dict: + """ + Generate the complete version mapping in JSON format. + + Returns: + Dictionary with version -> build options mapping + """ + result = {} + + for version, release_tags in sorted(self.version_map.items()): + # Use the latest tag as primary + latest_tag = max(release_tags.keys()) + + # Only collect files from the latest release tag + assets = release_tags[latest_tag] + if not assets: + continue + + files = {} + for asset in assets: + # Validate size + if asset.size <= 0: + raise ValueError(f"Invalid size {asset.size} for {asset.asset_name}") + + # Fetch SHA256 if not present (only for latest releases) + if not asset.sha256: + sha256_url = f"{asset.download_url}.sha256" + sha256_hash = self.fetcher.fetch_sha256(sha256_url) + asset.sha256 = sha256_hash + print(f'Fetched SHA256 for {asset.asset_name}', file=sys.stderr) + + # Create key from platform/arch/libc/variant combination + key = f"{asset.platform}-{asset.arch}-{asset.libc}-{asset.variant}" + # Extract just the filename from the URL + filename = asset.download_url.rsplit('/', 1)[1] + # Store: key -> dict with filename, sha256, and size + files[key] = { + 'filename': filename, + 'sha256': asset.sha256, + 'size': asset.size + } + + if not files: + continue + + # Build base URL with release tag + # Example: https://cache-redirector.jetbrains.com/github.com/astral-sh/python-build-standalone/releases/download/20241016 + # Get any file's full URL to extract the base pattern + sample_asset = assets[0] + sample_url = sample_asset.download_url + # Extract base: https://.../releases/download/TAG + base_url_with_tag = sample_url.rsplit('/', 1)[0] + + # Use cache redirector for astral-sh/python-build-standalone + if 'astral-sh/python-build-standalone' in base_url_with_tag: + base_url_with_tag = base_url_with_tag.replace('https://github.com/', 'https://cache-redirector.jetbrains.com/github.com/') + + result[version] = { + 'baseUrl': base_url_with_tag, + 'files': files + } + + return result + + +def write_json_file(version_map: Dict, output_path: Path) -> None: + """ + Write the version mapping to a JSON file. + + Args: + version_map: Dictionary mapping Python versions to build options + output_path: Path to the output JSON file + """ + with open(output_path, 'w', encoding='utf-8') as f: + json.dump(version_map, f, indent=2, sort_keys=True) + + print(f'Written {len(version_map)} version mappings to {output_path}', file=sys.stderr) + + +def main(): + """Main entry point.""" + import os + import argparse + + parser = argparse.ArgumentParser( + description='Generate Python version to platform mapping' + ) + parser.add_argument( + '--output', + type=Path, + default=Path(__file__).parent.parent / 'gen' / 'com' / 'intellij' / 'python' / 'test' / 'env' / 'core' / 'python-version-mapping.json', + help='Output JSON file path' + ) + parser.add_argument( + '--token', + type=str, + default=os.environ.get('GITHUB_TOKEN'), + help='GitHub personal access token (or set GITHUB_TOKEN env var)' + ) + + args = parser.parse_args() + + # Create fetcher and mapper + fetcher = GitHubReleasesFetcher(github_token=args.token) + mapper = PythonVersionMapper(fetcher) + + # Process both repositories + # Note: python-build-standalone has 1000+ releases, need many pages to get all platforms + mapper.process_releases('astral-sh/python-build-standalone', max_pages=200) + mapper.process_releases('qnox/python-2.7', max_pages=10) + + # Generate mapping + version_map = mapper.generate_mapping() + + if not version_map: + print('Error: No version mappings generated', file=sys.stderr) + sys.exit(1) + + # Write output + args.output.parent.mkdir(parents=True, exist_ok=True) + write_json_file(version_map, args.output) + + print(f'Successfully generated mapping with {len(version_map)} versions', file=sys.stderr) + + +if __name__ == '__main__': + main() diff --git a/python/python-test-env/core/src/com/intellij/python/test/env/core/CachingPyEnvironmentFactory.kt b/python/python-test-env/core/src/com/intellij/python/test/env/core/CachingPyEnvironmentFactory.kt new file mode 100644 index 000000000000..bfc526ee4ef8 --- /dev/null +++ b/python/python-test-env/core/src/com/intellij/python/test/env/core/CachingPyEnvironmentFactory.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.intellij.python.test.env.core + +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Internal +class CachingPyEnvironmentFactory( + private val wrapper: PyEnvironmentFactory, +) : PyEnvironmentFactory { + + private val cache = PyEnvironmentCache() + + override suspend fun createEnvironment(factory: PyEnvironmentFactory, spec: PyEnvironmentSpec<*>): PyEnvironment { + return cache.getOrCreate(spec.toCacheKey()) { + wrapper.createEnvironment(factory, spec) + } + } + + override fun close() { + cache.close() + } +} diff --git a/python/python-test-env/core/src/com/intellij/python/test/env/core/DefaultPyEnvironmentFactory.kt b/python/python-test-env/core/src/com/intellij/python/test/env/core/DefaultPyEnvironmentFactory.kt new file mode 100644 index 000000000000..0bb7f3da12c5 --- /dev/null +++ b/python/python-test-env/core/src/com/intellij/python/test/env/core/DefaultPyEnvironmentFactory.kt @@ -0,0 +1,34 @@ +// 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.test.env.core + +import java.nio.file.Path + +/** + * Default implementation of [PyEnvironmentFactory] that delegates to registered providers. + * + * Each provider is registered with a predicate that determines if it can handle a given spec. + * When creating an environment, the factory iterates through providers and uses the first + * matching one. + */ +class DefaultPyEnvironmentFactory( + private val workingDir: Path, + private val providers: List) -> Boolean, PyEnvironmentProvider<*>>> +): PyEnvironmentFactory { + + override suspend fun createEnvironment(factory: PyEnvironmentFactory, spec: PyEnvironmentSpec<*>): PyEnvironment { + for ((predicate, provider) in providers) { + if (predicate(spec)) { + val context = object : PyEnvironmentProvider.Context { + override val factory: PyEnvironmentFactory = factory + override val workingDir: Path = this@DefaultPyEnvironmentFactory.workingDir + override val cacheDir: Path = PyEnvDownloadCache.cacheDirectory() + } + @Suppress("UNCHECKED_CAST") + return (provider as PyEnvironmentProvider>).setupEnvironment(context, spec) + } + } + error("No provider found for environment specification: $spec") + } + + override fun close() {} +} diff --git a/python/python-test-env/core/src/com/intellij/python/test/env/core/ProcessUtils.kt b/python/python-test-env/core/src/com/intellij/python/test/env/core/ProcessUtils.kt new file mode 100644 index 000000000000..0c1142cf7d07 --- /dev/null +++ b/python/python-test-env/core/src/com/intellij/python/test/env/core/ProcessUtils.kt @@ -0,0 +1,46 @@ +// 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.test.env.core + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.util.io.awaitExit +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jetbrains.annotations.ApiStatus + +/** + * Executes a command and streams output to the logger in real-time. + * + * @param command The command and arguments to execute + * @param logger The logger to use for output + * @param logPrefix Prefix for log messages (e.g., "pip", "miniconda installer") + * @throws IllegalStateException if the process exits with non-zero code + */ +@ApiStatus.Internal +suspend fun executeProcess( + command: List, + logger: Logger, + logPrefix: String = "process" +) = withContext(Dispatchers.IO) { + val processBuilder = ProcessBuilder(command) + processBuilder.redirectErrorStream(true) + + logger.info("Running command: ${command.joinToString(" ")}") + val process = processBuilder.start() + + // Stream output line by line in real-time for debugging stuck processes + process.inputStream.bufferedReader().use { reader -> + reader.lineSequence().forEach { line -> + logger.info("$logPrefix: $line") + } + } + + val exitCode = process.awaitExit() + if (exitCode != 0) { + logger.error("Process failed with exit code $exitCode") + error("Process execution failed. Exit code: $exitCode") + } + else { + logger.info("Process completed successfully") + } +} + diff --git a/python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvDownloadCache.kt b/python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvDownloadCache.kt new file mode 100644 index 000000000000..95b4cf144cc5 --- /dev/null +++ b/python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvDownloadCache.kt @@ -0,0 +1,139 @@ +// 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.test.env.core + +import com.intellij.openapi.diagnostic.Logger +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jetbrains.annotations.ApiStatus +import java.net.HttpURLConnection +import java.net.URI +import java.nio.channels.FileChannel +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption +import java.nio.file.StandardOpenOption +import java.security.MessageDigest +import kotlin.io.path.* + +/** + * Shared download cache for Python environment providers. + * + * This cache: + * - Stores downloaded files persistently across test runs + * - Prevents redundant downloads of the same resources + * - Thread-safe for concurrent access + * - Uses content-based cache keys (URL hash) + * + * Cache directory structure: + * ``` + * ~/.cache/intellij-python-test-env/ + * downloads/ + * _filename.ext + * _filename.ext + * lock/ + * ``` + */ +@ApiStatus.Internal +object PyEnvDownloadCache { + private val LOG = Logger.getInstance(PyEnvDownloadCache::class.java) + + private val cacheDir: Path by lazy { + val userHome = System.getProperty("user.home") + val baseDir = Path.of(userHome, ".cache", "intellij-python-test-env", "downloads") + baseDir.createDirectories() + LOG.info("Download cache directory: $baseDir") + baseDir + } + + /** + * Get a cached file or download it if not present. + * + * Uses file-based locking to prevent concurrent downloads of the same resource + * without blocking other downloads. + * + * @param url URL to download from + * @param filename Original filename (used for cache file naming) + * @return Path to the cached file + */ + suspend fun getOrDownload(url: String, filename: String): Path { + val cacheKey = generateCacheKey(url) + val cachedFile = cacheDir.resolve("${cacheKey}_${filename}") + val lockFile = cacheDir.resolve("${cacheKey}_${filename}.lock") + + // Fast path: check if already cached + if (cachedFile.exists() && cachedFile.fileSize() > 0) { + LOG.info("Using cached file: $cachedFile (from $url)") + return cachedFile + } + + // Use file-based lock for this specific download + val lockChannel = FileChannel.open( + lockFile, + StandardOpenOption.CREATE, + StandardOpenOption.WRITE + ) + + try { + val fileLock = lockChannel.lock() + try { + // Double-check after acquiring lock + if (cachedFile.exists() && cachedFile.fileSize() > 0) { + LOG.info("Using cached file (double-check): $cachedFile") + return cachedFile + } + + LOG.info("Downloading $url to cache: $cachedFile") + return downloadFile(url, cachedFile) + } + finally { + fileLock.release() + } + } + finally { + lockChannel.close() + lockFile.deleteIfExists() + } + } + + /** + * Download a file from URL to the specified path. + */ + private suspend fun downloadFile(url: String, destination: Path): Path = withContext(Dispatchers.IO) { + val tempFile = Files.createTempFile(cacheDir, "download_", ".tmp") + + val connection = URI(url).toURL().openConnection() as HttpURLConnection + connection.requestMethod = "GET" + connection.connectTimeout = 30000 + connection.readTimeout = 30000 + + if (connection.responseCode != 200) { + tempFile.deleteIfExists() + error("Failed to download from $url: HTTP ${connection.responseCode}") + } + + connection.inputStream.use { input -> + Files.copy(input, tempFile, StandardCopyOption.REPLACE_EXISTING) + } + + Files.move(tempFile, destination, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING) + + LOG.info("Successfully downloaded and cached: $destination") + destination + } + + /** + * Generate a cache key from URL using SHA-256 hash. + */ + private fun generateCacheKey(url: String): String { + val digest = MessageDigest.getInstance("SHA-256") + val hash = digest.digest(url.toByteArray()) + return hash.joinToString("") { "%02x".format(it) }.take(16) + } + + /** + * Get the base cache directory path (parent of downloads directory). + * Use this for storing unpacked distributives alongside downloads. + */ + fun cacheDirectory(): Path = cacheDir.parent + +} diff --git a/python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentCache.kt b/python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentCache.kt new file mode 100644 index 000000000000..1f037b958532 --- /dev/null +++ b/python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentCache.kt @@ -0,0 +1,136 @@ +// 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.test.env.core + +import com.intellij.openapi.diagnostic.Logger +import org.jetbrains.annotations.ApiStatus +import java.util.concurrent.ConcurrentHashMap + +/** + * Wrapper around [PyEnvironment] that prevents closing the wrapped environment. + * Used to return cached environments that should remain alive for reuse. + */ +@ApiStatus.Internal +private class NonClosingPyEnvironmentWrapper(private val delegate: PyEnvironment) : PyEnvironment by delegate { + override fun close() { + // Do nothing - the cached environment must stay alive + } + + override fun unwrap(): T? { + @Suppress("UNCHECKED_CAST") + return delegate as? T + } +} + +/** + * Global cache for Python test environments. + * + * Environments are cached based on their specification and reused across multiple tests. + * Environments remain alive until explicitly released or JVM shutdown. + * + * JUnit integration should: + * - Call [releaseEnvironment] after each test if immediate cleanup is desired + * - Call [releaseAll] at the end of test suite for full cleanup + * - JVM shutdown hook provides last-resort cleanup + */ +@ApiStatus.Internal +internal class PyEnvironmentCache: AutoCloseable { + private val LOG = Logger.getInstance(PyEnvironmentCache::class.java) + + private val cache = ConcurrentHashMap() + + init { + // Register shutdown hook as last resort cleanup + Runtime.getRuntime().addShutdownHook(Thread { + releaseAll() + }) + } + + /** + * Get or create an environment for the given specification. + * Once created, the environment is kept alive until explicitly released. + * + * @param cacheKey Unique key identifying this environment configuration + * @param creator Suspend function to create the environment if not cached + * @return Result containing the cached or newly created environment wrapped in a no-op close wrapper + */ + suspend fun getOrCreate( + cacheKey: CacheKey, + creator: suspend () -> PyEnvironment, + ): PyEnvironment { + // Fast path: check if already cached + cache[cacheKey]?.let { environment -> + LOG.info("Environment cache HIT for key: $cacheKey, path: ${environment.pythonPath}") + return NonClosingPyEnvironmentWrapper(environment) + } + + // Slow path: create new environment + LOG.info("Environment cache MISS for key: $cacheKey, creating new environment...") + val startTime = System.currentTimeMillis() + + val environment = creator() + val creationTime = (System.currentTimeMillis() - startTime) / 1000.0 + + // Store in cache, or return existing if another thread created it first + val existingOrNew = cache.putIfAbsent(cacheKey, environment) + return if (existingOrNew == null) { + LOG.info("Environment CREATED in %.2f seconds for key: %s, path: %s".format(creationTime, cacheKey, environment.pythonPath)) + NonClosingPyEnvironmentWrapper(environment) + } + else { + LOG.info("Environment already created by another thread for key: $cacheKey") + NonClosingPyEnvironmentWrapper(existingOrNew) + } + } + + /** + * Release a specific environment from cache and close it. + * + * @param cacheKey Unique key identifying the environment to release + */ + fun releaseEnvironment(cacheKey: CacheKey) { + cache.remove(cacheKey)?.let { environment -> + LOG.info("Releasing environment for key: $cacheKey, path: ${environment.pythonPath}") + try { + environment.close() + LOG.info("Environment disposed successfully for key: $cacheKey") + } + catch (e: Exception) { + LOG.warn("Failed to dispose environment for key: $cacheKey", e) + } + } + } + + /** + * Release all cached environments and close them. + * Should be called by JUnit integration at end of test suite. + */ + fun releaseAll() { + val count = cache.size + if (count == 0) { + LOG.info("No cached environments to release") + return + } + + LOG.info("Releasing all $count cached environments...") + var successCount = 0 + var failCount = 0 + + cache.values.forEach { environment -> + try { + environment.close() + successCount++ + } + catch (e: Exception) { + LOG.warn("Failed to dispose environment at ${environment.pythonPath}", e) + failCount++ + } + } + cache.clear() + + LOG.info("Environment cache cleanup complete: $successCount disposed successfully, $failCount failed") + } + + override fun close() { + releaseAll() + } +} diff --git a/python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentFactory.kt b/python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentFactory.kt new file mode 100644 index 000000000000..5f96f1ae5225 --- /dev/null +++ b/python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentFactory.kt @@ -0,0 +1,15 @@ +// 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.test.env.core + +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Internal +interface PyEnvironmentFactory: AutoCloseable { + + suspend fun createEnvironment(spec: PyEnvironmentSpec<*>): PyEnvironment { + return createEnvironment(this, spec) + } + + suspend fun createEnvironment(factory: PyEnvironmentFactory, spec: PyEnvironmentSpec<*>): PyEnvironment + +} \ No newline at end of file diff --git a/python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentProvider.kt b/python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentProvider.kt new file mode 100644 index 000000000000..23eadfce93b8 --- /dev/null +++ b/python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentProvider.kt @@ -0,0 +1,156 @@ +// 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.test.env.core + +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.projectRoots.SdkType +import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil +import com.intellij.openapi.vfs.VfsUtil +import com.intellij.util.io.Decompressor +import com.jetbrains.python.PyNames +import com.jetbrains.python.PythonBinary +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jetbrains.annotations.ApiStatus +import java.nio.file.Path + +/** + * Represents a configured Python environment with operations for managing libraries and SDK creation. + * + * This abstraction encapsulates a Python environment and provides methods to: + * 1. Install additional libraries + * 2. Create an SDK from the environment + * 3. Clean up resources when closed + */ +@ApiStatus.Internal +interface PyEnvironment : AutoCloseable { + /** + * Path to the Python executable in this environment + */ + val pythonPath: PythonBinary + + /** + * Path to the environment directory + */ + val envPath: Path + + suspend fun prepareSdk(): Sdk = withContext(Dispatchers.IO) { + val vfsFile = VfsUtil.findFile(pythonPath, true) ?: error("Cannot find Python executable: ${pythonPath}") + SdkConfigurationUtil.setupSdk(emptyArray(), vfsFile, + SdkType.findByName(PyNames.PYTHON_SDK_ID_NAME)!!, null, null) + } + + /** + * Unwrap this environment to get the concrete implementation type. + * Useful when the environment is wrapped by caching or other delegation layers. + * + * @return The unwrapped concrete implementation, or null if this environment is not of type [T] + */ + fun unwrap(): T? { + @Suppress("UNCHECKED_CAST") + return this as? T + } + + /** + * Clean up resources associated with this environment. + * Default implementation does nothing - override if cleanup is needed. + */ + override fun close() {} +} + +/** + * Provider for setting up Python test environments. + * + * The provider is created with a working directory where all environments will be created. + * Each environment is set up in a subdirectory of the working directory. + * + * Implementations are responsible for: + * 1. Setting up the virtual environment using the appropriate tool (venv/uv/conda) + * 2. Installing the specified Python version + * 3. Creating a PyEnvironment abstraction for further operations + */ +@ApiStatus.Internal +abstract class PyEnvironmentProvider>( + /** + * Prefix for environment directories (e.g., "plain", "uv", "conda") + */ + private val envPrefix: String, +) { + private var envCounter = 0 + + /** + * Generate next environment directory path within the working directory. + * Uses provider-specific prefix to avoid conflicts between different provider types. + */ + protected fun nextEnvPath(workingDir: Path): Path { + return workingDir.resolve("${envPrefix}_${++envCounter}") + } + + /** + * Set up a Python environment based on the specification. + * The environment will be created in a subdirectory of the working directory. + * + * @param context Context providing access to the factory, working directory, and cache directory + * @param spec Environment specification with Python version, type, and libraries + * @return PyEnvironment abstraction for the created environment + */ + abstract suspend fun setupEnvironment(context: Context, spec: S): PyEnvironment + + /** + * Extract archive using Decompressor framework. + * Supports tar.gz, .tgz, and .zip formats. + * + * @param archiveFile Path to the archive file + * @param targetDir Directory where archive contents will be extracted + * @param prefixToStrip Path prefix to strip during extraction (e.g., "uv-x86_64-unknown-linux-gnu" for UV archives) + * @throws IllegalArgumentException if archive format is not supported + */ + protected fun unpackArchive(archiveFile: Path, targetDir: Path, prefixToStrip: String? = null) { + val fileName = archiveFile.fileName.toString() + when { + fileName.endsWith(".tar.gz", ignoreCase = true) || fileName.endsWith(".tgz", ignoreCase = true) -> { + val decompressor = Decompressor.Tar(archiveFile) + .removePrefixPath("python") + if (prefixToStrip != null) { + decompressor.removePrefixPath(prefixToStrip) + } + decompressor.extract(targetDir) + } + fileName.endsWith(".zip", ignoreCase = true) -> { + // Use withZipExtensions() for proper handling of ZIP metadata (directory flags, symlinks, etc.) + val decompressor = Decompressor.Zip(archiveFile).withZipExtensions() + .removePrefixPath("python") + if (prefixToStrip != null) { + decompressor.removePrefixPath(prefixToStrip) + } + decompressor.extract(targetDir) + } + else -> { + error("Unsupported archive format: $fileName. Expected .tar.gz, .tgz, or .zip") + } + } + } + + /** + * Context for setting up Python environments. + * Provides access to the factory, working directory, and cache directory. + */ + interface Context { + /** + * The factory instance used for creating environments. + * Can be used to create nested or dependent environments. + */ + val factory: PyEnvironmentFactory + + /** + * Working directory where environments are created. + * Each environment typically gets its own subdirectory. + */ + val workingDir: Path + + /** + * Cache directory for storing downloaded artifacts. + * Used to avoid re-downloading Python distributions and other resources. + */ + val cacheDir: Path + } +} diff --git a/python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentSpec.kt b/python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentSpec.kt new file mode 100644 index 000000000000..e6ec0c30347c --- /dev/null +++ b/python/python-test-env/core/src/com/intellij/python/test/env/core/PyEnvironmentSpec.kt @@ -0,0 +1,70 @@ +// 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.test.env.core + +import com.intellij.util.text.SemVer +import org.jetbrains.annotations.ApiStatus + +/** + * Base specification for Python environment requirements for tests. + * + * Use type-specific builder functions instead of this class directly: + */ +@ApiStatus.Internal +abstract class PyEnvironmentSpec> { + var pythonVersion: SemVer = LATEST_PYTHON_VERSION + + private val _libraries = mutableListOf() + val libraries: List get() = _libraries + + /** + * DSL builder for specifying libraries + */ + fun libraries(block: LibrariesBuilder.() -> Unit) { + val builder = LibrariesBuilder() + builder.block() + _libraries.addAll(builder.libraries) + } + + /** + * Generate a cache key for this environment specification. + * The key uniquely identifies the environment configuration. + * + * @return Cache key string + */ + abstract fun toCacheKey(): CacheKey + + /** + * Helper to create a cache key from common components. + */ + protected fun buildCacheKey(providerType: String, vararg additionalComponents: String): CacheKey { + val components = mutableListOf(providerType, pythonVersion.toString()) + components.addAll(additionalComponents) + if (libraries.isNotEmpty()) { + components.add("libs:" + libraries.sorted().joinToString(",")) + } + return CacheKey(components.joinToString("|")) + } + + fun pythonVersion(version: String): SemVer { + return parsePythonVersion(version) + } +} + +/** + * Builder for library specifications + */ +@ApiStatus.Internal +class LibrariesBuilder { + internal val libraries = mutableListOf() + + /** + * Add a library with version specification + * Example: +"numpy==2.0.2" or +"pandas>=1.5.0" + */ + operator fun String.unaryPlus() { + libraries.add(this) + } +} + +@JvmInline +value class CacheKey(val value: String) diff --git a/python/python-test-env/core/src/com/intellij/python/test/env/core/PyVersionMapping.kt b/python/python-test-env/core/src/com/intellij/python/test/env/core/PyVersionMapping.kt new file mode 100644 index 000000000000..96f6bed0bc75 --- /dev/null +++ b/python/python-test-env/core/src/com/intellij/python/test/env/core/PyVersionMapping.kt @@ -0,0 +1,162 @@ +// 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.test.env.core + +import com.fasterxml.jackson.annotation.JsonProperty +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonTypeRef +import com.fasterxml.jackson.module.kotlin.registerKotlinModule +import com.intellij.util.system.CpuArch +import com.intellij.util.system.OS +import com.intellij.util.text.SemVer +import org.jetbrains.annotations.ApiStatus + +/** + * Shared Python version mapping utility for test environment providers. + * + * Maps major.minor versions to full major.minor.patch versions based on + * python-build-standalone releases. + */ +@ApiStatus.Internal +object PyVersionMapping { + + /** + * Python version build information. + * Files map key format: platform-arch-libc-variant + * Example: "linux-x86_64-gnu-install_only" + * Files map value: FileInfo with filename and optional sha256 + */ + data class PythonBuildInfo( + @field:JsonProperty("baseUrl") val baseUrl: String, + @field:JsonProperty("files") val files: Map + ) + + /** + * File information for a Python build. + */ + data class FileInfo( + @field:JsonProperty("filename") val filename: String, + @field:JsonProperty("sha256") val sha256: String, + @field:JsonProperty("size") val size: Long + ) + + /** + * Download information for a Python build. + */ + data class DownloadInfo( + val url: String, + val sha256: String, + val size: Long + ) + + /** + * Python version to build information mapping. + * Loaded from python-version-mapping.json resource file. + */ + private val PYTHON_VERSION_MAPPING: Map by lazy { + loadVersionMapping() + } + + /** + * Get the Python version to build information mapping. + * @return Map of version strings to PythonBuildInfo + */ + fun getPythonVersionMapping(): Map = PYTHON_VERSION_MAPPING + + /** + * Load version mapping from JSON file. + */ + private fun loadVersionMapping(): Map { + val resourceName = "/com/intellij/python/test/env/core/python-version-mapping.json" + val mapper = ObjectMapper().registerKotlinModule() + + val inputStream = PyVersionMapping::class.java.getResourceAsStream(resourceName) + ?: error("Failed to load Python version mapping from $resourceName") + + return inputStream.use { stream -> + mapper.readValue(stream, jacksonTypeRef>()) + }.mapKeys { parsePythonVersion(it.key) } + } + + /** + * Get the download information for python-build-standalone for a given Python version. + * Automatically detects the platform, architecture, and selects appropriate libc/variant. + * + * @param version Version string like "3.11", "3.12", or "3.11.14" + * @return Download information containing URL and optional SHA256 checksum + */ + fun getDownloadInfo(version: String): DownloadInfo { + val fullVersion = parsePythonVersion(version) + val buildInfo = PYTHON_VERSION_MAPPING[fullVersion] + ?: error("No build information found for Python $fullVersion") + + // Detect platform + val platform = when (OS.CURRENT) { + OS.Windows -> "windows" + OS.macOS -> "darwin" + OS.Linux -> "linux" + else -> error("Unsupported platform: ${OS.CURRENT}") + } + + // Detect architecture + val baseArch = when { + CpuArch.isArm64() -> "aarch64" + CpuArch.isIntel64() -> "x86_64" + else -> error("Unsupported architecture: ${CpuArch.CURRENT}") + } + + // For ARM64, try both aarch64 and arm64 as synonyms on all platforms + val archVariants = if (baseArch == "aarch64") { + listOf("aarch64", "arm64") + } else { + listOf(baseArch) + } + + // Select libc preference + val libc = when (platform) { + "windows" -> "msvc" + "darwin" -> "unknown" + "linux" -> "gnu" // Prefer gnu over musl by default + else -> "unknown" + } + + // Try to find a file with preferred variant and libc, fallback if needed + val preferredVariants = listOf("install_only", "install_only_stripped") + val preferredLibcs = if (platform == "linux") listOf(libc, "musl") else listOf(libc) + + var fileInfo: FileInfo? = null + + // Try preferred combinations first + for (arch in archVariants) { + for (variant in preferredVariants) { + for (libcOption in preferredLibcs) { + val key = "$platform-$arch-$libcOption-$variant" + if (key in buildInfo.files) { + fileInfo = buildInfo.files[key] + break + } + } + if (fileInfo != null) break + } + if (fileInfo != null) break + } + + // If no match found, try any file for this platform + if (fileInfo == null) { + for (arch in archVariants) { + val matchingKey = buildInfo.files.keys.firstOrNull { it.startsWith("$platform-$arch-") } + if (matchingKey != null) { + fileInfo = buildInfo.files[matchingKey] + break + } + } + } + + if (fileInfo == null) { + error("No suitable build found for Python $fullVersion on $platform/$baseArch. Available: ${buildInfo.files.keys}") + } + + // Construct URL: baseUrl/{filename} + val url = "${buildInfo.baseUrl}/${fileInfo.filename}" + return DownloadInfo(url, fileInfo.sha256, fileInfo.size) + } +} diff --git a/python/python-test-env/core/src/com/intellij/python/test/env/core/PythonVersion.kt b/python/python-test-env/core/src/com/intellij/python/test/env/core/PythonVersion.kt new file mode 100644 index 000000000000..f1b67713d68f --- /dev/null +++ b/python/python-test-env/core/src/com/intellij/python/test/env/core/PythonVersion.kt @@ -0,0 +1,100 @@ +// 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.test.env.core + +import com.intellij.util.text.SemVer +import org.jetbrains.annotations.ApiStatus + +/** + * Major.minor to full major.minor.patch version mapping. + * Generated dynamically from PyVersionMapping - uses the latest stable patch version for each major.minor. + * Excludes pre-release versions (alpha, beta, rc, etc.). + */ +private val VERSION_TO_FULL: Map by lazy { + PyVersionMapping.getPythonVersionMapping().keys + .filter { it.preRelease == null } + .groupBy { version -> + "${version.major}.${version.minor}" + } + .mapValues { (_, versions) -> + versions.maxOrNull()!! + } +} + +/** + * Parse a Python version string into a SemVer instance. + * + * Supports formats: "3.11", "3.12.5", "3.11.14", "3.10.1a1", "3.10.1b3", "3.10.1rc6", "3.13.2somestring" + * + * If only major.minor is provided (e.g., "3.11"), automatically fills in the patch version + * from the known version mapping. If the version is not in the mapping, defaults to patch 0. + * If the full version is provided (e.g., "3.11.14"), uses it as-is. + * + * Pre-release suffixes (a, b, rc) with optional numbers are supported. + * Arbitrary post-release strings (e.g., "3.13.2somestring") are also supported and sorted lowest. + * + * Python pre-release versions (a, b, rc) are parsed without the SemVer hyphen separator. + * Pre-release versions are sorted: empty > rc > beta > alpha > other strings + * Within the same pre-release type, higher numbers come first (e.g., rc6 > rc2, b10 > b3, a15 > a1) + * + * @param version Version string + * @return Parsed SemVer with custom pre-release comparison + * @throws IllegalArgumentException if the version string is invalid + */ +@ApiStatus.Internal +fun parsePythonVersion(version: String): SemVer { + // Match pattern: major.minor[.patch][suffix] + val regex = Regex("""^(\d+)\.(\d+)(?:\.(\d+)(.*))?$""") + val match = regex.matchEntire(version) + ?: throw IllegalArgumentException("Invalid Python version format: $version. Expected formats: '3.11', '3.12.5', '3.10.1a1', '3.10.1b3', '3.10.1rc6', '3.13.2somestring'") + + val major = match.groupValues[1].toInt() + val minor = match.groupValues[2].toInt() + val patchStr = match.groupValues[3] + val suffix = match.groupValues[4].takeIf { it.isNotEmpty() } + + // If patch is provided, use it with the suffix + if (patchStr.isNotEmpty()) { + val patch = patchStr.toInt() + return SemVer(version, major, minor, patch, suffix) + } + + // Only major.minor provided, look up the full version (only for stable versions) + if (suffix == null) { + val majorMinor = "$major.$minor" + val fullVersion = VERSION_TO_FULL[majorMinor] + if (fullVersion != null) { + return fullVersion + } + return SemVer("$major.$minor.0", major, minor, 0, null) + } + + // major.minor with suffix, default patch to 0 + return SemVer("$major.$minor.0$suffix", major, minor, 0, suffix) +} + +/** + * Latest stable Python version. + * Generated dynamically from PyVersionMapping - uses the highest major.minor.patch version available. + * Excludes pre-release versions (alpha, beta, rc, etc.). + */ +val LATEST_PYTHON_VERSION: SemVer by lazy { + PyVersionMapping.getPythonVersionMapping().keys + .filter { it.preRelease == null } + .maxOrNull() + ?: error("No stable Python versions found in mapping") +} + +/** + * Extension function to check if a Python version matches a major.minor version string. + * The patch version is ignored in the comparison. + * + * Examples: + * - SemVer("3.11.14", 3, 11, 14).matches("3.11") -> true + * - SemVer("3.11.14", 3, 11, 14).matches("3.12") -> false + */ +@ApiStatus.Internal +fun SemVer.matches(majorMinor: String): Boolean { + val parts = majorMinor.split(".") + if (parts.size < 2) return false + return major == parts[0].toIntOrNull() && minor == parts[1].toIntOrNull() +} diff --git a/python/python-test-env/core/src/com/intellij/python/test/env/core/install.kt b/python/python-test-env/core/src/com/intellij/python/test/env/core/install.kt new file mode 100644 index 000000000000..41c94846bc9c --- /dev/null +++ b/python/python-test-env/core/src/com/intellij/python/test/env/core/install.kt @@ -0,0 +1,105 @@ +package com.intellij.python.test.env.core + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.util.system.OS +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jetbrains.annotations.ApiStatus +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.attribute.PosixFilePermission +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.deleteRecursively +import kotlin.io.path.exists +import kotlin.io.path.pathString + +/** + * Extracts archive to target directory if not already extracted (checked via completion marker). + * + * This function implements the standard extraction flow: + * 1. Check if extraction was already completed (marker exists) + * 2. Clean up incomplete extractions if directory exists without marker + * 3. Create target directory and call unpack function + * 4. Create completion marker + * + * @param target Target directory for extraction + * @param logger Logger for operation messages + * @param unpack Function that performs the actual unpacking to the target directory + */ +@ApiStatus.Internal +@OptIn(ExperimentalPathApi::class) +suspend fun extractIfNecessary( + target: Path, + logger: Logger, + unpack: suspend (Path) -> Unit +) = withContext(Dispatchers.IO) { + val completionMarker = target.resolve(".extract_complete") + + // Check if already extracted + if (completionMarker.exists()) { + logger.info("Already extracted at: $target") + return@withContext + } + + // If directory exists but extraction is incomplete, clean it up + if (target.exists()) { + logger.info("Cleaning up incomplete extraction at: $target") + try { + target.deleteRecursively() + } catch (e: Exception) { + logger.warn("Failed to fully delete directory, will retry extraction", e) + } + } + + // Extract archive + logger.info("Extracting to: $target") + unpack(target) + logger.info("Extraction completed") + + // Mark extraction as complete + Files.createFile(completionMarker) + logger.info("Extraction setup completed") +} + +/** + * Installs Python packages using pip. + * + * @param pythonPath Path to Python executable + * @param libraries List of package specifications to install (e.g., "numpy==1.24.0") + * @param logger The logger to use for output + * @throws IllegalStateException if pip install fails + */ +@ApiStatus.Internal +suspend fun installPipPackages( + pythonPath: Path, + libraries: List, + logger: Logger +) { + if (libraries.isEmpty()) { + logger.info("No libraries to install") + return + } + + val command = listOf( + pythonPath.pathString, + "-m", "pip", "install", + "--disable-pip-version-check", + "--progress-bar", "off" + ) + libraries + + executeProcess(command, logger, "pip") +} + +fun markExecutable(logger: Logger, executable: Path) { + if (OS.CURRENT != OS.Windows) { + logger.info("Setting executable permissions") + Files.setPosixFilePermissions( + executable, + setOf( + PosixFilePermission.OWNER_READ, + PosixFilePermission.OWNER_WRITE, + PosixFilePermission.OWNER_EXECUTE + ) + ) + } +} \ No newline at end of file diff --git a/python/python-test-env/junit4/BUILD.bazel b/python/python-test-env/junit4/BUILD.bazel new file mode 100644 index 000000000000..e58f6d626488 --- /dev/null +++ b/python/python-test-env/junit4/BUILD.bazel @@ -0,0 +1,46 @@ +### auto-generated section `build intellij.python.test.env.junit4` start +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +resourcegroup( + name = "junit4_test_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "junit4", + visibility = ["//visibility:public"], + srcs = glob([], allow_empty = True), + runtime_deps = [ + "@lib//:jetbrains-annotations", + "//python/python-test-env/core", + "@lib//:kotlin-stdlib", + ] +) + +jvm_library( + name = "junit4_test_lib", + module_name = "intellij.python.test.env.junit4", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [":junit4_test_resources"], + deps = [ + "@lib//:jetbrains-annotations", + "@lib//:junit4", + "//python/python-test-env/core", + "//python/python-test-env/core:core_test_lib", + "@lib//:kotlin-stdlib", + "//python/python-test-env/common:common_test_lib", + ], + runtime_deps = [":junit4"] +) +### auto-generated section `build intellij.python.test.env.junit4` end + +### auto-generated section `test intellij.python.test.env.junit4` start +load("@community//build:tests-options.bzl", "jps_test") + +jps_test( + name = "junit4_test", + runtime_deps = [":junit4_test_lib"] +) +### auto-generated section `test intellij.python.test.env.junit4` end \ No newline at end of file diff --git a/python/python-test-env/junit4/intellij.python.test.env.junit4.iml b/python/python-test-env/junit4/intellij.python.test.env.junit4.iml new file mode 100644 index 000000000000..667b5a16b0a4 --- /dev/null +++ b/python/python-test-env/junit4/intellij.python.test.env.junit4.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/python/python-test-env/junit4/resources/intellij.python.test.env.junit4.xml b/python/python-test-env/junit4/resources/intellij.python.test.env.junit4.xml new file mode 100644 index 000000000000..6fb76a66265d --- /dev/null +++ b/python/python-test-env/junit4/resources/intellij.python.test.env.junit4.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/python/python-test-env/junit4/src/com/intellij/python/test/env/junit4/JUnit4FactoryHolder.kt b/python/python-test-env/junit4/src/com/intellij/python/test/env/junit4/JUnit4FactoryHolder.kt new file mode 100644 index 000000000000..f472690aa02d --- /dev/null +++ b/python/python-test-env/junit4/src/com/intellij/python/test/env/junit4/JUnit4FactoryHolder.kt @@ -0,0 +1,20 @@ +package com.intellij.python.test.env.junit4 + +import com.intellij.python.test.env.common.createPyEnvironmentFactory +import com.intellij.python.test.env.core.PyEnvironmentFactory + +object JUnit4FactoryHolder { + + private val factory: PyEnvironmentFactory by lazy { + val f = createPyEnvironmentFactory() + Runtime.getRuntime().addShutdownHook(Thread { + f.close() + }) + f + } + + fun getOrCreate(): PyEnvironmentFactory { + return factory + } + +} diff --git a/python/python-test-env/junit5/BUILD.bazel b/python/python-test-env/junit5/BUILD.bazel new file mode 100644 index 000000000000..f8c29dae6d3b --- /dev/null +++ b/python/python-test-env/junit5/BUILD.bazel @@ -0,0 +1,82 @@ +### auto-generated section `build intellij.python.test.env.junit5` start +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +resourcegroup( + name = "junit5_test_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "junit5", + visibility = ["//visibility:public"], + srcs = glob([], allow_empty = True), + exports = ["//python/python-test-env/core"], + runtime_deps = [ + "@lib//:jetbrains-annotations", + "//python/python-test-env/core", + "@lib//:kotlin-stdlib", + "@lib//:kotlinx-coroutines-core", + "//python/python-test-env/common", + "//python/openapi:community", + "//platform/projectModel-api:projectModel", + "//platform/execution", + "//libraries/junit5", + ] +) + +jvm_library( + name = "junit5_test_lib", + module_name = "intellij.python.test.env.junit5", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form", "test/**/*.kt", "test/**/*.java", "test/**/*.form"], allow_empty = True), + resources = [":junit5_test_resources"], + deps = [ + "@lib//:jetbrains-annotations", + "//python/python-test-env/core", + "//python/python-test-env/core:core_test_lib", + "@lib//:kotlin-stdlib", + "//platform/util", + "@lib//:kotlinx-coroutines-core", + "//python/python-test-env/common", + "//python/python-test-env/common:common_test_lib", + "//platform/testFramework/junit5", + "//platform/testFramework/junit5:junit5_test_lib", + "//python/openapi:community", + "//python/openapi:community_test_lib", + "//platform/projectModel-api:projectModel", + "//python/python-test-env/plain:plain_test_lib", + "//platform/core-api:core", + "//python/python-poetry/common", + "//python/pipenv", + "//python/python-sdk:sdk", + "//python/python-sdk:sdk_test_lib", + "//python/junit5Tests-framework:community-junit5Tests-framework_test_lib", + "//python:python-community-impl", + "//python:python-community-impl_test_lib", + "//platform/execution", + "//python/python-test-env/conda:conda_test_lib", + "//python/python-venv:community-impl-venv", + "//python/python-venv:community-impl-venv_test_lib", + "//python/testFramework", + "//libraries/junit5", + "//platform/testFramework/common", + "//python/services/system-python", + "//python/services/system-python:system-python_test_lib", + ], + exports = [ + "//python/python-test-env/core", + "//python/python-test-env/core:core_test_lib", + ], + runtime_deps = [":junit5"] +) +### auto-generated section `build intellij.python.test.env.junit5` end + +### auto-generated section `test intellij.python.test.env.junit5` start +load("@community//build:tests-options.bzl", "jps_test") + +jps_test( + name = "junit5_test", + runtime_deps = [":junit5_test_lib"] +) +### auto-generated section `test intellij.python.test.env.junit5` end \ No newline at end of file diff --git a/python/python-test-env/junit5/intellij.python.test.env.junit5.iml b/python/python-test-env/junit5/intellij.python.test.env.junit5.iml new file mode 100644 index 000000000000..12da561abbfb --- /dev/null +++ b/python/python-test-env/junit5/intellij.python.test.env.junit5.iml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/python/python-test-env/junit5/resources/intellij.python.test.env.junit5.xml b/python/python-test-env/junit5/resources/intellij.python.test.env.junit5.xml new file mode 100644 index 000000000000..a2c00b586d41 --- /dev/null +++ b/python/python-test-env/junit5/resources/intellij.python.test.env.junit5.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/CondaEnv.kt b/python/python-test-env/junit5/src/com/intellij/python/community/junit5Tests/framework/conda/CondaEnv.kt similarity index 100% rename from python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/CondaEnv.kt rename to python/python-test-env/junit5/src/com/intellij/python/community/junit5Tests/framework/conda/CondaEnv.kt diff --git a/python/python-test-env/junit5/src/com/intellij/python/community/junit5Tests/framework/conda/PyEnvTestCaseWithConda.kt b/python/python-test-env/junit5/src/com/intellij/python/community/junit5Tests/framework/conda/PyEnvTestCaseWithConda.kt new file mode 100644 index 000000000000..5414c57a6124 --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/community/junit5Tests/framework/conda/PyEnvTestCaseWithConda.kt @@ -0,0 +1,28 @@ +// 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.junit5Tests.framework.conda + +import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase +import com.intellij.python.test.env.common.PredefinedPyEnvironments +import com.intellij.python.test.env.junit5.conda.CondaPythonEnvExtension +import org.junit.jupiter.api.extension.ExtendWith + +/** + * Python and conda env test case that supports [com.intellij.python.junit5Tests.framework.env.PythonBinaryPath], + * [com.intellij.python.junit5Tests.framework.env.PythonSdk], and [CondaEnv] parameter injection. + * Uses Conda environment from predefined environments. + * + * Example: + * ```kotlin +@PyEnvTestCaseWithConda +class PyEnvTestExample { + @Test + fun test(@CondaEnv conda: PyCondaEnv) { ... } + + @Test + fun testSdk(@PythonSdk sdk: Sdk) { ... } +} + * ``` + */ +@PyEnvTestCase(PredefinedPyEnvironments.CONDA) +@ExtendWith(CondaPythonEnvExtension::class) +annotation class PyEnvTestCaseWithConda \ No newline at end of file diff --git a/python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/condaEnvTool.kt b/python/python-test-env/junit5/src/com/intellij/python/community/junit5Tests/framework/conda/condaEnvTool.kt similarity index 100% rename from python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/condaEnvTool.kt rename to python/python-test-env/junit5/src/com/intellij/python/community/junit5Tests/framework/conda/condaEnvTool.kt diff --git a/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/BeforeRunOnEnvironmentInvocation.kt b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/BeforeRunOnEnvironmentInvocation.kt new file mode 100644 index 000000000000..c3d849239574 --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/BeforeRunOnEnvironmentInvocation.kt @@ -0,0 +1,10 @@ +package com.intellij.python.junit5Tests.framework.env + +import kotlin.annotation.AnnotationRetention +import kotlin.annotation.AnnotationTarget +import kotlin.annotation.Retention +import kotlin.annotation.Target + +@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +annotation class BeforeRunOnEnvironmentInvocation \ No newline at end of file diff --git a/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/PyEnvTestCase.kt b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/PyEnvTestCase.kt new file mode 100644 index 000000000000..54b29e1d7543 --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/PyEnvTestCase.kt @@ -0,0 +1,41 @@ +// 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.junit5Tests.framework.env + +import com.intellij.python.test.env.common.PredefinedPyEnvironments +import com.intellij.python.test.env.junit5.* +import com.intellij.testFramework.junit5.TestApplication +import org.junit.jupiter.api.extension.ExtendWith + +/** + * Python env test case that supports [PythonBinaryPath] and [PythonSdk] parameter injection. + * + * @param envs values from [com.intellij.python.test.env.common.PredefinedPyEnvironments] for environments where to run test. + * If empty, defaults to Python 3.12. + * + * Example: + * ```kotlin +@PyEnvTestCase +class PyEnvTestExample { + @Test + fun test(@PythonBinaryPath binary: PythonBinary) { ... } + + @Test + fun testSdk(@PythonSdk sdk: Sdk) { ... } +} + +@PyEnvTestCase(env = PredefinedPyEnvironments.CONDA) +class PyCondaTest { + @Test + fun test(@PythonBinaryPath binary: PythonBinary) { ... } +} + * ``` + */ +@TestApplication +@Target(AnnotationTarget.CLASS) +@ExtendWith( + PythonBinaryPathExtension::class, + PythonSdkExtension::class, + PythonFactoryExtension::class, + EnvTestPythonProviderExtension::class +) +annotation class PyEnvTestCase(val env : PredefinedPyEnvironments = PredefinedPyEnvironments.VENV_3_12) \ No newline at end of file diff --git a/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/PythonBinaryPath.kt b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/PythonBinaryPath.kt similarity index 92% rename from python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/PythonBinaryPath.kt rename to python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/PythonBinaryPath.kt index 0ba0d1328cb1..8bd236258520 100644 --- a/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/PythonBinaryPath.kt +++ b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/PythonBinaryPath.kt @@ -3,7 +3,6 @@ package com.intellij.python.junit5Tests.framework.env /** * Mark [java.nio.file.Path] test parameter to get first python binary for env test. - * If you need sdk -- use [pySdkFixture] * * Example: * ```kotlin diff --git a/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/PythonSdk.kt b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/PythonSdk.kt new file mode 100644 index 000000000000..8bba87d29730 --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/PythonSdk.kt @@ -0,0 +1,15 @@ +// 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.junit5Tests.framework.env + +/** + * Mark [com.intellij.openapi.projectRoots.Sdk] test parameter to get Python SDK for env test. + * If you only need binary path -- use [PythonBinaryPath] + * + * Example: + * ```kotlin + * @Test + * fun checkPythonSdk(@PythonSdk sdk: Sdk) // SDK is a full Python SDK + * ``` + */ +@Target(AnnotationTarget.VALUE_PARAMETER) +annotation class PythonSdk \ No newline at end of file diff --git a/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/RequiresPoetry.kt b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/RequiresPoetry.kt new file mode 100644 index 000000000000..8a0a9fc8ecc4 --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/RequiresPoetry.kt @@ -0,0 +1,46 @@ +// 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.junit5Tests.framework.env + +import com.intellij.python.test.env.junit5.RequiresPoetryExtension +import org.junit.jupiter.api.extension.ExtendWith + +/** + * Annotation that ensures Poetry tool is available and configured for the test. + * + * This annotation can be applied to test classes or individual test methods. + * It will automatically configure the Poetry path in PropertiesComponent before the test runs. + * + * Usage: + * ```kotlin + * // Applied to class - Poetry configured for all tests + * @PyEnvTestCase + * @RequiresPoetry + * class MyPoetryTest { + * @Test + * fun testPoetryFeature() { + * // Poetry is configured and available here + * } + * } + * + * // Applied to individual test methods + * @PyEnvTestCase + * class MyMixedTest { + * @Test + * @RequiresPoetry + * fun testWithPoetry() { + * // Poetry is configured only for this test + * } + * + * @Test + * fun testWithoutPoetry() { + * // Poetry not configured here + * } + * } + * ``` + * + * Note: This annotation requires @PyEnvTestCase to be present on the class to provide the Python environment. + */ +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +@ExtendWith(RequiresPoetryExtension::class) +annotation class RequiresPoetry diff --git a/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/RequiresUv.kt b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/RequiresUv.kt new file mode 100644 index 000000000000..404ce06c1538 --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/RequiresUv.kt @@ -0,0 +1,46 @@ +// 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.junit5Tests.framework.env + +import com.intellij.python.test.env.junit5.RequiresUvExtension +import org.junit.jupiter.api.extension.ExtendWith + +/** + * Annotation that ensures `uv` tool is available and configured for the test. + * + * This annotation can be applied to test classes or individual test methods. + * It will automatically configure the Uv path in PropertiesComponent before the test runs. + * + * Usage: + * ```kotlin + * // Applied to class - Uv configured for all tests + * @PyEnvTestCase + * @RequiresUv + * class MyUvTest { + * @Test + * fun testUvFeature() { + * // Uv is configured and available here + * } + * } + * + * // Applied to individual test methods + * @PyEnvTestCase + * class MyMixedTest { + * @Test + * @RequiresUv + * fun testWithUv() { + * // Uv is configured only for this test + * } + * + * @Test + * fun testWithoutUv() { + * // Uv not configured here + * } + * } + * ``` + * + * Note: This annotation requires @PyEnvTestCase to be present on the class to provide the Python environment. + */ +@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.RUNTIME) +@ExtendWith(RequiresUvExtension::class) +annotation class RequiresUv diff --git a/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/RunOnEnvironments.kt b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/RunOnEnvironments.kt new file mode 100644 index 000000000000..b51ae9bdef1d --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/RunOnEnvironments.kt @@ -0,0 +1,16 @@ +package com.intellij.python.junit5Tests.framework.env + +import com.intellij.python.test.env.common.PredefinedPyEnvironments +import com.intellij.python.test.env.junit5.RunOnEnvironmentsExtension +import org.junit.jupiter.api.ClassTemplate +import org.junit.jupiter.api.extension.ClassTemplateInvocationLifecycleMethod +import org.junit.jupiter.api.extension.ExtendWith + +@PyEnvTestCase +@ClassTemplate +@ClassTemplateInvocationLifecycleMethod(classTemplateAnnotation = PyEnvTestCase::class, lifecycleMethodAnnotation = BeforeRunOnEnvironmentInvocation::class) +@ExtendWith( + RunOnEnvironmentsExtension::class, +) +@Target(AnnotationTarget.CLASS) +annotation class RunOnEnvironments(vararg val envs: PredefinedPyEnvironments = [PredefinedPyEnvironments.VENV_3_12]) diff --git a/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/pySdkFixture.kt b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/pySdkFixture.kt new file mode 100644 index 000000000000..4c1e0ad54663 --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/junit5Tests/framework/env/pySdkFixture.kt @@ -0,0 +1,58 @@ +// 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.junit5Tests.framework.env + +import com.intellij.openapi.application.edtWriteAction +import com.intellij.openapi.projectRoots.ProjectJdkTable +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.python.test.env.common.PredefinedPyEnvironments +import com.intellij.python.test.env.common.createEnvironment +import com.intellij.python.test.env.core.PyEnvironment +import com.intellij.python.test.env.core.PyEnvironmentFactory +import com.intellij.python.test.env.junit5.RunOnEnvironmentsExtension +import com.intellij.python.test.env.junit5.getOrCreatePyEnvironmentFactory +import com.intellij.testFramework.junit5.fixture.TestFixture +import com.intellij.testFramework.junit5.fixture.TestFixtureInitializer +import com.intellij.testFramework.junit5.fixture.testFixture +import com.jetbrains.python.sdk.persist + +/** + * Create sdk fixture using tags from [@PyEnvTestCase][com.intellij.python.junit5Tests.framework.env.PyEnvTestCase] annotation. + * Requires test class to be annotated with @PyEnvTestCase. + * + * @throws IllegalStateException if @PyEnvTestCase annotation is not found on the test class + */ +fun pySdkFixture(): TestFixture> = testFixture { context -> + initializedTestFixture(RunOnEnvironmentsExtension.getPythonEnvironment(context.extensionContext)) +} + +/** + * Creates [Sdk] (if you only need a python path, use [com.intellij.python.junit5Tests.framework.env.PythonBinaryPath] + * or [com.intellij.python.community.junit5Tests.framework.conda.CondaEnv]) + */ +fun pySdkFixture( + env: PredefinedPyEnvironments, +): TestFixture> = testFixture { context -> + val factory = getOrCreatePyEnvironmentFactory(context.extensionContext) + initializedPySdkFixture(factory, env) +} + +private suspend fun TestFixtureInitializer.R>.initializedPySdkFixture( + factory: PyEnvironmentFactory, + env: PredefinedPyEnvironments, +): TestFixtureInitializer.InitializedTestFixture> { + val env = factory.createEnvironment(env) + return initializedTestFixture(env) +} + +private suspend fun TestFixtureInitializer.R>.initializedTestFixture( + env: PyEnvironment, +): TestFixtureInitializer.InitializedTestFixture> { + val sdk = env.prepareSdk() + sdk.persist() + return initialized(SdkFixture(sdk, env)) { + edtWriteAction { + ProjectJdkTable.getInstance().removeJdk(sdk) + env.close() + } + } +} \ No newline at end of file diff --git a/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/EnvTestPythonProviderExtension.kt b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/EnvTestPythonProviderExtension.kt new file mode 100644 index 000000000000..41bab630ef31 --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/EnvTestPythonProviderExtension.kt @@ -0,0 +1,57 @@ +// 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.test.env.junit5 + +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.util.Disposer +import com.intellij.python.community.services.systemPython.SystemPythonProvider +import com.intellij.python.test.env.common.EnvTestPythonProvider +import com.intellij.testFramework.registerExtension +import org.jetbrains.annotations.ApiStatus.Internal +import org.junit.jupiter.api.extension.BeforeAllCallback +import org.junit.jupiter.api.extension.ExtensionContext + +/** + * JUnit5 extension that registers [EnvTestPythonProvider] as an application-level service + * before all tests and automatically deregisters it after all tests complete. + * + * The provider is registered once per test class in the root context. + * + * Usage: + * ``` + * @ExtendWith(EnvTestPythonProviderExtension::class) + * class MyTest { + * @Test + * fun test() { + * // EnvTestPythonProvider is available as SystemPythonProvider + * } + * } + * ``` + */ +@Internal +class EnvTestPythonProviderExtension : BeforeAllCallback { + + override fun beforeAll(context: ExtensionContext) { + val rootContext = context.root + val namespace = ExtensionContext.Namespace.create(EnvTestPythonProviderExtension::class.java) + + rootContext.getStore(namespace).getOrComputeIfAbsent("provider") { + val factory = getOrCreatePyEnvironmentFactory(context) + val provider = EnvTestPythonProvider(factory) + val disposable = Disposer.newDisposable("EnvTestPythonProvider") + + ApplicationManager.getApplication().registerExtension( + SystemPythonProvider.EP, + provider, + disposable + ) + ProviderResource(disposable) + } + } + + private class ProviderResource(private val disposable: Disposable) : AutoCloseable { + override fun close() { + Disposer.dispose(disposable) + } + } +} diff --git a/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/PythonBinaryPathExtension.kt b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/PythonBinaryPathExtension.kt new file mode 100644 index 000000000000..ce45e0dd4b59 --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/PythonBinaryPathExtension.kt @@ -0,0 +1,26 @@ +// 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.test.env.junit5 + +import com.intellij.python.junit5Tests.framework.env.PythonBinaryPath +import com.jetbrains.python.PythonBinary +import org.jetbrains.annotations.ApiStatus.Internal +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.ParameterContext +import org.junit.jupiter.api.extension.ParameterResolver + +/** + * Extension that provides [PythonBinary] parameter annotated with [com.intellij.python.junit5Tests.framework.env.PythonBinaryPath]. + * Retrieves the Python environment initialized by [RunOnEnvironmentsExtension]. + */ +@Internal +internal class PythonBinaryPathExtension : ParameterResolver { + + override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean { + return parameterContext.parameter.type == PythonBinary::class.java && + parameterContext.parameter.isAnnotationPresent(PythonBinaryPath::class.java) + } + + override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): PythonBinary { + return RunOnEnvironmentsExtension.getPythonEnvironment(extensionContext).pythonPath + } +} diff --git a/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/PythonFactoryExtension.kt b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/PythonFactoryExtension.kt new file mode 100644 index 000000000000..4d0c8221e9fa --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/PythonFactoryExtension.kt @@ -0,0 +1,57 @@ +// 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.test.env.junit5 + +import com.intellij.python.test.env.common.createPyEnvironmentFactory +import com.intellij.python.test.env.core.PyEnvironmentFactory +import org.jetbrains.annotations.ApiStatus.Internal +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.ParameterContext +import org.junit.jupiter.api.extension.ParameterResolver + +/** + * Retrieves or creates a [PyEnvironmentFactory] from the root extension context. + * The factory is stored in the root context and shared across all tests. + * It will be automatically closed when the root context is closed (since it implements [AutoCloseable]). + * + * This utility function can be used by any JUnit5 extension or test that needs access to the factory. + * + * @param context The extension context from which to retrieve or create the factory + * @return The shared [PyEnvironmentFactory] instance + */ +@Internal +fun getOrCreatePyEnvironmentFactory(context: ExtensionContext): PyEnvironmentFactory { + val rootContext = context.root + val namespace = ExtensionContext.Namespace.create(PythonFactoryExtension::class.java) + return rootContext.getStore(namespace) + .getOrComputeIfAbsent("pyEnvironmentFactory", + { createPyEnvironmentFactory() }, + PyEnvironmentFactory::class.java) +} + +/** + * JUnit5 extension that manages [PyEnvironmentFactory] lifecycle. + * Creates a single factory instance in the root context and injects it into test parameters. + * The factory is automatically closed when the root context is closed. + * + * Usage: + * ``` + * @ExtendWith(PythonFactoryExtension::class) + * class MyTest { + * @Test + * fun test(factory: PyEnvironmentFactory) { + * // use factory + * } + * } + * ``` + */ +@Internal +class PythonFactoryExtension : ParameterResolver { + + override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean { + return parameterContext.parameter.type == PyEnvironmentFactory::class.java + } + + override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Any { + return getOrCreatePyEnvironmentFactory(extensionContext) + } +} diff --git a/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/PythonSdkExtension.kt b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/PythonSdkExtension.kt new file mode 100644 index 000000000000..7692ac9d306b --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/PythonSdkExtension.kt @@ -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.test.env.junit5 + +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.python.junit5Tests.framework.env.PythonSdk +import kotlinx.coroutines.runBlocking +import org.jetbrains.annotations.ApiStatus.Internal +import org.junit.jupiter.api.extension.AfterAllCallback +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.ParameterContext +import org.junit.jupiter.api.extension.ParameterResolver + +/** + * Extension that provides [Sdk] parameter annotated with [PythonSdk]. + * Retrieves the Python environment initialized by [RunOnEnvironmentsExtension] and creates SDK only once. + */ +@Internal +internal class PythonSdkExtension : ParameterResolver, AfterAllCallback { + + private val namespace = ExtensionContext.Namespace.create(PythonSdkExtension::class.java) + private val sdkKey = "pythonSdk" + + override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean { + return parameterContext.parameter.type == Sdk::class.java && + parameterContext.parameter.isAnnotationPresent(PythonSdk::class.java) + } + + override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Sdk { + return extensionContext.getStore(namespace).getOrComputeIfAbsent(sdkKey, { + createSdk(extensionContext) + }, Sdk::class.java) + } + + private fun createSdk(extensionContext: ExtensionContext): Sdk { + val pythonEnv = RunOnEnvironmentsExtension.getPythonEnvironment(extensionContext) + val sdk = runBlocking { + pythonEnv.prepareSdk() + } + return sdk + } + + override fun afterAll(context: ExtensionContext) { + context.getStore(namespace)?.remove(sdkKey) + } +} diff --git a/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/RequiresPoetryExtension.kt b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/RequiresPoetryExtension.kt new file mode 100644 index 000000000000..3dc3a3f183ec --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/RequiresPoetryExtension.kt @@ -0,0 +1,73 @@ +// 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.test.env.junit5 + +import com.intellij.ide.util.PropertiesComponent +import com.intellij.openapi.diagnostic.Logger +import com.intellij.python.community.impl.poetry.common.poetryPath +import com.intellij.python.junit5Tests.framework.resolvePythonTool +import com.intellij.python.test.env.core.PyEnvironment +import com.intellij.python.test.env.core.LATEST_PYTHON_VERSION +import com.intellij.python.test.env.plain.pythonEnvironment +import com.jetbrains.python.sdk.impl.resolvePythonHome +import kotlinx.coroutines.runBlocking +import org.jetbrains.annotations.ApiStatus.Internal +import org.junit.jupiter.api.extension.BeforeAllCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext +import org.opentest4j.TestAbortedException + +/** + * Extension that ensures Poetry tool is available and configured. + * + * This extension creates a Python environment with poetry library installed, + * stores it in the extension context, and configures the Poetry path in PropertiesComponent. + */ +@Internal +class RequiresPoetryExtension : BeforeAllCallback, BeforeEachCallback { + + companion object { + private val LOG = Logger.getInstance(RequiresPoetryExtension::class.java) + private val namespace = ExtensionContext.Namespace.create(RequiresPoetryExtension::class.java) + private const val POETRY_ENV_KEY = "poetryEnvironment" + } + + override fun beforeAll(context: ExtensionContext) { + configurePoetry(context) + } + + override fun beforeEach(context: ExtensionContext) { + configurePoetry(context) + } + + private fun configurePoetry(context: ExtensionContext) { + val store = context.getStore(namespace) + val poetryEnv = store.getOrComputeIfAbsent(POETRY_ENV_KEY, { + createPoetryEnvironment(context) + }, PyEnvironment::class.java) + + val pythonBinary = poetryEnv.pythonPath + val poetryPath = pythonBinary.resolvePythonHome().resolvePythonTool("poetry") + + PropertiesComponent.getInstance().poetryPath = poetryPath.toString() + LOG.info("Poetry configured at: $poetryPath") + } + + private fun createPoetryEnvironment(context: ExtensionContext): PyEnvironment { + val factory = getOrCreatePyEnvironmentFactory(context) + val envSpec = pythonEnvironment { + pythonVersion = LATEST_PYTHON_VERSION + libraries { + +"poetry==1.8.3" + } + } + + return runBlocking { + try { + factory.createEnvironment(envSpec) + } + catch (e: Exception) { + throw TestAbortedException("Failed to create Python environment with poetry: ${e.message}", e) + } + } + } +} diff --git a/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/RequiresUvExtension.kt b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/RequiresUvExtension.kt new file mode 100644 index 000000000000..4081808985a4 --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/RequiresUvExtension.kt @@ -0,0 +1,66 @@ +// 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.test.env.junit5 + +import com.intellij.ide.util.PropertiesComponent +import com.intellij.openapi.diagnostic.Logger +import com.intellij.python.test.env.core.PyEnvironment +import com.intellij.python.test.env.core.LATEST_PYTHON_VERSION +import com.intellij.python.test.env.uv.UvPyEnvironment +import com.intellij.python.test.env.uv.uvEnvironment +import kotlinx.coroutines.runBlocking +import org.jetbrains.annotations.ApiStatus.Internal +import org.junit.jupiter.api.extension.BeforeAllCallback +import org.junit.jupiter.api.extension.BeforeEachCallback +import org.junit.jupiter.api.extension.ExtensionContext +import org.opentest4j.TestAbortedException +import kotlin.io.path.pathString + +/** + * Extension that ensures Uv tool is available and configured. + * + * This extension creates an uv environment, stores it in the extension context, and configures the uv path in PropertiesComponent. + */ +@Internal +class RequiresUvExtension : BeforeAllCallback, BeforeEachCallback { + + companion object { + private val LOG = Logger.getInstance(RequiresUvExtension::class.java) + private val namespace = ExtensionContext.Namespace.create(RequiresUvExtension::class.java) + private const val UV_ENV_KEY = "uvEnvironment" + } + + override fun beforeAll(context: ExtensionContext) { + configurePoetry(context) + } + + override fun beforeEach(context: ExtensionContext) { + configurePoetry(context) + } + + private fun configurePoetry(context: ExtensionContext) { + val store = context.getStore(namespace) + val uvEnv = store.getOrComputeIfAbsent(UV_ENV_KEY, { + createUvEnvironment(context).unwrap() + }, UvPyEnvironment::class.java) + + PropertiesComponent.getInstance().setValue("PyCharm.Uv.Path", uvEnv.uvExecutable.pathString) + + LOG.info("Uv configured at: ${uvEnv.uvExecutable.pathString}") + } + + private fun createUvEnvironment(context: ExtensionContext): PyEnvironment { + val factory = getOrCreatePyEnvironmentFactory(context) + val envSpec = uvEnvironment("0.9.21") { + pythonVersion = LATEST_PYTHON_VERSION + } + + return runBlocking { + try { + factory.createEnvironment(envSpec) + } + catch (e: Exception) { + throw TestAbortedException("Failed to create uv environment: ${e.message}", e) + } + } + } +} diff --git a/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/RunOnEnvironmentsExtension.kt b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/RunOnEnvironmentsExtension.kt new file mode 100644 index 000000000000..4cd85f2530f8 --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/RunOnEnvironmentsExtension.kt @@ -0,0 +1,158 @@ +// 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.test.env.junit5 + +import com.intellij.execution.configurations.GeneralCommandLine +import com.intellij.execution.process.CapturingProcessHandler +import com.intellij.execution.process.ProcessNotCreatedException +import com.intellij.ide.util.PropertiesComponent +import com.intellij.openapi.diagnostic.Logger +import com.intellij.python.community.impl.pipenv.pipenvPath +import com.intellij.python.community.impl.poetry.common.poetryPath +import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase +import com.intellij.python.junit5Tests.framework.env.RunOnEnvironments +import com.intellij.python.junit5Tests.framework.resolvePythonTool +import com.intellij.python.test.env.core.PyEnvironment +import com.intellij.python.test.env.core.PyEnvironmentSpec +import com.intellij.util.containers.orNull +import com.jetbrains.python.PythonBinary +import com.jetbrains.python.sdk.impl.resolvePythonHome +import kotlinx.coroutines.runBlocking +import org.jetbrains.annotations.ApiStatus.Internal +import org.junit.jupiter.api.extension.ClassTemplateInvocationContext +import org.junit.jupiter.api.extension.ClassTemplateInvocationContextProvider +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.TestTemplateInvocationContext +import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider +import org.junit.platform.commons.support.AnnotationSupport +import java.nio.file.Path +import java.util.stream.Stream +import kotlin.streams.asStream + +/** + * Extension that initializes Python environment based on tags from [@PyEnvTestCase][PyEnvTestCase] annotation. + * Other extensions can retrieve the initialized environment from the extension context. + */ +@Internal +class RunOnEnvironmentsExtension : TestTemplateInvocationContextProvider, ClassTemplateInvocationContextProvider { + + companion object { + private val LOG = Logger.getInstance(RunOnEnvironmentsExtension::class.java) + private val checkedTools = mutableMapOf>() + + val namespace: ExtensionContext.Namespace = ExtensionContext.Namespace.create(RunOnEnvironmentsExtension::class.java) + const val envKey = "pythonEnv" + + fun getPythonEnvironment(extensionContext: ExtensionContext): PyEnvironment { + return extensionContext.getStore(namespace).get(envKey, PyEnvironment::class.java) + ?: getOrCreatePythonEnvironment(extensionContext) + } + + fun getOrCreatePythonEnvironment(extensionContext: ExtensionContext): PyEnvironment { + val annotation = findPyEnvTestCaseAnnotation(extensionContext) + ?: throw IllegalStateException("Python environment not initialized. Make sure @PyEnvTestCase is present.") + return getOrCreatePythonEnvironment(extensionContext, annotation.env.spec) + } + + private fun findRunOnEnvironmentAnnotation(context: ExtensionContext): RunOnEnvironments? = + context.testMethod.flatMap { + AnnotationSupport.findAnnotation(context.requiredTestMethod, RunOnEnvironments::class.java) + }.or { + AnnotationSupport.findAnnotation(context.requiredTestClass, RunOnEnvironments::class.java) + }.orNull() + + private fun findPyEnvTestCaseAnnotation(context: ExtensionContext): PyEnvTestCase? = + context.testMethod.flatMap { + AnnotationSupport.findAnnotation(context.requiredTestMethod, PyEnvTestCase::class.java) + }.or { + AnnotationSupport.findAnnotation(context.requiredTestClass, PyEnvTestCase::class.java) + }.orNull() + + private fun getOrCreatePythonEnvironment(context: ExtensionContext, spec: PyEnvironmentSpec<*>): PyEnvironment { + return context.getStore(namespace).getOrComputeIfAbsent(envKey, { + createPythonEnvironment(context, spec) + }, PyEnvironment::class.java) + } + + private fun createPythonEnvironment(context: ExtensionContext, envSpec: PyEnvironmentSpec<*>): PyEnvironment { + val factory = getOrCreatePyEnvironmentFactory(context) + val env = runBlocking { + factory.createEnvironment(envSpec) + } + + // Configure poetry, pipenv, and uv (only for non-conda environments) + val pythonBinary = env.pythonPath + PropertiesComponent.getInstance().poetryPath = checkAndGetToolPath(pythonBinary, "poetry", false) + PropertiesComponent.getInstance().pipenvPath = checkAndGetToolPath(pythonBinary, "pipenv", false) + + val uv = pythonBinary.resolvePythonHome().resolvePythonTool("uv") + PropertiesComponent.getInstance().setValue("PyCharm.Uv.Path", uv.toString()) + + return env + } + + private fun checkAndGetToolPath(env: PythonBinary, toolName: String, toThrow: Boolean): String? { + val tool = env.resolvePythonHome().resolvePythonTool(toolName) + if (checkedTools[toolName]?.contains(tool) != true) { + val output = try { + CapturingProcessHandler(GeneralCommandLine(tool.toString(), "--version")).runProcess(60_000, true) + } + catch (e: ProcessNotCreatedException) { + val message = "Tool ${toolName} not found at $tool. Make sure it's installed in the Python environment." + if (toThrow) { + throw AssertionError(message, e) + } + else { + LOG.warn(message) + return null + } + } + assert(output.exitCode == 0) { "$tool seems to be broken, output: $output. For Windows check `fix_path.cmd`" } + LOG.info("${toolName} found at $tool") + checkedTools.compute(toolName) { _, v -> (v ?: mutableSetOf()).also { it.add(tool) } } + } + + return tool.toString() + } + + } + + override fun supportsTestTemplate(context: ExtensionContext): Boolean { + val annotation = AnnotationSupport.findAnnotation(context.requiredTestMethod, PyEnvTestCase::class.java).orNull() + return annotation != null + } + + override fun provideTestTemplateInvocationContexts(context: ExtensionContext): Stream { + val annotation = findRunOnEnvironmentAnnotation(context) ?: error("No @RunOnEnvironment annotation found") + return annotation.envs.map { env -> + object : TestTemplateInvocationContext { + override fun getDisplayName(invocationIndex: Int): String { + return "Python ${env.spec.pythonVersion}" + } + + override fun prepareInvocation(context: ExtensionContext) { + getOrCreatePythonEnvironment(context, env.spec) + } + } + }.asSequence().asStream() + } + + override fun supportsClassTemplate(context: ExtensionContext): Boolean { + val annotation = AnnotationSupport.findAnnotation(context.requiredTestClass, RunOnEnvironments::class.java).orNull() + return annotation != null + } + + override fun provideClassTemplateInvocationContexts(context: ExtensionContext): Stream { + val annotation = AnnotationSupport.findAnnotation(context.requiredTestClass, RunOnEnvironments::class.java).orElseThrow() + return annotation.envs.map { env -> + object : ClassTemplateInvocationContext { + override fun getDisplayName(invocationIndex: Int): String { + return "Python ${env.spec.pythonVersion}" + } + + override fun prepareInvocation(context: ExtensionContext) { + getOrCreatePythonEnvironment(context, env.spec) + } + } + }.asSequence().asStream() + } +} diff --git a/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/conda/CondaPythonEnvExtension.kt b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/conda/CondaPythonEnvExtension.kt new file mode 100644 index 000000000000..98024384e6ee --- /dev/null +++ b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/conda/CondaPythonEnvExtension.kt @@ -0,0 +1,41 @@ +// 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.test.env.junit5.conda + +import com.intellij.python.community.junit5Tests.framework.conda.CondaEnv +import com.intellij.python.test.env.conda.CondaPyEnvironment +import com.intellij.python.test.env.junit5.RunOnEnvironmentsExtension +import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv +import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity +import org.jetbrains.annotations.ApiStatus.Internal +import org.junit.jupiter.api.extension.ExtensionContext +import org.junit.jupiter.api.extension.ParameterContext +import org.junit.jupiter.api.extension.ParameterResolver + +/** + * Extension that provides [PyCondaEnv] parameter annotated with [CondaEnv]. + * Retrieves the Conda environment initialized by [RunOnEnvironmentsExtension] with conda tags. + */ +@Internal +internal class CondaPythonEnvExtension : ParameterResolver { + + override fun supportsParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): Boolean { + return parameterContext.parameter.type == PyCondaEnv::class.java && + parameterContext.parameter.isAnnotationPresent(CondaEnv::class.java) + } + + override fun resolveParameter(parameterContext: ParameterContext, extensionContext: ExtensionContext): PyCondaEnv { + val pyEnv = RunOnEnvironmentsExtension.getPythonEnvironment(extensionContext) + + // Unwrap cached environment to get the real CondaPyEnvironment + val condaEnv = pyEnv.unwrap() + ?: throw IllegalStateException( + "Environment is not a Conda environment (actual type: ${pyEnv::class.qualifiedName}). " + + "Make sure @PyEnvTestCaseWithConda or @PyEnvTestCase(tags=[\"conda\"]) is used." + ) + + return PyCondaEnv( + envIdentity = PyCondaEnvIdentity.UnnamedEnv(condaEnv.envPath.toString(), isBase = false), + fullCondaPathOnTarget = condaEnv.condaExecutable.toString() + ) + } +} \ No newline at end of file diff --git a/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/impl/package-info.java b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/conda/package-info.java similarity index 75% rename from python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/impl/package-info.java rename to python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/conda/package-info.java index ccd3bc680022..3b53fa9458fb 100644 --- a/python/junit5Tests-framework/src/com/intellij/python/junit5Tests/framework/env/impl/package-info.java +++ b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/conda/package-info.java @@ -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. @ApiStatus.Internal -package com.intellij.python.junit5Tests.framework.env.impl; +package com.intellij.python.test.env.junit5.conda; import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/python/python-venv/tests/com/intellij/python/community/impl/venv/tests/pyVenvFixture.kt b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/pyVenvFixture.kt similarity index 75% rename from python/python-venv/tests/com/intellij/python/community/impl/venv/tests/pyVenvFixture.kt rename to python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/pyVenvFixture.kt index dabde2d3e534..769bf3dde68e 100644 --- a/python/python-venv/tests/com/intellij/python/community/impl/venv/tests/pyVenvFixture.kt +++ b/python/python-test-env/junit5/src/com/intellij/python/test/env/junit5/pyVenvFixture.kt @@ -1,41 +1,39 @@ // 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.impl.venv.tests +package com.intellij.python.test.env.junit5 import com.intellij.openapi.application.EDT import com.intellij.openapi.application.edtWriteAction import com.intellij.openapi.module.Module import com.intellij.openapi.projectRoots.ProjectJdkTable import com.intellij.openapi.projectRoots.Sdk -import com.intellij.openapi.roots.ModuleRootModificationUtil import com.intellij.python.community.impl.venv.createVenv -import com.intellij.python.community.testFramework.testEnv.TypeVanillaPython3 import com.intellij.python.junit5Tests.framework.env.SdkFixture +import com.intellij.python.test.env.core.PyEnvironment import com.intellij.testFramework.junit5.fixture.TestFixture import com.intellij.testFramework.junit5.fixture.testFixture -import com.jetbrains.python.PythonBinary import com.jetbrains.python.getOrThrow import com.jetbrains.python.sdk.persist import com.jetbrains.python.sdk.pythonSdk import com.jetbrains.python.sdk.setAssociationToModule +import com.jetbrains.python.tools.createSdk import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.nio.file.Path - /** * Create virtual env in [where]. If [addToSdkTable] then also added to the project jdk table */ -fun TestFixture>.pyVenvFixture( +fun TestFixture>.pyVenvFixture( where: TestFixture, addToSdkTable: Boolean, moduleFixture: TestFixture? = null, ): TestFixture = testFixture { - val basePython = this@pyVenvFixture.init().env + val env = this@pyVenvFixture.init().env withContext(Dispatchers.EDT) { val module = moduleFixture?.init() val venvDir = where.init() - val venvPython = createVenv(basePython, venvDir).getOrThrow() - val venvSdk = withContext(Dispatchers.IO){ TypeVanillaPython3.createSdk(venvPython)} + val venvPython = createVenv(env.pythonPath, venvDir).getOrThrow() + val venvSdk = withContext(Dispatchers.IO) { createSdk(venvPython) } if (addToSdkTable) { venvSdk.persist() if (module != null) { @@ -49,5 +47,4 @@ fun TestFixture>.pyVenvFixture( } } } -} - +} \ No newline at end of file diff --git a/python/junit5Tests-framework/conda/src/com/intellij/python/junit5Tests/env/conda/showCase/PyEnvWithCondaShowCaseTest.kt b/python/python-test-env/junit5/test/com/intellij/python/test/env/junit5/conda/showcase/PyEnvWithCondaShowCaseTest.kt similarity index 95% rename from python/junit5Tests-framework/conda/src/com/intellij/python/junit5Tests/env/conda/showCase/PyEnvWithCondaShowCaseTest.kt rename to python/python-test-env/junit5/test/com/intellij/python/test/env/junit5/conda/showcase/PyEnvWithCondaShowCaseTest.kt index 375ec87e4d2f..92c110b40b8d 100644 --- a/python/junit5Tests-framework/conda/src/com/intellij/python/junit5Tests/env/conda/showCase/PyEnvWithCondaShowCaseTest.kt +++ b/python/python-test-env/junit5/test/com/intellij/python/test/env/junit5/conda/showcase/PyEnvWithCondaShowCaseTest.kt @@ -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.intellij.python.junit5Tests.env.conda.showCase +package com.intellij.python.test.env.junit5.conda.showcase import com.intellij.python.community.junit5Tests.framework.conda.CondaEnv import com.intellij.python.community.junit5Tests.framework.conda.PyEnvTestCaseWithConda diff --git a/python/python-test-env/junit5/test/com/intellij/python/test/env/junit5/conda/showcase/RunOnEnvironmentsShowCaseTest.kt b/python/python-test-env/junit5/test/com/intellij/python/test/env/junit5/conda/showcase/RunOnEnvironmentsShowCaseTest.kt new file mode 100644 index 000000000000..ff4340aba01c --- /dev/null +++ b/python/python-test-env/junit5/test/com/intellij/python/test/env/junit5/conda/showcase/RunOnEnvironmentsShowCaseTest.kt @@ -0,0 +1,40 @@ +// 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.test.env.junit5.conda.showcase + +import com.intellij.python.community.impl.venv.createVenv +import com.intellij.python.junit5Tests.framework.env.BeforeRunOnEnvironmentInvocation +import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase +import com.intellij.python.junit5Tests.framework.env.PythonBinaryPath +import com.intellij.python.junit5Tests.framework.env.RunOnEnvironments +import com.intellij.python.test.env.common.PredefinedPyEnvironments +import kotlinx.coroutines.runBlocking +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.io.TempDir +import java.nio.file.Path +import kotlin.io.path.exists +import kotlin.io.path.isExecutable +import kotlin.io.path.isRegularFile + +@PyEnvTestCase +@RunOnEnvironments(PredefinedPyEnvironments.VANILLA_3_13, PredefinedPyEnvironments.VANILLA_3_14) +class RunOnEnvironmentsShowCaseTest { + + @BeforeRunOnEnvironmentInvocation + fun init(@PythonBinaryPath python: Path, @TempDir tempDir: Path) { + runBlocking { + createVenv(python, tempDir) + } + } + + @Test + fun checkPythonPath(@PythonBinaryPath python: Path) { + ensurePythonWorks(python) + } + + private fun ensurePythonWorks(python: Path) { + Assertions.assertTrue(python.exists(), "$python doesn't exist") + Assertions.assertTrue(python.isRegularFile(), "$python isn't file") + Assertions.assertTrue(python.isExecutable(), "$python isn't executable") + } +} \ No newline at end of file diff --git a/python/python-test-env/plain/BUILD.bazel b/python/python-test-env/plain/BUILD.bazel new file mode 100644 index 000000000000..ed4ba84cba2d --- /dev/null +++ b/python/python-test-env/plain/BUILD.bazel @@ -0,0 +1,70 @@ +### auto-generated section `build intellij.python.test.env.plain` start +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +resourcegroup( + name = "plain_test_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "plain", + visibility = ["//visibility:public"], + srcs = glob([], allow_empty = True), + runtime_deps = [ + "@lib//:jetbrains-annotations", + "//python/python-test-env/core", + "//python/openapi:community", + "//python/python-venv:community-impl-venv", + "//platform/core-api:core", + "//platform/projectModel-api:projectModel", + "//python/python-sdk:sdk", + "@lib//:kotlin-stdlib", + "//platform/analysis-api:analysis", + "//platform/lang-impl", + "//python:python-community-impl", + "//platform/ide-core", + "//platform/lang-core", + "//python/testFramework", + ] +) + +jvm_library( + name = "plain_test_lib", + module_name = "intellij.python.test.env.plain", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [":plain_test_resources"], + deps = [ + "@lib//:jetbrains-annotations", + "//python/python-test-env/core", + "//python/python-test-env/core:core_test_lib", + "//python/openapi:community", + "//python/openapi:community_test_lib", + "//python/python-venv:community-impl-venv", + "//platform/core-api:core", + "//platform/projectModel-api:projectModel", + "//python/python-sdk:sdk", + "//python/python-sdk:sdk_test_lib", + "@lib//:kotlin-stdlib", + "//platform/analysis-api:analysis", + "//platform/lang-impl", + "//python:python-community-impl", + "//platform/ide-core", + "//platform/lang-core", + "//python/testFramework", + "//python/python-exec-service:community-execService", + "//python/python-exec-service:community-execService_test_lib", + ], + runtime_deps = [":plain"] +) +### auto-generated section `build intellij.python.test.env.plain` end + +### auto-generated section `test intellij.python.test.env.plain` start +load("@community//build:tests-options.bzl", "jps_test") + +jps_test( + name = "plain_test", + runtime_deps = [":plain_test_lib"] +) +### auto-generated section `test intellij.python.test.env.plain` end \ No newline at end of file diff --git a/python/python-test-env/plain/intellij.python.test.env.plain.iml b/python/python-test-env/plain/intellij.python.test.env.plain.iml new file mode 100644 index 000000000000..59af5314caa9 --- /dev/null +++ b/python/python-test-env/plain/intellij.python.test.env.plain.iml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/python/python-test-env/plain/resources/intellij.python.test.env.plain.xml b/python/python-test-env/plain/resources/intellij.python.test.env.plain.xml new file mode 100644 index 000000000000..1a2d86c2b9cc --- /dev/null +++ b/python/python-test-env/plain/resources/intellij.python.test.env.plain.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/python/python-test-env/plain/src/com/intellij/python/test/env/plain/PlainPyEnvironmentProvider.kt b/python/python-test-env/plain/src/com/intellij/python/test/env/plain/PlainPyEnvironmentProvider.kt new file mode 100644 index 000000000000..67589a30eedc --- /dev/null +++ b/python/python-test-env/plain/src/com/intellij/python/test/env/plain/PlainPyEnvironmentProvider.kt @@ -0,0 +1,163 @@ +// 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.test.env.plain + +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.python.test.env.core.* +import com.intellij.util.io.sanitizeFileName +import com.intellij.util.system.OS +import com.jetbrains.python.PythonBinary +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jetbrains.annotations.ApiStatus +import java.net.URLDecoder.decode +import java.nio.file.Path +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.deleteRecursively +import kotlin.io.path.exists +import kotlin.io.path.fileSize + +/** + * Environment provider for plain Python (no venv). + * + * This provider: + * 1. Downloads Python from python-build-standalone for the current OS + * 2. If no libraries needed: Extracts to ~/.cache/python// and returns cached Python + * 3. If libraries needed: Extracts to plain_x/ and installs libraries there + * + * Downloads Python from: https://github.com/astral-sh/python-build-standalone/releases + * + */ +@ApiStatus.Internal +class PlainPyEnvironmentProvider : PyEnvironmentProvider("plain") { + + override suspend fun setupEnvironment(context: Context, spec: PlainPyEnvironmentSpec): PyEnvironment { + val logger = thisLogger() + logger.info("Setting up plain Python environment") + logger.info("Python version: ${spec.pythonVersion}") + + val archive = downloadPython(spec.pythonVersion.toString()) + + if (spec.libraries.isEmpty()) { + logger.info("No libraries specified, using cached Python") + val cachedPython = setupPython(archive, getCacheDir(archive)) + logger.info("Plain Python path: $cachedPython") + logger.info("Plain Python environment setup completed") + return PlainPyEnvironment(cachedPython, cachedPython.parent.parent, false) + } else { + val targetPath = nextEnvPath(context.workingDir) + logger.info("Setting up plain Python with libraries at: $targetPath") + val python = setupPython(archive, targetPath) + logger.info("Plain Python path: $python") + logger.info("Installing libraries: ${spec.libraries.joinToString(", ")}") + installLibraries(python, spec.libraries) + logger.info("Libraries installed successfully") + logger.info("Plain Python environment setup completed at: $targetPath") + return PlainPyEnvironment(python, targetPath, true) + } + } + + private suspend fun downloadPython(pythonVersion: String): Path { + val logger = thisLogger() + val downloadInfo = PyVersionMapping.getDownloadInfo(pythonVersion) + val archiveFileName = downloadInfo.url.substringAfterLast('/') + + val decodedFileName = decode(archiveFileName, "UTF-8") + val sanitizedFileName = sanitizeFileName(decodedFileName, replacement = "_", truncateIfNeeded = false) + + logger.info("Downloading Python archive: $sanitizedFileName (from URL: $archiveFileName)") + val archive = PyEnvDownloadCache.getOrDownload(downloadInfo.url, sanitizedFileName) + logger.info("Python archive downloaded: $archive") + + // Verify size and SHA256 + logger.info("Verifying download integrity") + verifyDownload(archive, downloadInfo.size, downloadInfo.sha256) + logger.info("Download verification successful") + + return archive + } + + private suspend fun verifyDownload(file: Path, expectedSize: Long, expectedSha256: String) = withContext(Dispatchers.IO) { + // Check size first (faster) + val actualSize = file.fileSize() + if (actualSize != expectedSize) { + error("File size mismatch for $file. Expected: $expectedSize bytes, Actual: $actualSize bytes") + } + + // Then verify SHA256 + val actualSha256 = com.intellij.util.io.sha256Hex(file) + if (!actualSha256.equals(expectedSha256, ignoreCase = true)) { + error("SHA256 checksum mismatch for $file. Expected: $expectedSha256, Actual: $actualSha256") + } + } + + private fun getCacheDir(archive: Path): Path { + val fileName = archive.fileName.toString() + val archiveBaseName = when { + fileName.endsWith(".tar.gz", ignoreCase = true) -> fileName.substringBeforeLast(".tar.gz") + fileName.endsWith(".zip", ignoreCase = true) -> fileName.substringBeforeLast(".zip") + else -> fileName + } + return PyEnvDownloadCache.cacheDirectory().resolve("python").resolve(archiveBaseName) + } + + private suspend fun installLibraries(pythonPath: Path, libraries: List) { + installPipPackages(pythonPath, libraries, thisLogger()) + } + + /** + * Extracts Python archive to the specified directory. + */ + private suspend fun setupPython(archive: Path, targetDir: Path): Path { + val logger = thisLogger() + + extractIfNecessary(targetDir, logger) { target -> + unpackArchive(archive, target) + val pythonPath = findPythonExecutableInInstall(target) + markExecutable(logger, pythonPath) + } + + return findPythonExecutableInInstall(targetDir) + } + + /** + * Find Python executable in the extracted installation. + */ + private fun findPythonExecutableInInstall(pythonDir: Path): Path { + if (!pythonDir.exists()) { + error("Python directory does not exist: $pythonDir") + } + + val possiblePaths = if (OS.CURRENT == OS.Windows) { + listOf( + pythonDir.resolve("python.exe"), + pythonDir.resolve("Scripts").resolve("python.exe"), + pythonDir.resolve("bin").resolve("python.exe") + ) + } else { + listOf( + pythonDir.resolve("bin").resolve("python3"), + pythonDir.resolve("bin").resolve("python"), + pythonDir.resolve("python") + ) + } + + return possiblePaths.firstOrNull { it.exists() } + ?: error("Python executable not found in $pythonDir. Tried: ${possiblePaths.joinToString(", ")}") + } + + /** + * Plain Python environment implementation + */ + private class PlainPyEnvironment( + override val pythonPath: PythonBinary, + override val envPath: Path, + private val cleanUpOnClose: Boolean, + ) : PyEnvironment { + @OptIn(ExperimentalPathApi::class) + override fun close() { + if (cleanUpOnClose) { + envPath.deleteRecursively() + } + } + } +} diff --git a/python/python-test-env/plain/src/com/intellij/python/test/env/plain/PlainPyEnvironmentSpec.kt b/python/python-test-env/plain/src/com/intellij/python/test/env/plain/PlainPyEnvironmentSpec.kt new file mode 100644 index 000000000000..6540c3b111fc --- /dev/null +++ b/python/python-test-env/plain/src/com/intellij/python/test/env/plain/PlainPyEnvironmentSpec.kt @@ -0,0 +1,49 @@ +// 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.test.env.plain + +import com.intellij.python.test.env.core.CacheKey +import com.intellij.python.test.env.core.PyEnvironmentSpec +import org.jetbrains.annotations.ApiStatus + +/** + * Specification for plain Python venv environment. + * + * Example usage: + * ```kotlin + * val env = pythonEnvironment { + * pythonVersion = "3.11" + * libraries { + * +"numpy==2.0.2" + * +"pandas>=1.5.0" + * } + * } + * ``` + */ +@ApiStatus.Internal +class PlainPyEnvironmentSpec : PyEnvironmentSpec() { + + override fun toCacheKey(): CacheKey { + return buildCacheKey("plain") + } +} + +/** + * DSL entry point for creating plain Python venv environment specifications. + * + * Example: + * ```kotlin + * val env = pythonEnvironment { + * pythonVersion = "3.11" + * libraries { + * +"numpy==2.0.2" + * +"pandas>=1.5.0" + * } + * } + * ``` + */ +@ApiStatus.Internal +fun pythonEnvironment(block: PlainPyEnvironmentSpec.() -> Unit): PlainPyEnvironmentSpec { + val spec = PlainPyEnvironmentSpec() + spec.block() + return spec +} \ No newline at end of file diff --git a/python/python-test-env/plain/src/com/intellij/python/test/env/plain/VirtualenvPyEnvironmentProvider.kt b/python/python-test-env/plain/src/com/intellij/python/test/env/plain/VirtualenvPyEnvironmentProvider.kt new file mode 100644 index 000000000000..d68f15401304 --- /dev/null +++ b/python/python-test-env/plain/src/com/intellij/python/test/env/plain/VirtualenvPyEnvironmentProvider.kt @@ -0,0 +1,82 @@ +// 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.test.env.plain + +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.python.community.execService.asBinToExec +import com.intellij.python.community.impl.venv.createVenv +import com.intellij.python.test.env.core.PyEnvironment +import com.intellij.python.test.env.core.PyEnvironmentProvider +import com.intellij.python.test.env.core.installPipPackages +import com.jetbrains.python.PythonBinary +import com.jetbrains.python.getOrThrow +import com.jetbrains.python.venvReader.VirtualEnvReader +import org.jetbrains.annotations.ApiStatus +import java.nio.file.Path +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.deleteRecursively + +/** + * Environment provider for plain Python with venv. + * + * This provider: + * 1. Downloads Python from python-build-standalone for the current OS + * 2. Extracts Python to workingDir/python// + * 3. Creates a virtual environment using Python's built-in venv module + * 4. Installs required libraries using pip + * 5. Returns a PyEnvironment abstraction for further operations + * + * Downloads Python from: https://github.com/astral-sh/python-build-standalone/releases + * + */ +@ApiStatus.Internal +class VirtualenvPyEnvironmentProvider : PyEnvironmentProvider("venv") { + + override suspend fun setupEnvironment(context: Context, spec: VirtualenvPyEnvironmentSpec): PyEnvironment { + val logger = thisLogger() + val targetPath = nextEnvPath(context.workingDir) + + logger.info("Setting up virtual environment at: $targetPath") + logger.info("Python version: ${spec.pythonVersion}") + + logger.info("Creating base Python environment") + val basePythonEnvironment = context.factory.createEnvironment(pythonEnvironment { pythonVersion = spec.pythonVersion }) + logger.info("Base Python path: ${basePythonEnvironment.pythonPath}") + + logger.info("Creating virtual environment") + createVenv( + python = basePythonEnvironment.pythonPath.asBinToExec(), + venvDir = targetPath.toString(), + inheritSitePackages = false + ).getOrThrow() + + val venvPython = VirtualEnvReader.Instance.findPythonInPythonRoot(targetPath) + ?: error("Failed to find Python executable in virtual environment directory: $targetPath") + logger.info("Virtual environment Python path: $venvPython") + + if (spec.libraries.isNotEmpty()) { + logger.info("Installing libraries: ${spec.libraries.joinToString(", ")}") + installLibraries(venvPython, spec.libraries) + logger.info("Libraries installed successfully") + } + + logger.info("Virtual environment setup completed at: $targetPath") + return VirtualenvPyEnvironment(venvPython, targetPath) + } + + private suspend fun installLibraries(pythonPath: Path, libraries: List) { + installPipPackages(pythonPath, libraries, thisLogger()) + } + + /** + * Plain Python environment implementation + */ + private class VirtualenvPyEnvironment( + override val pythonPath: PythonBinary, + override val envPath: Path, + ) : PyEnvironment { + @OptIn(ExperimentalPathApi::class) + override fun close() { + envPath.deleteRecursively() + } + } +} diff --git a/python/python-test-env/plain/src/com/intellij/python/test/env/plain/VirtualenvPyEnvironmentSpec.kt b/python/python-test-env/plain/src/com/intellij/python/test/env/plain/VirtualenvPyEnvironmentSpec.kt new file mode 100644 index 000000000000..7c5801ef47f1 --- /dev/null +++ b/python/python-test-env/plain/src/com/intellij/python/test/env/plain/VirtualenvPyEnvironmentSpec.kt @@ -0,0 +1,49 @@ +// 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.test.env.plain + +import com.intellij.python.test.env.core.CacheKey +import com.intellij.python.test.env.core.PyEnvironmentSpec +import org.jetbrains.annotations.ApiStatus + +/** + * Specification for plain Python venv environment. + * + * Example usage: + * ```kotlin + * val env = pythonEnvironment { + * pythonVersion = "3.11" + * libraries { + * +"numpy==2.0.2" + * +"pandas>=1.5.0" + * } + * } + * ``` + */ +@ApiStatus.Internal +class VirtualenvPyEnvironmentSpec : PyEnvironmentSpec() { + + override fun toCacheKey(): CacheKey { + return buildCacheKey("venv") + } +} + +/** + * DSL entry point for creating plain Python venv environment specifications. + * + * Example: + * ```kotlin + * val env = venvEnvironment { + * pythonVersion = "3.11" + * libraries { + * +"numpy==2.0.2" + * +"pandas>=1.5.0" + * } + * } + * ``` + */ +@ApiStatus.Internal +fun venvEnvironment(block: VirtualenvPyEnvironmentSpec.() -> Unit): VirtualenvPyEnvironmentSpec { + val spec = VirtualenvPyEnvironmentSpec() + spec.block() + return spec +} \ No newline at end of file diff --git a/python/python-test-env/uv/BUILD.bazel b/python/python-test-env/uv/BUILD.bazel new file mode 100644 index 000000000000..556f99b3bf1b --- /dev/null +++ b/python/python-test-env/uv/BUILD.bazel @@ -0,0 +1,70 @@ +### auto-generated section `build intellij.python.test.env.uv` start +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +resourcegroup( + name = "uv_test_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) + +jvm_library( + name = "uv", + visibility = ["//visibility:public"], + srcs = glob([], allow_empty = True), + runtime_deps = [ + "//python/python-test-env/core", + "@lib//:jetbrains-annotations", + "//python/openapi:community", + "@lib//:kotlin-stdlib", + "//platform/util", + "@lib//:kotlinx-coroutines-core", + "//platform/analysis-api:analysis", + "//platform/projectModel-api:projectModel", + "//platform/lang-impl", + "//python:python-community-impl", + "//platform/core-api:core", + "//python/python-sdk:sdk", + "//platform/ide-core", + "//platform/lang-core", + "//python/testFramework", + ] +) + +jvm_library( + name = "uv_test_lib", + module_name = "intellij.python.test.env.uv", + visibility = ["//visibility:public"], + srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [":uv_test_resources"], + deps = [ + "//python/python-test-env/core", + "//python/python-test-env/core:core_test_lib", + "@lib//:jetbrains-annotations", + "//python/openapi:community", + "//python/openapi:community_test_lib", + "@lib//:kotlin-stdlib", + "//platform/util", + "@lib//:kotlinx-coroutines-core", + "//platform/analysis-api:analysis", + "//platform/projectModel-api:projectModel", + "//platform/lang-impl", + "//python:python-community-impl", + "//platform/core-api:core", + "//python/python-sdk:sdk", + "//python/python-sdk:sdk_test_lib", + "//platform/ide-core", + "//platform/lang-core", + "//python/testFramework", + ], + runtime_deps = [":uv"] +) +### auto-generated section `build intellij.python.test.env.uv` end + +### auto-generated section `test intellij.python.test.env.uv` start +load("@community//build:tests-options.bzl", "jps_test") + +jps_test( + name = "uv_test", + runtime_deps = [":uv_test_lib"] +) +### auto-generated section `test intellij.python.test.env.uv` end \ No newline at end of file diff --git a/python/setup-test-environment/intellij.python.community.testFramework.testEnv.iml b/python/python-test-env/uv/intellij.python.test.env.uv.iml similarity index 68% rename from python/setup-test-environment/intellij.python.community.testFramework.testEnv.iml rename to python/python-test-env/uv/intellij.python.test.env.uv.iml index 9a82b79d6884..2f741f6ff33d 100644 --- a/python/setup-test-environment/intellij.python.community.testFramework.testEnv.iml +++ b/python/python-test-env/uv/intellij.python.test.env.uv.iml @@ -3,21 +3,25 @@ - - + + - - - - - + + - - - + + + + + - + + + + + + \ No newline at end of file diff --git a/python/setup-test-environment/resources/intellij.python.community.testFramework.testEnv.xml b/python/python-test-env/uv/resources/intellij.python.test.env.uv.xml similarity index 55% rename from python/setup-test-environment/resources/intellij.python.community.testFramework.testEnv.xml rename to python/python-test-env/uv/resources/intellij.python.test.env.uv.xml index 22a71a08a54d..b2d536bc1e85 100644 --- a/python/setup-test-environment/resources/intellij.python.community.testFramework.testEnv.xml +++ b/python/python-test-env/uv/resources/intellij.python.test.env.uv.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/python/python-test-env/uv/src/com/intellij/python/test/env/uv/UvPyEnvironmentProvider.kt b/python/python-test-env/uv/src/com/intellij/python/test/env/uv/UvPyEnvironmentProvider.kt new file mode 100644 index 000000000000..c5611f550e1f --- /dev/null +++ b/python/python-test-env/uv/src/com/intellij/python/test/env/uv/UvPyEnvironmentProvider.kt @@ -0,0 +1,219 @@ +// 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.test.env.uv + +import com.intellij.openapi.diagnostic.thisLogger +import com.intellij.python.test.env.core.* +import com.intellij.python.test.env.core.extractIfNecessary +import com.intellij.util.system.CpuArch +import com.intellij.util.system.OS +import com.jetbrains.python.PythonBinary +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.jetbrains.annotations.ApiStatus +import java.nio.file.Path +import kotlin.io.path.* + +/** + * Specification for UV Python environment with mandatory version pinning. + * + * @property uvVersion Specific UV version (e.g., "0.5.11") + */ +@ApiStatus.Internal +class UvPyEnvironmentSpec( + val uvVersion: String, +) : PyEnvironmentSpec() { + + override fun toCacheKey(): CacheKey { + return buildCacheKey("uv", "uv:$uvVersion") + } +} + +/** + * DSL entry point for creating uv environment specifications. + * + * Version must be specified explicitly - no default or "latest" option. + * + * Example: + * ```kotlin + * val env = uvEnvironment("0.9.21") { + * pythonVersion = LATEST_PYTHON_VERSION + * libraries { + * +"numpy==2.0.2" + * } + * } + * ``` + */ +@ApiStatus.Internal +fun uvEnvironment(uvVersion: String, block: UvPyEnvironmentSpec.() -> Unit): UvPyEnvironmentSpec { + val spec = UvPyEnvironmentSpec(uvVersion) + spec.block() + return spec +} + +/** + * Environment provider for UV-managed Python environments. + * + * This provider: + * 1. Downloads UV binary for the current OS + * 2. Unpacks it to workingDir/uv// + * 3. Uses UV to download and setup Python with the requested version + * 4. Installs required libraries using UV + * 5. Creates an SDK from the environment + * + */ +@ApiStatus.Internal +class UvPyEnvironmentProvider : PyEnvironmentProvider("uv") { + + private companion object { + const val UV_GITHUB_RELEASE_URL = "https://github.com/astral-sh/uv/releases" + } + + override suspend fun setupEnvironment(context: Context, spec: UvPyEnvironmentSpec): PyEnvironment { + val logger = thisLogger() + val targetPath = nextEnvPath(context.workingDir) + + logger.info("Setting up UV environment at: $targetPath") + logger.info("UV version: ${spec.uvVersion}") + logger.info("Python version: ${spec.pythonVersion}") + + val uvExecutable = downloadAndSetupUv(spec) + logger.info("UV executable: $uvExecutable") + + val fullPythonVersion = spec.pythonVersion.toString() + + logger.info("Installing Python with UV") + val pythonPath = installPythonWithUv(uvExecutable, targetPath, fullPythonVersion) + logger.info("UV Python path: $pythonPath") + + if (spec.libraries.isNotEmpty()) { + logger.info("Installing libraries: ${spec.libraries.joinToString(", ")}") + installLibraries(uvExecutable, pythonPath, spec.libraries) + logger.info("Libraries installed successfully") + } + + logger.info("UV environment setup completed at: $targetPath") + return UvPyEnvironment(pythonPath, targetPath, uvExecutable) + } + + private suspend fun installLibraries(uvExecutable: Path, pythonPath: Path, libraries: List) { + val logger = thisLogger() + + if (libraries.isEmpty()) { + logger.info("No libraries to install") + return + } + + val command = listOf(uvExecutable.pathString, "pip", "install", "--python", pythonPath.pathString) + libraries + executeProcess(command, logger, "uv pip") + } + + /** + * Downloads and unpacks UV binary for the current OS to cache directory. + * Directory name includes archive name to handle different OS/architecture variants. + * Returns path to UV executable. + */ + private suspend fun downloadAndSetupUv(spec: UvPyEnvironmentSpec): Path { + val logger = thisLogger() + // Determine platform-specific download URL + val downloadUrl = getUvDownloadUrl(spec.uvVersion) + val archiveFileName = downloadUrl.substringAfterLast('/') + // Use archive name (without extension) as directory name to handle different variants + val archiveBaseName = archiveFileName.substringBeforeLast(".tar.gz").substringBeforeLast(".zip") + val uvDir = PyEnvDownloadCache.cacheDirectory().resolve("uv").resolve(archiveBaseName) + val uvExecutable = uvDir.resolve(if (OS.CURRENT == OS.Windows) "uv.exe" else "uv") + + extractIfNecessary(uvDir, logger) { target -> + logger.info("Downloading UV archive: $archiveFileName") + val cachedArchive = PyEnvDownloadCache.getOrDownload(downloadUrl, archiveFileName) + logger.info("UV archive downloaded: $cachedArchive") + // Extract UV binary directly from cached archive + // UV archives are structured as: uv-/uv or uv-/uv.exe + // We need to strip the first directory component + val archivePrefix = archiveBaseName // e.g., "uv-x86_64-unknown-linux-gnu" + logger.info("Extracting UV binary (stripping prefix: $archivePrefix)") + unpackArchive(cachedArchive, target, prefixToStrip = archivePrefix) + + markExecutable(logger, uvExecutable) + } + + return uvExecutable + } + + /** + * Uses UV to install Python with the specified version. + */ + private suspend fun installPythonWithUv( + uvExecutable: Path, + envPath: Path, + pythonVersion: String, + ): Path { + val logger = thisLogger() + return withContext(Dispatchers.IO) { + logger.info("Creating environment directory: $envPath") + envPath.createDirectories() + + // Use uv to create a venv with specific Python version + // uv will download Python if not already available + val command = listOf( + uvExecutable.pathString, + "venv", + envPath.pathString, + "--python", + pythonVersion + ) + + executeProcess(command, logger, "uv venv") + + logger.info("UV venv created successfully") + + // Find Python executable in created venv + logger.info("Looking for Python executable in: $envPath") + val pythonPath = if (OS.CURRENT == OS.Windows) { + envPath.resolve("Scripts").resolve("python.exe") + } + else { + envPath.resolve("bin").resolve("python") + } + + if (!pythonPath.exists()) { + error("Python executable not found in UV venv: ${pythonPath.pathString}") + } + + logger.info("Found Python executable: $pythonPath") + + pythonPath + } + } + + private fun getUvDownloadUrl(version: String): String { + val arch = when { + CpuArch.isArm64() -> "aarch64" + CpuArch.isIntel64() -> "x86_64" + else -> "x86_64" + } + + val platform = when (OS.CURRENT) { + OS.Windows -> "$arch-pc-windows-msvc" + OS.macOS -> "$arch-apple-darwin" + OS.Linux -> "$arch-unknown-linux-gnu" + else -> throw IllegalStateException("Unsupported OS: ${OS.CURRENT}") + } + + return "$UV_GITHUB_RELEASE_URL/download/$version/uv-$platform.tar.gz" + } + + /** + * UV Python environment implementation + */ +} + +class UvPyEnvironment( + override val pythonPath: PythonBinary, + override val envPath: Path, + val uvExecutable: Path +) : PyEnvironment { + @OptIn(ExperimentalPathApi::class) + override fun close() { + envPath.deleteRecursively() + } +} diff --git a/python/python-venv/BUILD.bazel b/python/python-venv/BUILD.bazel index 0e698000cd09..ac3a0cc83d17 100644 --- a/python/python-venv/BUILD.bazel +++ b/python/python-venv/BUILD.bazel @@ -36,22 +36,15 @@ jvm_library( jvm_library( name = "community-impl-venv_test_lib", + module_name = "intellij.python.community.impl.venv", visibility = ["//visibility:public"], - srcs = glob(["tests/**/*.kt", "tests/**/*.java", "tests/**/*.form"], allow_empty = True), + srcs = glob([], allow_empty = True), resources = [":community-impl-venv_test_resources"], - associates = [":community-impl-venv"], - deps = [ - "@lib//:kotlin-stdlib", - "@lib//:jetbrains-annotations", - "//python/openapi:community", + runtime_deps = [ + ":community-impl-venv", "//python/openapi:community_test_lib", - "//python/python-exec-service:community-execService", "//python/python-exec-service:community-execService_test_lib", - "//libraries/kotlinx/coroutines/core", - "//python/python-sdk:sdk", "//python/python-sdk:sdk_test_lib", - "//platform/projectModel-api:projectModel", - "//platform/util", "//python/junit5Tests-framework:community-junit5Tests-framework_test_lib", "//platform/testFramework/common", "//platform/testFramework/junit5", @@ -59,19 +52,9 @@ jvm_library( "@lib//:junit5", "//platform/ide-core-impl", "//platform/execution", - "//platform/core-api:core", - "//python/python-exec-service/execService.python", - "//python/python-exec-service/execService.python:execService.python_test_lib", - "//platform/eel-provider", + "//platform/lang-impl", + "//platform/lang-core", + "//python/testFramework", ] ) -### auto-generated section `build intellij.python.community.impl.venv` end - -### auto-generated section `test intellij.python.community.impl.venv` start -load("@community//build:tests-options.bzl", "jps_test") - -jps_test( - name = "community-impl-venv_test", - runtime_deps = [":community-impl-venv_test_lib"] -) -### auto-generated section `test intellij.python.community.impl.venv` end \ No newline at end of file +### auto-generated section `build intellij.python.community.impl.venv` end \ No newline at end of file diff --git a/python/python-venv/intellij.python.community.impl.venv.iml b/python/python-venv/intellij.python.community.impl.venv.iml index b4d423e7815b..cba86270745b 100644 --- a/python/python-venv/intellij.python.community.impl.venv.iml +++ b/python/python-venv/intellij.python.community.impl.venv.iml @@ -5,7 +5,6 @@ - @@ -27,5 +26,8 @@ + + + \ No newline at end of file diff --git a/python/services/internal-impl/BUILD.bazel b/python/services/internal-impl/BUILD.bazel index 0befab1b79c0..b289741bd67a 100644 --- a/python/services/internal-impl/BUILD.bazel +++ b/python/services/internal-impl/BUILD.bazel @@ -56,7 +56,6 @@ jvm_library( "//python/python-sdk:sdk_test_lib", "//python/junit5Tests-framework:community-junit5Tests-framework_test_lib", "//python/python-exec-service/execService.python", - "//python/python-exec-service/execService.python:execService.python_test_lib", ] ) ### auto-generated section `build intellij.python.community.services.internal.impl` end diff --git a/python/services/shared/BUILD.bazel b/python/services/shared/BUILD.bazel index a5875000ffa7..0892ac1d2bf4 100644 --- a/python/services/shared/BUILD.bazel +++ b/python/services/shared/BUILD.bazel @@ -48,7 +48,6 @@ jvm_library( "//platform/testFramework/junit5:junit5_test_lib", "//libraries/hamcrest", "//python/python-exec-service/execService.python", - "//python/python-exec-service/execService.python:execService.python_test_lib", "//platform/diagnostic", "//platform/util", ] diff --git a/python/services/system-python/BUILD.bazel b/python/services/system-python/BUILD.bazel index 4b4677258872..f4180e3f1ea5 100644 --- a/python/services/system-python/BUILD.bazel +++ b/python/services/system-python/BUILD.bazel @@ -71,13 +71,10 @@ jvm_library( "//python/junit5Tests-framework:community-junit5Tests-framework_test_lib", "//python/installer", "//python/python-venv:community-impl-venv", - "//python/python-venv:community-impl-venv_test_lib", - "//python/setup-test-environment:community-testFramework-testEnv", "//platform/testFramework", "//platform/testFramework:testFramework_test_lib", "//python/python-psi-impl:psi-impl", "//python/python-exec-service/execService.python", - "//python/python-exec-service/execService.python:execService.python_test_lib", ], exports = [ "//python/services/shared", diff --git a/python/services/system-python/intellij.python.community.services.systemPython.iml b/python/services/system-python/intellij.python.community.services.systemPython.iml index 78bbd98ac8c7..9b7835f68488 100644 --- a/python/services/system-python/intellij.python.community.services.systemPython.iml +++ b/python/services/system-python/intellij.python.community.services.systemPython.iml @@ -30,7 +30,6 @@ - diff --git a/python/services/system-python/src/com/intellij/python/community/services/systemPython/systemPythonServiceImpl.kt b/python/services/system-python/src/com/intellij/python/community/services/systemPython/systemPythonServiceImpl.kt index 5bd9c3d7b2f6..b9b13277dc2a 100644 --- a/python/services/system-python/src/com/intellij/python/community/services/systemPython/systemPythonServiceImpl.kt +++ b/python/services/system-python/src/com/intellij/python/community/services/systemPython/systemPythonServiceImpl.kt @@ -45,7 +45,7 @@ internal suspend fun getCacheTimeout(): Duration? = @State(name = "SystemPythonService", storages = [Storage("systemPythonService.xml", roamingType = RoamingType.LOCAL)], allowLoadInTests = true) @Internal -internal class SystemPythonServiceImpl(scope: CoroutineScope) : SystemPythonService, SimplePersistentStateComponent(MyServiceState()) { +class SystemPythonServiceImpl(scope: CoroutineScope) : SystemPythonService, SimplePersistentStateComponent(MyServiceState()) { private val findPythonsMutex = Mutex() private val _cacheImpl: CompletableDeferred?> = CompletableDeferred() private suspend fun cache() = _cacheImpl.await() diff --git a/python/services/system-python/testResources/META-INF/plugin.xml b/python/services/system-python/testResources/META-INF/plugin.xml index e96a300b6bff..fef48d0f56a1 100644 --- a/python/services/system-python/testResources/META-INF/plugin.xml +++ b/python/services/system-python/testResources/META-INF/plugin.xml @@ -4,9 +4,6 @@ - - - - + \ No newline at end of file diff --git a/python/services/system-python/testResources/intellij.python.community.services.systemPython._test.xml b/python/services/system-python/testResources/intellij.python.community.services.systemPython._test.xml index db259d0ef34a..3085fe637e79 100644 --- a/python/services/system-python/testResources/intellij.python.community.services.systemPython._test.xml +++ b/python/services/system-python/testResources/intellij.python.community.services.systemPython._test.xml @@ -1,14 +1,8 @@ - - - - - - - + \ No newline at end of file diff --git a/python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/impl/EnvTestPythonProvider.kt b/python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/impl/EnvTestPythonProvider.kt deleted file mode 100644 index d7f016c1a2ce..000000000000 --- a/python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/impl/EnvTestPythonProvider.kt +++ /dev/null @@ -1,41 +0,0 @@ -// 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.junit5Tests.env.systemPython.impl - -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.util.Disposer -import com.intellij.platform.eel.EelApi -import com.intellij.platform.eel.provider.localEel -import com.intellij.python.community.services.systemPython.SystemPythonProvider -import com.intellij.python.community.testFramework.testEnv.TypeVanillaPython -import com.intellij.python.community.testFramework.testEnv.TypeVanillaPython3 -import com.jetbrains.python.PythonBinary -import com.jetbrains.python.Result -import com.jetbrains.python.errorProcessing.PyResult -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.toSet - -/** - * Register tests pythons as system pythons - */ -internal class EnvTestPythonProvider : SystemPythonProvider { - override suspend fun findSystemPythons(eelApi: EelApi): PyResult> { - var pythons = emptySet() - if (eelApi == localEel) { - // Add Py27 temporary to test Py27 - // It is perfectly valid not to find any python because some tests might run without a python and still have this module on a class-path - pythons = merge(TypeVanillaPython3.getTestEnvironments(), TypeVanillaPython2.getTestEnvironments()) - .map { (python, closeable) -> - Disposer.register(ApplicationManager.getApplication()) { - closeable.close() - } - - python - }.toSet() - } - - return Result.success(pythons) - } -} - -private object TypeVanillaPython2 : TypeVanillaPython("python2.7") \ No newline at end of file diff --git a/python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/SystemPythonRootsFixer.kt b/python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/impl/SystemPythonRootsFixer.kt similarity index 80% rename from python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/SystemPythonRootsFixer.kt rename to python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/impl/SystemPythonRootsFixer.kt index 238af3f4e348..0239f5adbb93 100644 --- a/python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/SystemPythonRootsFixer.kt +++ b/python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/impl/SystemPythonRootsFixer.kt @@ -1,18 +1,20 @@ // 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.junit5Tests.env.systemPython +package com.intellij.python.junit5Tests.env.systemPython.impl import com.intellij.ide.ApplicationInitializedListener import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess import com.intellij.python.community.services.systemPython.SystemPythonService +import org.jetbrains.annotations.TestOnly import kotlin.io.path.pathString +@TestOnly internal class SystemPythonRootsFixer : ApplicationInitializedListener { override suspend fun execute() { val disposable = ApplicationManager.getApplication() val pythonDirs = SystemPythonService() .findSystemPythons() - .map { it.pythonBinary.parent.pathString } + .map { it.pythonBinary.toRealPath().parent.pathString } .toTypedArray() VfsRootAccess.allowRootAccess(disposable, *pythonDirs) } diff --git a/python/setup-test-environment/.dockerignore b/python/setup-test-environment/.dockerignore deleted file mode 100644 index f8b92c3aa087..000000000000 --- a/python/setup-test-environment/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -.gradle -build diff --git a/python/setup-test-environment/.gitignore b/python/setup-test-environment/.gitignore deleted file mode 100644 index 84c048a73cc2..000000000000 --- a/python/setup-test-environment/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build/ diff --git a/python/setup-test-environment/BUILD.bazel b/python/setup-test-environment/BUILD.bazel deleted file mode 100644 index 02531c6432f4..000000000000 --- a/python/setup-test-environment/BUILD.bazel +++ /dev/null @@ -1,30 +0,0 @@ -### auto-generated section `build intellij.python.community.testFramework.testEnv` start -load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") - -resourcegroup( - name = "community-testFramework-testEnv_resources", - srcs = glob(["resources/**/*"]), - strip_prefix = "resources" -) - -jvm_library( - name = "community-testFramework-testEnv", - module_name = "intellij.python.community.testFramework.testEnv", - visibility = ["//visibility:public"], - srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), - resources = [":community-testFramework-testEnv_resources"], - deps = [ - "@lib//:kotlin-stdlib", - "//platform/core-api:core", - "//platform/lang-core", - "//platform/projectModel-api:projectModel", - "//platform/util", - "//python/openapi:community", - "//python/python-sdk:sdk", - "//platform/execution", - "//libraries/kotlin/reflect", - "//platform/lang-impl", - "//platform/platform-impl:ide-impl", - ] -) -### auto-generated section `build intellij.python.community.testFramework.testEnv` end \ No newline at end of file diff --git a/python/setup-test-environment/Dockerfile b/python/setup-test-environment/Dockerfile deleted file mode 100644 index ac1b3e39ef61..000000000000 --- a/python/setup-test-environment/Dockerfile +++ /dev/null @@ -1,51 +0,0 @@ -# registry.jetbrains.team/p/ij/test-containers/pycharm-tests -# https://buildserver.labs.intellij.net/buildConfiguration/ijplatform_master_PyCharmEnvTestsLinuxInDocker_SetupTestEnvironment -ARG BASE_IMAGE=debian:12 - -FROM $BASE_IMAGE AS builder -LABEL maintainer="Ilya Kazakevich" - -# Pythons will go here -ENV PYCHARM_PYTHONS=/pythons/ -ENV DEBIAN_FRONTEND=noninteractive - -RUN apt-get update && \ - apt-get install -y curl gcc make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget pgp \ - llvm libncurses5-dev libncursesw5-dev xz-utils tk-dev libffi-dev liblzma-dev python3-pip default-jre-headless && \ - rm -rf /var/lib/apt/lists/* -RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /docker-archive-keyring.gpg -# To install docker - -ADD . . -RUN ./gradlew build - -############################ - -FROM $BASE_IMAGE AS runner -ENV DEBIAN_FRONTEND=noninteractive -COPY --from=builder /docker-archive-keyring.gpg /usr/share/keyrings/docker-archive-keyring.gpg -RUN echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian bookworm stable" > /etc/apt/sources.list.d/docker.list - -# xterm installs freetype, xlib, cb etc. GL is also required by some python tests -# First, install certs to install docker. Then update, then install docker and other tools -# Docker cli is used in tests -# Git is used in product-info.json generation (see ProductInfoGenerator#generateGitRevisionProperty) to run tests in dev-mode -RUN apt-get update && apt-get install -y ca-certificates && apt-get upgrade -y && apt-get update && apt-get install -y openssl libgl1 zsh fish bash xterm locales libsqlite3-dev apt-transport-https ca-certificates curl software-properties-common docker-ce-cli git && rm -rf /var/lib/apt/lists/* - -# UTF locale is required for testFileEncoding -# libsqlite3 is for Django -RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \ - locale-gen - -ENV LC_ALL=en_US.UTF-8 -ENV PYCHARM_PYTHONS=/pythons/ -ENV CONDA_PATH=/pythons/conda/bin/conda - -# don't ask the user to accept conda TOS -# see PCQA-1152 and https://github.com/anaconda/conda-anaconda-tos?tab=readme-ov-file#cicd-environments -ENV CONDA_PLUGINS_AUTO_ACCEPT_TOS=yes - -COPY --from=builder /pythons/ /pythons - -# To make sure all pythons are executable -RUN find / -executable -type f,l -name "python" -print0 | xargs -0 -I '{}' sh -c "'{}' --version" diff --git a/python/setup-test-environment/README.txt b/python/setup-test-environment/README.txt deleted file mode 100644 index ec8912064b5b..000000000000 --- a/python/setup-test-environment/README.txt +++ /dev/null @@ -1,3 +0,0 @@ -Python tests require pythons with certain modules. - -Run `build.gradle.kts` to install them. Then, use classes from this module to access them. \ No newline at end of file diff --git a/python/setup-test-environment/build.gradle.kts b/python/setup-test-environment/build.gradle.kts deleted file mode 100644 index f257e3ac8ae6..000000000000 --- a/python/setup-test-environment/build.gradle.kts +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. - -import org.apache.tools.ant.taskdefs.condition.Os -import java.net.URL -import kotlin.io.path.createLinkPointingTo -import kotlin.io.path.exists - -plugins { - id("java") - id("com.jetbrains.python.envs") version "0.0.33" -} - -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) - } -} - -enum class PythonType { PYTHON, CONDA } -// If you decided to change a default path, make sure to update in `community/python/testSrc/com/jetbrains/env/PyEnvTestSettings.kt` -val pythonsDirectory = File(System.getenv().getOrDefault("PYCHARM_PYTHONS", File(buildDir, "pythons").path)) -val venvsDirectory = File(System.getenv().getOrDefault("PYCHARM_PYTHON_VIRTUAL_ENVS", File(buildDir, "envs").path)) - -/** - * Installs python interpreters for env. tests using CPython. - * Utilizes following env variables: - * - * PYCHARM_PYTHONS -- path to install cPythons - * PYCHARM_PYTHON_VIRTUAL_ENVS -- path to install condas - * - * PYCHARM_ZIP_REPOSITORY -- to download unpacked pythons for Windows (default cpython does not support unattended installation) - * Recommended value: https://repo.labs.intellij.net/pycharm/python-archives-windows/ - * - * Pitfall: TMP var on windows points to very long path inside of user local dir and may lead to errors. - * It is recommended to create "c:\temp\" with full write access and set TMP="c:\temp\" on Windows. - * - * ``PyEnvTestSettings`` class uses these vars also. - * - */ - -val isWindows = Os.isFamily(Os.FAMILY_WINDOWS) -val isUnix = Os.isFamily(Os.FAMILY_UNIX) -val isMacOs = Os.isFamily(Os.FAMILY_MAC) - -val pythonVersionMapping = mapOf( - "2.7" to "2.7.18", - "3.8" to "3.8.10", - "3.9" to "3.9.13", - "3.10" to "3.10.8", - "3.11" to "3.11.0", - "3.12" to "3.12.0", - "3.13" to "3.13.0" -) - -val defaultPackages = listOf("virtualenv") - -envs { - bootstrapDirectory = pythonsDirectory - envsDirectory = venvsDirectory - - // I don't think that it's desired behaviour to install pythons for tests user-wide what will be done - // if we don't force these options (also there may be conflicts with existing installations) - zipRepository = URL(System.getenv().getOrDefault("PYCHARM_ZIP_REPOSITORY", - "https://packages.jetbrains.team/files/p/py/python-archives-windows/")) - shouldUseZipsFromRepository = isWindows -} - -tasks.register("copy_buildserver_win_fix") { - // these files are required to fix paths on Windows, see their readme - doLast { - copy { - from("buildserver_win_fix") - into(pythonsDirectory) - } - } -} - -tasks.register("kill_python_processes") { - onlyIf { isWindows } - - // TODO: looks ugly, how can it be improved? - commandLine("powershell", """"Get-Process | where {${'$'}_.Name -ieq \"python\"} | Stop-Process"""") -} - -tasks.named("clean") { - dependsOn("kill_python_processes") - - delete(project.layout.buildDirectory) - delete(pythonsDirectory) - delete(venvsDirectory) -} - -tasks.named("build") { - dependsOn(tasks.matching { it.name.startsWith("setup_") || it.name == "updateConda" }, "clean", "copy_buildserver_win_fix") -} - - - -fun createPython( - id: String, - version: String, - packages: List = listOf(), - tags: List = listOf(), - type: PythonType = PythonType.PYTHON, -) { - val pythonHome = File(pythonsDirectory, id) - val packages = packages + defaultPackages - - envs { - when (type) { - PythonType.PYTHON -> python(id, pythonVersionMapping[version], packages) - PythonType.CONDA -> { - conda(id, version, packages) - tasks.register("updateConda") { - val cmd = pythonHome.resolve("condabin").resolve(if (isWindows) "conda.bat" else "conda").toPath().toString() - // Update manifests as old soon might become unusable - commandLine(cmd, "update", "conda", "-y") - commandLine(cmd, "update", "--all", "-y") - } - } - } - } - - - - project.tasks.create("populate_tags_$id") { - dependsOn(tasks.matching { it.name.matches("Bootstrap_[A-Z]*_'$id'.*".toRegex()) }) - onlyIf { tags.isNotEmpty() } - - doLast { - val tagsFile = pythonHome.resolve("tags.txt") - println("Adding tags to: $tagsFile") - tagsFile.writeText(tags.joinToString(separator = "\n")) - } - } - - project.tasks.create("populate_links_$id") { - dependsOn("populate_tags_$id") - - // as we have non-exact version as a key of hashMap retrieval logic from - // the old script may be easily omitted (TBD: will we be able to keep clear mapping..? - // maybe one will ever want to add "myCoolPythonVersion" as a key and break the logic) - val linkPath = pythonHome.resolve("python$version" + if (isWindows) ".exe" else "").toPath() - val executablePath = pythonHome.resolve(if (isWindows) "python.exe" else "bin/python$version").toPath() - - onlyIf { !linkPath.exists() && type == PythonType.PYTHON } - - doLast { - println("Generating link: $linkPath -> $executablePath") - linkPath.createLinkPointingTo(executablePath) - } - } - - // the task serves as aggregator so that one could just execute `./gradlew setup_python_123` - // to build some specific environment - project.tasks.create("setup_$id") { - setDependsOn(listOf("clean", "populate_links_$id", "copy_buildserver_win_fix")) - } -} - -createPython("py312_django_latest", "3.12", - listOf("django", "behave-django", "behave", "pytest", "untangle", "djangorestframework"), - listOf("python3.12", "django", "django20", "behave", "behave-django", "django2", "pytest", "untangle")) - -val qtTags = mutableListOf() -val qtPackages = mutableListOf() -if (isUnix && !isMacOs) { //qt is for Linux only - qtPackages.addAll(listOf("pyqt5==5.12", "PySide2==5.12.1")) - qtTags.add("qt") -} - -createPython("python2.7", "2.7", - listOf(), - listOf("python2.7")) - -createPython("python3.8", "3.8", - listOf("ipython==7.8", "django==2.2", "behave", "jinja2", "tox>=2.0", "nose", "pytest", "django-nose", "behave-django", - "pytest-xdist", "untangle", "numpy", "pandas") + qtPackages, - listOf("python3.8", "python3", "ipython", "ipython780", "skeletons", "django", "behave", "behave-django", "tox", "jinja2", - "packaging", "pytest", "nose", "django-nose", "behave-django", "django2", "xdist", "untangle", "pandas") + qtTags) - -createPython("python3.9", "3.9", - listOf("pytest", "pytest-xdist"), - listOf("python3.9", "python3", "pytest", "xdist", "packaging")) - -createPython("python3.10", "3.10", - listOf("untangle"), listOf("python3.10", "untangle")) - -createPython("python3.11", "3.11", - listOf("black == 23.1.0", "joblib", "tensorflow", "poetry", "uv"), - listOf("python3.11", "python3", "black", "poetry", "uv", "joblib", "tensorflow")) - -createPython("python3.12", "3.12", - listOf("teamcity-messages", "Twisted", "pytest", "poetry", "uv", "hatch", "pipenv", "black>=23.11.0") - // TODO: maybe switch to optional dependency Twisted[windows-platform] - // https://docs.twisted.org/en/stable/installation/howto/optional.html - + if (isWindows) listOf("pypiwin32") else listOf(), //win32api is required for pypiwin32 - listOf("python3", "poetry", "uv", "hatch", "pipenv", "python3.12", "messages", "twisted", "pytest", "black-fragments-formatting")) - -// set CONDA_PATH to conda binary location to be able to run tests -createPython("conda", "Miniconda3-py312_25.1.1-0", listOf(), listOf("conda"), type = PythonType.CONDA) - -createPython("python3.13", "3.13", - listOf("ruff"), - listOf("python3.13", "python3", "ruff")) diff --git a/python/setup-test-environment/buildserver_win_fix/README.txt b/python/setup-test-environment/buildserver_win_fix/README.txt deleted file mode 100644 index 79a01a2b981d..000000000000 --- a/python/setup-test-environment/buildserver_win_fix/README.txt +++ /dev/null @@ -1,6 +0,0 @@ -The problem: -https://mail.python.org/pipermail/python-win32/2024-December/014944.html - -We build environment on Agent1, then download it to Agent2. -Scripts (i.e. `pip.exe`) have shebangs hardcoded. -To fix it, run `fix_path.cmd` diff --git a/python/setup-test-environment/buildserver_win_fix/fix_path.cmd b/python/setup-test-environment/buildserver_win_fix/fix_path.cmd deleted file mode 100644 index f92f348adb38..000000000000 --- a/python/setup-test-environment/buildserver_win_fix/fix_path.cmd +++ /dev/null @@ -1,18 +0,0 @@ -@echo off -@REM Runs `fix_path.py` against every python either in the same dir or in PYCHARM_PYTHONS -SETLOCAL EnableDelayedExpansion -SET FIX="%~dp0\fix_path.py" - -IF "%PYCHARM_PYTHONS%"=="" SET PYCHARM_PYTHONS="%~dp0" - -FOR /D %%d in ("%PYCHARM_PYTHONS%\*") do ( - @rem skip conda - echo "%%d" | find "conda" > nul - if !ERRORLEVEL!==1 ( - @rem skip 2.7 - echo "%%d" | find "27" > nul - if !ERRORLEVEL!==1 ( - %%d\python.exe %FIX% - ) - ) -) \ No newline at end of file diff --git a/python/setup-test-environment/buildserver_win_fix/fix_path.py b/python/setup-test-environment/buildserver_win_fix/fix_path.py deleted file mode 100644 index 1f1f2a7e98a3..000000000000 --- a/python/setup-test-environment/buildserver_win_fix/fix_path.py +++ /dev/null @@ -1,53 +0,0 @@ -import sys -import sysconfig -from pathlib import Path - - -# On Windows you have lots of scripts (i.e `Scripts\pip.exe`). They are usually have hardcoded path to the python. -# It makes them non-transferable. -# This script replaces hard-coded paths to the one from the current interpreter. - -# When download `.zip` file with pythons from buildserver, run this script on each python to make sure scripts are ok - - -def get_script_content(script_path: Path) -> bytes: - with open(script_path, "r+b") as f: - return f.read() - - -def put_script_data(script_path: Path, script_data: bytes): - with open(script_path, "w+b") as f: - return f.write(script_data) - - -def find_shebang_start_index(where_to_search_shebang: bytes) -> int: - # Almost always shebang starts here (right after the last PE section) - default_offset: int = 0x1A600 - if len(where_to_search_shebang) > default_offset and where_to_search_shebang[ - default_offset] == '#': - return default_offset - - # Not found in default offset? Let's search - for drive_letter_code in range(ord('a'), ord('z') + 1): - for drive_letter in [chr(drive_letter_code), chr(drive_letter_code).upper()]: - try: - return where_to_search_shebang.index( - f"#!{drive_letter}:\\".encode('UTF-8')) - except ValueError: - pass - - -if __name__ == "__main__": - new_shebang = f"#!{sys.executable}".encode('UTF-8') - scripts_dir = sysconfig.get_path('scripts') - for script in Path(scripts_dir).glob("*.exe"): - script_content = get_script_content(script) - shebang_start = find_shebang_start_index(script_content) - if not shebang_start: - print(f"No python found in {script}") - continue - shebang_end = script_content.find(b'\n', shebang_start) - assert shebang_end, "No shebang end found: broken binary?" - old_shebang = script_content[shebang_start:shebang_end] - new_script_content = script_content.replace(old_shebang, new_shebang) - put_script_data(script, new_script_content) diff --git a/python/setup-test-environment/conda/BUILD.bazel b/python/setup-test-environment/conda/BUILD.bazel deleted file mode 100644 index 109743ff83d6..000000000000 --- a/python/setup-test-environment/conda/BUILD.bazel +++ /dev/null @@ -1,28 +0,0 @@ -### auto-generated section `build intellij.python.community.testFramework.testEnv.conda` start -load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") - -resourcegroup( - name = "conda_resources", - srcs = glob(["resources/**/*"]), - strip_prefix = "resources" -) - -jvm_library( - name = "conda", - module_name = "intellij.python.community.testFramework.testEnv.conda", - visibility = ["//visibility:public"], - srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), - resources = [":conda_resources"], - deps = [ - "@lib//:kotlin-stdlib", - "//python/setup-test-environment:community-testFramework-testEnv", - "//python:python-community-impl", - "//python/python-sdk:sdk", - "//platform/projectModel-api:projectModel", - "//platform/execution", - "//platform/util", - "//libraries/kotlinx/coroutines/core", - "//python/python-exec-service:community-execService", - ] -) -### auto-generated section `build intellij.python.community.testFramework.testEnv.conda` end \ No newline at end of file diff --git a/python/setup-test-environment/conda/resources/intellij.python.community.testFramework.testEnv.conda.xml b/python/setup-test-environment/conda/resources/intellij.python.community.testFramework.testEnv.conda.xml deleted file mode 100644 index cf55fa1f54a1..000000000000 --- a/python/setup-test-environment/conda/resources/intellij.python.community.testFramework.testEnv.conda.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/python/setup-test-environment/conda/src/com/intellij/python/community/testFramework/testEnv/conda/TypeConda.kt b/python/setup-test-environment/conda/src/com/intellij/python/community/testFramework/testEnv/conda/TypeConda.kt deleted file mode 100644 index c99ed29ee3a9..000000000000 --- a/python/setup-test-environment/conda/src/com/intellij/python/community/testFramework/testEnv/conda/TypeConda.kt +++ /dev/null @@ -1,79 +0,0 @@ -package com.intellij.python.community.testFramework.testEnv.conda - -import com.intellij.execution.processTools.getResultStdout -import com.intellij.openapi.diagnostic.logger -import com.intellij.openapi.projectRoots.Sdk -import com.intellij.python.community.execService.BinOnEel -import com.intellij.python.community.testFramework.testEnv.PythonType -import com.intellij.util.io.awaitExit -import com.jetbrains.python.PythonBinary -import com.jetbrains.python.getOrThrow -import com.jetbrains.python.packaging.PyCondaPackageService -import com.jetbrains.python.packaging.findCondaExecutableRelativeToEnv -import com.jetbrains.python.sdk.add.v2.conda.getCondaVersion -import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv -import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import org.jetbrains.annotations.ApiStatus.Internal -import java.nio.file.Path -import kotlin.io.path.pathString - -@Internal -data object TypeConda : PythonType("conda") { - override suspend fun createSdkFor(env: PyCondaEnv): Sdk = - env.createSdkFromThisEnv(null, emptyList()) - - override suspend fun pythonPathToEnvironment(pythonBinary: PythonBinary, envDir: Path): Pair { - // First, find python binary, then calculate conda from it as env stores "conda" as a regular env - val condaPath = findCondaExecutableRelativeToEnv(pythonBinary) ?: error("Conda root $pythonBinary doesn't have conda binary") - // Save a path to conda because some legacy code might use it instead of a full conda path from additional data - PyCondaPackageService.onCondaEnvCreated(condaPath.pathString) - - // We'll remove then on close - val condaEnvsBeforeTest = getCondaNames(condaPath) - - val cleanupCondas = AutoCloseable { - runBlocking(Dispatchers.IO) { - val condasToRemove = getCondaNames(condaPath) - condasToRemove.removeAll(condaEnvsBeforeTest) - for (envName in condasToRemove) { - println("Removing $envName") - - for (arg in arrayOf("--name", "-p")) { - val args = arrayOf(condaPath.toString(), "remove", arg, envName, "--all", "-y") - val exec = Runtime.getRuntime().exec(args) - launch { - try { - exec.awaitExit() - } - catch (e: CancellationException) { - exec.destroyForcibly() - throw e - } - } - exec.getResultStdout().getOrElse { - logger().warn(it) - } - } - } - } - } - return Pair( - PyCondaEnv( - envIdentity = PyCondaEnvIdentity.UnnamedEnv(envDir.toString(), isBase = true), - fullCondaPathOnTarget = condaPath.toString(), - ), - cleanupCondas - ) - } - - private suspend fun getCondaNames(condaPath: Path): MutableSet { - val envs = PyCondaEnv.getEnvs(BinOnEel(condaPath)).getOrThrow() - .map { it.envIdentity.userReadableName } - .toMutableSet() - return envs - } -} \ No newline at end of file diff --git a/python/setup-test-environment/gradle/wrapper/gradle-wrapper.jar b/python/setup-test-environment/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 249e5832f090a2944b7473328c07c9755baa3196..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60756 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 '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -APP_NAME="Gradle" -APP_BASE_NAME=${0##*/} - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/python/setup-test-environment/gradlew.bat b/python/setup-test-environment/gradlew.bat deleted file mode 100644 index f127cfd49d40..000000000000 --- a/python/setup-test-environment/gradlew.bat +++ /dev/null @@ -1,91 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/python/setup-test-environment/settings.gradle.kts b/python/setup-test-environment/settings.gradle.kts deleted file mode 100644 index 17247524617f..000000000000 --- a/python/setup-test-environment/settings.gradle.kts +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. - -pluginManagement { - repositories { - maven { - url = uri("https://packages.jetbrains.team/maven/p/py/public-maven") - } - gradlePluginPortal() - } -} \ No newline at end of file diff --git a/python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/PyEnvTestSettings.kt b/python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/PyEnvTestSettings.kt deleted file mode 100644 index bfc5440c5e57..000000000000 --- a/python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/PyEnvTestSettings.kt +++ /dev/null @@ -1,139 +0,0 @@ -// 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.testFramework.testEnv - -import com.intellij.python.community.testFramework.testEnv.PyEnvTestSettings.Companion.PATH_TO_TEST_ENV_PYTHON_INTERPRETERS -import com.intellij.util.SystemProperties -import org.jetbrains.annotations.ApiStatus -import org.jetbrains.annotations.TestOnly -import java.io.File - - -private val IS_UNDER_TEAMCITY: Boolean = System.getenv("TEAMCITY_VERSION") != null - -/** - * Configures env test environment using env vars and properties. - * Environment variables are also used in gradle script (setup-test-environment) - */ -@TestOnly -@ApiStatus.Internal -data class PyEnvTestSettings( - private val folderWithCPythons: String?, - private val folderWithCondas: String?, - private val additionalInterpreters: List, - @get:JvmName("useRemoteSdk") - val useRemoteSdk: Boolean, - val isEnvConfiguration: Boolean, - val isUnderTeamCity: Boolean, -) { - private val foldersWithPythons: List = listOfNotNull(folderWithCPythons, folderWithCondas).map { File(it) } - - /** - * Paths to all existing python SDKs (as directories with python installation) - */ - @TestOnly - @get:JvmName("getPythons") - internal val pythons: List = foldersWithPythons - .asSequence() - .filter(File::exists) - .flatMap { it.listFiles()?.toList() ?: emptyList() } - .plus(additionalInterpreters) - .filter { if (PyTestEnvVars.PYCHARM_PY_VERSION.isSet()) it.path.contains(PyTestEnvVars.PYCHARM_PY_VERSION.getValue()!!) else true } - .filter { it.isDirectory && !it.name.startsWith('.') } - .toList() - - /** - * Configuration in readable format - */ - fun reportConfiguration() = (PyTestEnvVars.getEnvValues() + listOf(toString())).joinToString("\n") - - companion object { - // If you decided to change the path, make sure to update it in community/python/setup-test-environment/build.gradle.kts - private const val PATH_TO_TEST_ENV_PYTHON_INTERPRETERS = "community/python/setup-test-environment/build/pythons" - - /** - * Tries to resolve [PATH_TO_TEST_ENV_PYTHON_INTERPRETERS] folder from current working dir. - * It allows automatically detecting the local folder with interpreters built by [community/python/setup-test-environment/build.gradle.kts] - */ - private fun detectDefaultPyInterpretersFolders(): List { - var currentFile: File? = File(System.getProperty("user.dir")) - while (currentFile != null) { - val pythonInterpretersFolder = currentFile.resolve(PATH_TO_TEST_ENV_PYTHON_INTERPRETERS) - if (pythonInterpretersFolder.exists()) { - return pythonInterpretersFolder.listFiles { it.isDirectory && !it.name.startsWith('.') }.toList() - } - currentFile = currentFile.parentFile - - } - return emptyList() - } - - fun fromEnvVariables(): PyEnvTestSettings { - val isUnderTeamCity = IS_UNDER_TEAMCITY - return PyEnvTestSettings( - folderWithCPythons = PyTestEnvVars.PYCHARM_PYTHONS.getValue(), - folderWithCondas = PyTestEnvVars.PYCHARM_PYTHON_VIRTUAL_ENVS.getValue(), - useRemoteSdk = SystemProperties.getBooleanProperty("pycharm.run_remote", false) || PyTestEnvVars.PYCHARM_RUN_REMOTE.isSet(), - isEnvConfiguration = System.getProperty("pycharm.env") != null - || PyTestEnvVars.PYCHARM_ENV.toString() in System.getenv(), - isUnderTeamCity = isUnderTeamCity, - additionalInterpreters = PyTestEnvVars.PYCHARM_PYTHON_ENVS.getValue()?.split(File.pathSeparator) - ?.map { File(it) } - ?.toList() - ?: if (isUnderTeamCity) { - emptyList() - } - else { - detectDefaultPyInterpretersFolders() - }, - ) - } - } -} - -/** - * Env variables used to configure tests - */ -@ApiStatus.Internal -enum class PyTestEnvVars(private val getVarName: (PyTestEnvVars) -> String = { it.name }) { - /** - * Path to folder with CPythons - */ - PYCHARM_PYTHONS, - - /** - * Path to folder with condas - */ - PYCHARM_PYTHON_VIRTUAL_ENVS, - - /** - * [File.separator] separated list of full paths to pythons (including binary) to add to folders found in [PYCHARM_PYTHON_VIRTUAL_ENVS] - * and [PYCHARM_PYTHONS] - */ - PYCHARM_PYTHON_ENVS, - - /** - * Set if launched using "PyEnvTests" - */ - PYCHARM_ENV, - - /** - * Only run remote-based tests - */ - PYCHARM_RUN_REMOTE, - - /** - * Run only on one PY version - */ - PYCHARM_PY_VERSION; - - - companion object { - fun getEnvValues() = entries.map { "$it : ${it.getValue()}" } - } - - override fun toString() = getVarName(this) - - fun getValue(): String? = System.getenv(toString()) - - fun isSet() = getValue() != null -} diff --git a/python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/PythonType.kt b/python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/PythonType.kt deleted file mode 100644 index 23d2d1cfb37e..000000000000 --- a/python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/PythonType.kt +++ /dev/null @@ -1,113 +0,0 @@ -// 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.testFramework.testEnv - -import com.intellij.openapi.application.ApplicationManager -import com.intellij.openapi.projectRoots.Sdk -import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess -import com.intellij.util.concurrency.annotations.RequiresBackgroundThread -import com.jetbrains.python.PythonBinary -import com.jetbrains.python.sdk.flavors.PythonSdkFlavor -import com.jetbrains.python.venvReader.VirtualEnvReader -import kotlinx.coroutines.flow.* -import org.jetbrains.annotations.NonNls -import java.nio.file.Path -import java.util.concurrent.ConcurrentHashMap -import kotlin.Result.Companion.failure -import kotlin.io.path.exists -import kotlin.io.path.isDirectory -import kotlin.io.path.pathString - -/** - * Gradle script installs two types of python: conda and vanilla. Env could be obtained by [createSdkClosableEnv] which also provides closable - * to clean up after the usage - */ -abstract class PythonType(private val tag: @NonNls String) { - companion object { - private val cache: MutableMap, List> = ConcurrentHashMap() - private const val PYTHON_FOR_TESTS: String = "PYTHON_FOR_TESTS" - private val customPython: String? get() = System.getenv(PYTHON_FOR_TESTS) - val BUILD_KTS_MESSAGE: String = "`build.gradle.kts` from the same module ${PythonType::class} sits in. Be sure to read it first: you will need to run `gradle build` there" - val customPythonMessage: String? get() = customPython?.let { "You are using custom python $it set by $PYTHON_FOR_TESTS env var" } - } - - /** - * Returns all test environments ordered from newest (highest) to oldest: each must be closed after the test. - * If in doubt, take first - */ - suspend fun getTestEnvironments(vararg additionalTags: @NonNls String, ensureAtLeastOnePython: Boolean = false): Flow> { - val key = setOf(*additionalTags) - val pythons = cache.getOrPut(key) { - PyEnvTestSettings - .fromEnvVariables() - .pythons - .map { it.toPath() } - .mapNotNull { path -> - val binary = VirtualEnvReader.Instance.findPythonInPythonRoot(path) - ?: error("No python in $path") - val flavor = PythonSdkFlavor.tryDetectFlavorByLocalPath(binary.toString()) - ?: error("Unknown flavor: $binary") - flavor.getVersionString(binary.toString())?.let { path to PythonSdkFlavor.getLanguageLevelFromVersionStringStatic(it) } - ?: error("Can't get language level for $flavor , $binary") - } - .sortedByDescending { (_, languageLevel) -> languageLevel } - .map { (path, _) -> path }.also { pythonDirs -> - // it is ok to access python dirs from tests - VfsRootAccess.allowRootAccess(ApplicationManager.getApplication(), *pythonDirs.map { it.pathString }.toTypedArray()) - } - - }.toMutableList() - val customPythonDir = if (pythons.isEmpty() && ensureAtLeastOnePython) { - val pythonStr = customPython - if (pythonStr == null) { - error(""" - To run this test you need a python interpreter. You have two options: - - 1. Use $BUILD_KTS_MESSAGE. - 2. Set env variable $PYTHON_FOR_TESTS to the executable python file you already have, i.e: `/bin/python3` or `c:\python\python.exe` - """.trimIndent()) - } - val pythonPath = Path.of(pythonStr) - val pythonBinary = when { - pythonPath.isDirectory() -> VirtualEnvReader.Instance.findPythonInPythonRoot(pythonPath) - pythonPath.exists() -> pythonPath - else -> null - } ?: error("$PYTHON_FOR_TESTS env var points to something that is not a python: $pythonPath") - pythonBinary.parent - } - else { - null - } - if (customPythonDir != null) { - pythons.add(customPythonDir) - } - return pythons.asFlow() - .filter { it == customPythonDir || typeMatchesEnv(it, *additionalTags) } - .map { envDir -> - pythonPathToEnvironment( - VirtualEnvReader.Instance.findPythonInPythonRoot(envDir) - ?: error("Can't find python binary in $envDir"), envDir) // This is a misconfiguration, hence an error - } - } - - - /** - * Returns sdk, (whatever it means) test environment and closable that must be closed after the test - */ - suspend fun createSdkClosableEnv(vararg additionalTags: @NonNls String): Result> = - getTestEnvironments(*additionalTags, ensureAtLeastOnePython = true).firstOrNull()?.let { (env, closable) -> - Result.success(Triple(createSdkFor(env), closable, env)) - } - ?: failure(AssertionError("No python found. See ${PyEnvTestSettings::class} class for more info")) - - protected abstract suspend fun createSdkFor(t: T): Sdk - - - protected abstract suspend fun pythonPathToEnvironment(pythonBinary: PythonBinary, envDir: Path): Pair - - - @RequiresBackgroundThread - private fun typeMatchesEnv(env: Path, vararg additionalTags: @NonNls String): Boolean { - val envTags = loadEnvTags(env) - return tag in envTags && additionalTags.all { it in envTags } - } -} \ No newline at end of file diff --git a/python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/TypeVanillaPython3.kt b/python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/TypeVanillaPython3.kt deleted file mode 100644 index 30bd5f53aba1..000000000000 --- a/python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/TypeVanillaPython3.kt +++ /dev/null @@ -1,32 +0,0 @@ -// 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.testFramework.testEnv - -import com.intellij.openapi.projectRoots.Sdk -import com.intellij.openapi.projectRoots.SdkType -import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil -import com.intellij.openapi.util.Disposer -import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess -import com.intellij.openapi.vfs.refreshAndFindVirtualFileOrDirectory -import com.jetbrains.python.PyNames -import com.jetbrains.python.PythonBinary -import java.nio.file.Path - -open class TypeVanillaPython(tag: String) : PythonType(tag) { - override suspend fun createSdkFor(python: PythonBinary): Sdk = createSdk(python) - - fun createSdk(python: PythonBinary): Sdk = - SdkConfigurationUtil.setupSdk(emptyArray(), python.refreshAndFindVirtualFileOrDirectory()!!, - SdkType.findByName(PyNames.PYTHON_SDK_ID_NAME)!!, null, null) - - // Python is directly executable - override suspend fun pythonPathToEnvironment(pythonBinary: PythonBinary, envDir: Path): Pair { - val disposable = Disposer.newDisposable("Python tests disposable for VfsRootAccess") - // We might have python installation outside the project root, but we still need to have access to it. - VfsRootAccess.allowRootAccess(disposable, pythonBinary.parent.toString()) - return Pair(pythonBinary, AutoCloseable { - Disposer.dispose(disposable) - }) - } -} - -object TypeVanillaPython3 : TypeVanillaPython("python3") \ No newline at end of file diff --git a/python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/envTags.kt b/python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/envTags.kt deleted file mode 100644 index 3de5c9e78158..000000000000 --- a/python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/envTags.kt +++ /dev/null @@ -1,29 +0,0 @@ -// 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.testFramework.testEnv - -import com.intellij.openapi.diagnostic.fileLogger -import com.jetbrains.python.venvReader.Directory -import java.io.IOException -import java.nio.file.Path -import kotlin.io.path.isDirectory -import kotlin.io.path.readLines - -private const val TAGS_FILE = "tags.txt" - -/** - * Loads [TAGS_FILE] from [pythonEnv] which should be a path to a python installation (either binary or directory) created by - * `community/python/setup-test-environment`. - * - * Returns set of tags i.e. `django` if interpreter has django. - */ -fun loadEnvTags(pythonEnv: Path): Set { - val envDir: Directory = if (pythonEnv.isDirectory()) pythonEnv else pythonEnv.parent - val tagsFile = envDir.resolve(TAGS_FILE) - try { - return tagsFile.readLines().toSet() - } - catch (_: IOException) { - fileLogger().warn("Can't read $tagsFile") - return emptySet() - } -} diff --git a/python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/package-info.java b/python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/package-info.java deleted file mode 100644 index 2705b2e28225..000000000000 --- a/python/setup-test-environment/src/com/intellij/python/community/testFramework/testEnv/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -/** - * Package to access pythons created by community/python/setup-test-environment/build.gradle.kts - */ -@ApiStatus.Internal -package com.intellij.python.community.testFramework.testEnv; - -import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/python/testFramework/BUILD.bazel b/python/testFramework/BUILD.bazel index 988ca6377f2f..e39ebabaffe7 100644 --- a/python/testFramework/BUILD.bazel +++ b/python/testFramework/BUILD.bazel @@ -1,11 +1,18 @@ ### auto-generated section `build intellij.python.community.testFramework` start -load("@rules_jvm//:jvm.bzl", "jvm_library") +load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") + +resourcegroup( + name = "testFramework_resources", + srcs = glob(["resources/**/*"]), + strip_prefix = "resources" +) jvm_library( name = "testFramework", module_name = "intellij.python.community.testFramework", visibility = ["//visibility:public"], srcs = glob(["src/**/*.kt", "src/**/*.java", "src/**/*.form"], allow_empty = True), + resources = [":testFramework_resources"], deps = [ "//platform/analysis-api:analysis", "//platform/core-api:core", @@ -21,7 +28,6 @@ jvm_library( "//platform/execution", "//platform/lang-impl", "//python:python-community-impl", - "//python/setup-test-environment:community-testFramework-testEnv", ] ) ### auto-generated section `build intellij.python.community.testFramework` end \ No newline at end of file diff --git a/python/testFramework/intellij.python.community.testFramework.iml b/python/testFramework/intellij.python.community.testFramework.iml index 394d020d82c7..db5415816c99 100644 --- a/python/testFramework/intellij.python.community.testFramework.iml +++ b/python/testFramework/intellij.python.community.testFramework.iml @@ -3,6 +3,7 @@ + @@ -21,6 +22,5 @@ - \ No newline at end of file diff --git a/python/testFramework/resources/intellij.python.community.testFramework.xml b/python/testFramework/resources/intellij.python.community.testFramework.xml new file mode 100644 index 000000000000..c78a3f989d4f --- /dev/null +++ b/python/testFramework/resources/intellij.python.community.testFramework.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/python/testFramework/src/com/jetbrains/python/tools/sdk.kt b/python/testFramework/src/com/jetbrains/python/tools/sdk.kt index 19b31bdeb1db..b4e92f5b0cde 100644 --- a/python/testFramework/src/com/jetbrains/python/tools/sdk.kt +++ b/python/testFramework/src/com/jetbrains/python/tools/sdk.kt @@ -1,66 +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.jetbrains.python.tools -import com.intellij.execution.target.FullPathOnTarget -import com.intellij.execution.target.TargetEnvironmentConfiguration -import com.intellij.openapi.application.edtWriteAction -import com.intellij.openapi.projectRoots.ProjectJdkTable import com.intellij.openapi.projectRoots.Sdk -import com.intellij.python.community.testFramework.testEnv.PythonType -import com.intellij.python.community.testFramework.testEnv.TypeVanillaPython3 -import com.intellij.remote.RemoteSdkException -import com.jetbrains.python.sdk.PythonSdkType -import com.jetbrains.python.sdk.flavors.PyFlavorAndData -import com.jetbrains.python.sdk.flavors.PyFlavorData -import com.jetbrains.python.sdk.flavors.UnixPythonSdkFlavor -import com.jetbrains.python.target.PyTargetAwareAdditionalData -import com.jetbrains.python.target.getInterpreterVersionForJava -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -import org.junit.Assume +import com.intellij.openapi.projectRoots.SdkType +import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil +import com.intellij.openapi.vfs.refreshAndFindVirtualFileOrDirectory +import com.jetbrains.python.PyNames +import com.jetbrains.python.PythonBinary -/** - * To be used with [createSdk] - */ -sealed class SdkCreationRequest { - data class LocalPython(val pythonType: PythonType<*> = TypeVanillaPython3) : SdkCreationRequest() - data class RemotePython(val targetConfig: TargetEnvironmentConfiguration) : SdkCreationRequest() +fun createSdk(venvPython: PythonBinary): Sdk { + return SdkConfigurationUtil.setupSdk(emptyArray(), + venvPython.refreshAndFindVirtualFileOrDirectory()!!, + SdkType.findByName(PyNames.PYTHON_SDK_ID_NAME)!!, + null, + null) } -/** - * Creates sdk either local (you can choose type then) or remote (always vanilla) - */ -suspend fun createSdk(request: SdkCreationRequest): Pair = withContext(Dispatchers.IO) { - when (request) { - is SdkCreationRequest.LocalPython -> { - val (sdk, closable, _) = request.pythonType.createSdkClosableEnv().getOrThrow() - Pair(sdk, closable) - } - is SdkCreationRequest.RemotePython -> { - val targetData = PyTargetAwareAdditionalData(PyFlavorAndData(PyFlavorData.Empty, UnixPythonSdkFlavor.getInstance()), - request.targetConfig).apply { - interpreterPath = PYTHON_PATH_ON_TARGET - } - try { - Assume.assumeNotNull("No $PYTHON_PATH_ON_TARGET on target", targetData.getInterpreterVersionForJava()) - } - catch (e: RemoteSdkException) { - Assume.assumeNoException("Error running $PYTHON_PATH_ON_TARGET", e) - } - Pair(ProjectJdkTable.getInstance().createSdk(PYTHON_PATH_ON_TARGET, PythonSdkType.getInstance()).apply { - sdkModificator.apply { - homePath = PYTHON_PATH_ON_TARGET - sdkAdditionalData = targetData - edtWriteAction { - commitChanges() - } - } - }, AutoCloseable { }) - } - } -} - -private const val PYTHON_PATH_ON_TARGET: FullPathOnTarget = "/usr/bin/python3" - - diff --git a/python/python-exec-service/execService.python/tests/com/intellij/python/junit5Tests/env/HelpersShowCaseTest.kt b/python/testSrc/com/intellij/python/junit5Tests/env/HelpersShowCaseTest.kt similarity index 100% rename from python/python-exec-service/execService.python/tests/com/intellij/python/junit5Tests/env/HelpersShowCaseTest.kt rename to python/testSrc/com/intellij/python/junit5Tests/env/HelpersShowCaseTest.kt diff --git a/python/python-exec-service/execService.python/tests/com/intellij/python/junit5Tests/env/PythonBinaryValidationTest.kt b/python/testSrc/com/intellij/python/junit5Tests/env/PythonBinaryValidationTest.kt similarity index 100% rename from python/python-exec-service/execService.python/tests/com/intellij/python/junit5Tests/env/PythonBinaryValidationTest.kt rename to python/testSrc/com/intellij/python/junit5Tests/env/PythonBinaryValidationTest.kt diff --git a/python/python-pyproject/test/com/intellij/python/junit5Tests/env/pyproject/PyWalkFileSystemEnvTest.kt b/python/testSrc/com/intellij/python/junit5Tests/env/pyproject/PyWalkFileSystemEnvTest.kt similarity index 100% rename from python/python-pyproject/test/com/intellij/python/junit5Tests/env/pyproject/PyWalkFileSystemEnvTest.kt rename to python/testSrc/com/intellij/python/junit5Tests/env/pyproject/PyWalkFileSystemEnvTest.kt diff --git a/python/services/internal-impl/tests/com/intellij/python/junit5Tests/env/services/internal/impl/PythonWithLanguageLevelImplTest.kt b/python/testSrc/com/intellij/python/junit5Tests/env/services/internal/impl/PythonWithLanguageLevelImplTest.kt similarity index 100% rename from python/services/internal-impl/tests/com/intellij/python/junit5Tests/env/services/internal/impl/PythonWithLanguageLevelImplTest.kt rename to python/testSrc/com/intellij/python/junit5Tests/env/services/internal/impl/PythonWithLanguageLevelImplTest.kt diff --git a/python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/Py27Test.kt b/python/testSrc/com/intellij/python/junit5Tests/env/systemPython/Py27Test.kt similarity index 100% rename from python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/Py27Test.kt rename to python/testSrc/com/intellij/python/junit5Tests/env/systemPython/Py27Test.kt diff --git a/python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/SystemPythonServiceShowCaseTest.kt b/python/testSrc/com/intellij/python/junit5Tests/env/systemPython/SystemPythonServiceShowCaseTest.kt similarity index 100% rename from python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/SystemPythonServiceShowCaseTest.kt rename to python/testSrc/com/intellij/python/junit5Tests/env/systemPython/SystemPythonServiceShowCaseTest.kt diff --git a/python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/impl/EnvProviderTest.kt b/python/testSrc/com/intellij/python/junit5Tests/env/systemPython/impl/EnvProviderTest.kt similarity index 100% rename from python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/impl/EnvProviderTest.kt rename to python/testSrc/com/intellij/python/junit5Tests/env/systemPython/impl/EnvProviderTest.kt diff --git a/python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/impl/package-info.java b/python/testSrc/com/intellij/python/junit5Tests/env/systemPython/impl/package-info.java similarity index 100% rename from python/services/system-python/tests/com/intellij/python/junit5Tests/env/systemPython/impl/package-info.java rename to python/testSrc/com/intellij/python/junit5Tests/env/systemPython/impl/package-info.java diff --git a/python/interpreters/tests/com/intellij/python/junit5Tests/env/tests/interpreters/InterpreterServiceShowCaseTest.kt b/python/testSrc/com/intellij/python/junit5Tests/env/tests/interpreters/InterpreterServiceShowCaseTest.kt similarity index 91% rename from python/interpreters/tests/com/intellij/python/junit5Tests/env/tests/interpreters/InterpreterServiceShowCaseTest.kt rename to python/testSrc/com/intellij/python/junit5Tests/env/tests/interpreters/InterpreterServiceShowCaseTest.kt index 8484999463d5..0009fe000834 100644 --- a/python/interpreters/tests/com/intellij/python/junit5Tests/env/tests/interpreters/InterpreterServiceShowCaseTest.kt +++ b/python/testSrc/com/intellij/python/junit5Tests/env/tests/interpreters/InterpreterServiceShowCaseTest.kt @@ -6,10 +6,10 @@ import com.intellij.openapi.projectRoots.Sdk import com.intellij.openapi.roots.ModuleRootModificationUtil import com.intellij.python.community.execService.ExecService import com.intellij.python.community.helpersLocator.PythonHelpersLocator -import com.intellij.python.community.impl.venv.tests.pyVenvFixture import com.intellij.python.community.interpreters.* import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase import com.intellij.python.junit5Tests.framework.env.pySdkFixture +import com.intellij.python.test.env.junit5.pyVenvFixture import com.intellij.testFramework.junit5.fixture.TestFixture import com.intellij.testFramework.junit5.fixture.moduleFixture import com.intellij.testFramework.junit5.fixture.projectFixture @@ -31,11 +31,11 @@ import kotlin.io.path.writeText @PyEnvTestCase class InterpreterServiceShowCaseTest { private val sdkFixture = pySdkFixture() - private val validSdkFixture = sdkFixture.pyVenvFixture(tempPathFixture(prefix = "valid"), addToSdkTable = true) - private val validSdkFixture2 = sdkFixture.pyVenvFixture(tempPathFixture(prefix = "valid2"), addToSdkTable = true) + private val validSdkFixture = sdkFixture.pyVenvFixture(tempPathFixture(prefix = "valid"), addToSdkTable = true, moduleFixture = null) + private val validSdkFixture2 = sdkFixture.pyVenvFixture(tempPathFixture(prefix = "valid2"), addToSdkTable = true, moduleFixture = null) - private val invalidSdkFixture = sdkFixture.pyVenvFixture(tempPathFixture(prefix = "invalid"), addToSdkTable = true) - private val sdkFixtureAnotherPath = sdkFixture.pyVenvFixture(tempPathFixture(prefix = "anotherpath"), addToSdkTable = true) + private val invalidSdkFixture = sdkFixture.pyVenvFixture(tempPathFixture(prefix = "invalid"), addToSdkTable = true, moduleFixture = null) + private val sdkFixtureAnotherPath = sdkFixture.pyVenvFixture(tempPathFixture(prefix = "anotherpath"), addToSdkTable = true, moduleFixture = null) private val moduleFixture = projectFixture().moduleFixture() diff --git a/python/python-venv/tests/com/intellij/python/junit5Tests/env/venv/showCase/PyEnvWithVenvShowCaseTest.kt b/python/testSrc/com/intellij/python/junit5Tests/env/venv/showCase/PyEnvWithVenvShowCaseTest.kt similarity index 94% rename from python/python-venv/tests/com/intellij/python/junit5Tests/env/venv/showCase/PyEnvWithVenvShowCaseTest.kt rename to python/testSrc/com/intellij/python/junit5Tests/env/venv/showCase/PyEnvWithVenvShowCaseTest.kt index 3737024304ef..d405b29756fb 100644 --- a/python/python-venv/tests/com/intellij/python/junit5Tests/env/venv/showCase/PyEnvWithVenvShowCaseTest.kt +++ b/python/testSrc/com/intellij/python/junit5Tests/env/venv/showCase/PyEnvWithVenvShowCaseTest.kt @@ -2,9 +2,9 @@ package com.intellij.python.junit5Tests.env.venv.showCase import com.intellij.openapi.projectRoots.ProjectJdkTable -import com.intellij.python.community.impl.venv.tests.pyVenvFixture import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase import com.intellij.python.junit5Tests.framework.env.pySdkFixture +import com.intellij.python.test.env.junit5.pyVenvFixture import com.intellij.testFramework.junit5.fixture.moduleFixture import com.intellij.testFramework.junit5.fixture.projectFixture import com.intellij.testFramework.junit5.fixture.tempPathFixture diff --git a/python/python-venv/tests/com/intellij/python/junit5Tests/env/venv/showCase/PyVenvCreationManuallyShowCaseTest.kt b/python/testSrc/com/intellij/python/junit5Tests/env/venv/showCase/PyVenvCreationManuallyShowCaseTest.kt similarity index 95% rename from python/python-venv/tests/com/intellij/python/junit5Tests/env/venv/showCase/PyVenvCreationManuallyShowCaseTest.kt rename to python/testSrc/com/intellij/python/junit5Tests/env/venv/showCase/PyVenvCreationManuallyShowCaseTest.kt index 5275d0e80d14..9fc66f118428 100644 --- a/python/python-venv/tests/com/intellij/python/junit5Tests/env/venv/showCase/PyVenvCreationManuallyShowCaseTest.kt +++ b/python/testSrc/com/intellij/python/junit5Tests/env/venv/showCase/PyVenvCreationManuallyShowCaseTest.kt @@ -2,7 +2,6 @@ package com.intellij.python.junit5Tests.env.venv.showCase import com.intellij.python.community.impl.venv.createVenv -import com.intellij.python.community.testFramework.testEnv.TypeVanillaPython3.createSdk import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase import com.intellij.python.junit5Tests.framework.env.PythonBinaryPath import com.intellij.testFramework.common.timeoutRunBlocking @@ -10,6 +9,7 @@ import com.jetbrains.python.PythonBinary import com.jetbrains.python.getOrThrow import com.jetbrains.python.sdk.flavors.PythonSdkFlavor import com.jetbrains.python.sdk.getOrCreateAdditionalData +import com.jetbrains.python.tools.createSdk import com.jetbrains.python.venvReader.Directory import org.junit.jupiter.api.Assertions.assertFalse import org.junit.jupiter.api.Assertions.assertTrue diff --git a/python/testSrc/com/jetbrains/env/ProviderTestEnvironment.kt b/python/testSrc/com/jetbrains/env/ProviderTestEnvironment.kt new file mode 100644 index 000000000000..4c5af7f6873d --- /dev/null +++ b/python/testSrc/com/jetbrains/env/ProviderTestEnvironment.kt @@ -0,0 +1,35 @@ +// 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.env + +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.python.test.env.core.PyEnvironment +import com.intellij.python.test.env.core.PyEnvironmentFactory +import com.intellij.python.test.env.core.PyEnvironmentSpec +import com.intellij.util.text.SemVer +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking + +private class ProviderTestEnvironment(private val factory: PyEnvironmentFactory, private val spec: PyEnvironmentSpec<*>, private val tags: Set) : + PyTestEnvironment { + private var environment: PyEnvironment? = null + + override fun getPythonVersion(): SemVer { + return spec.pythonVersion + } + + override fun getDescription(): String { + return "provider: " + spec.javaClass.getSimpleName() + " (Python " + spec.pythonVersion + ")" + } + + override fun getTags(): Set { + return tags + } + + override fun prepareSdk(): Sdk = runBlocking(Dispatchers.IO) { + factory.createEnvironment(spec).prepareSdk() + } + + override fun close() { + environment?.close() + } +} diff --git a/python/testSrc/com/jetbrains/env/PyEnvTaskRunner.java b/python/testSrc/com/jetbrains/env/PyEnvTaskRunner.java index deaa6ed51ce0..7a37f8d412ff 100644 --- a/python/testSrc/com/jetbrains/env/PyEnvTaskRunner.java +++ b/python/testSrc/com/jetbrains/env/PyEnvTaskRunner.java @@ -6,47 +6,47 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.python.community.testFramework.testEnv.EnvTagsKt; +import com.intellij.python.test.env.common.PredefinedPyEnvironments; +import com.intellij.python.test.env.core.PyEnvironmentFactory; +import com.intellij.python.test.env.core.PythonVersionKt; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.ContainerUtil; import com.jetbrains.LoggingRule; import com.jetbrains.python.psi.LanguageLevel; import com.jetbrains.python.sdk.PySdkUtil; import com.jetbrains.python.sdk.flavors.PythonSdkFlavor; -import com.jetbrains.python.tools.sdkTools.PySdkTools; -import com.jetbrains.python.tools.sdkTools.SdkCreationType; -import com.jetbrains.python.venvReader.VirtualEnvReader; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.io.File; -import java.nio.file.Path; import java.util.*; public class PyEnvTaskRunner { private static final Logger LOG = Logger.getInstance(PyEnvTaskRunner.class); - private final List myRoots; + @NotNull private final PyEnvironmentFactory myFactory; + private final @Nullable String myPythonVersionFilter; @Nullable private final LoggingRule myLoggingRule; /** - * @param loggingRule to be passed by {@link PyEnvTestCase}. - * {@link LoggingRule#startLogging(Disposable, Iterable)} will be called - * if task has {@link PyExecutionFixtureTestTask#getClassesToEnableDebug()} + * @param factory factory creates test environment instances + * @param pythonVersionFilter pythonVersionFilter + * @param loggingRule to be passed by {@link PyEnvTestCase}. + * {@link LoggingRule#startLogging(Disposable, Iterable)} will be called + * if task has {@link PyExecutionFixtureTestTask#getClassesToEnableDebug()} */ - public PyEnvTaskRunner(List roots, @Nullable final LoggingRule loggingRule) { - myRoots = roots; + public PyEnvTaskRunner(@NotNull PyEnvironmentFactory factory, + @Nullable String pythonVersionFilter, + @Nullable final LoggingRule loggingRule) { + myFactory = factory; + myPythonVersionFilter = pythonVersionFilter; myLoggingRule = loggingRule; } /** * Runs test on all interpreters. * - * @param skipOnFlavors optional array of interpreter flavors to skip - * + * @param skipOnFlavors optional array of interpreter flavors to skip * @param tagsRequiredByTest optional array of tags to run tests on interpreters with these tags only */ public void runTask(@NotNull final PyTestTask testTask, @@ -55,26 +55,33 @@ public class PyEnvTaskRunner { final String @NotNull ... tagsRequiredByTest) { boolean wasExecuted = false; - List passedRoots = new ArrayList<>(); + List passedEnvironments = new ArrayList<>(); final Set requiredTags = Sets.union(testTask.getTags(), Sets.newHashSet(tagsRequiredByTest)); - for (String root : getMinimalSetOfTestEnvs(requiredTags)) { - final boolean shouldRun = shouldRun(root, testTask); - if (!shouldRun) { - LOG.warn(String.format("Skipping %s (should run: %s)", root, shouldRun)); - continue; - } + // Get all environments from strategy and filter them + List allEnvironments = getAllEnvironments(); + List environments = getMinimalSetOfTestEnvs(allEnvironments, requiredTags); - LOG.warn(String.format("Running on root %s", root)); + if (environments.isEmpty()) { + LOG.warn("No matching environments found for tags: " + requiredTags); + return; + } + + LOG.warn(String.format("Found %d matching environments", environments.size())); + + for (PyTestEnvironment environment : environments) { + final String envDescription = environment.getDescription(); + + LOG.warn(String.format("Running on environment: %s", envDescription)); try { testTask.setUp(testName); wasExecuted = true; - final Path executable = VirtualEnvReader.getInstance().findPythonInPythonRoot(Path.of(root)); - assert executable != null : "No executable in " + root; - final Sdk sdk = getSdk(executable.toString(), testTask); + // Prepare SDK for this environment + final Sdk sdk = environment.prepareSdk(); + if (skipOnFlavors != null) { final PythonSdkFlavor flavor = PythonSdkFlavor.getFlavor(sdk); if (ContainerUtil.exists(skipOnFlavors, o -> o.isInstance(flavor))) { @@ -92,33 +99,37 @@ public class PyEnvTaskRunner { if (myLoggingRule != null) { final PyExecutionFixtureTestTask execTask = ObjectUtils.tryCast(testTask, PyExecutionFixtureTestTask.class); if (execTask != null) { - // Fill be disabled automatically on project disposing. + // Will be disabled automatically on project disposing. myLoggingRule.startLogging(execTask.getProject(), execTask.getClassesToEnableDebug()); } } + testTask.runTestOn(sdk.getHomePath(), sdk); - testTask.runTestOn(executable.toString(), sdk); - - passedRoots.add(root); + passedEnvironments.add(envDescription); } else { - LOG.warn(String.format("Skipping root %s", root)); + LOG.warn(String.format("Skipping environment %s (language level %s not supported)", envDescription, languageLevel)); } } catch (final RuntimeException | Error ex) { // Runtime and error are logged including environment info - LOG.warn(formatCollectionToString(passedRoots, "Tests passed environments") + - "Test failed on " + - getEnvType() + - " environment " + - root); + LOG.warn(formatCollectionToString(passedEnvironments, "Tests passed environments") + + "Test failed on environment " + envDescription); throw ex; } catch (final Exception e) { throw new PyEnvWrappingException(e); } finally { + // Clean up environment resources + try { + environment.close(); + } + catch (final Exception e) { + LOG.error("Failed to close environment: " + envDescription, e); + } + try { // Teardown should be called on the main thread because fixture teardown checks for // thread leaks, and blocked main thread is considered as leaked. @@ -134,52 +145,35 @@ public class PyEnvTaskRunner { LOG.warn("test " + testName + " was not executed.\n" + - formatCollectionToString(myRoots, "All roots") + - "\n" + - formatCollectionToString(requiredTags, "Required tags in tags.txt in root")); + "Required tags: " + requiredTags); } } - protected boolean shouldRun(String root, PyTestTask task) { - return true; + private List getAllEnvironments() { + return PyEnvTestCase.ALL_ENVIRONMENTS + .stream() + .map(e -> new ProviderTestEnvironment(myFactory, e.getSpec(), PredefinedPyEnvironments.Companion.getENVIRONMENTS_TO_TAGS().get(e))) + .filter(e -> myPythonVersionFilter == null || PythonVersionKt.matches(e.getPythonVersion(), myPythonVersionFilter)) + .toList(); } /** - * Get SDK by executable* - */ - @NotNull - protected Sdk getSdk(@NotNull final String executable, @NotNull final PyTestTask testTask) { - try { - final VirtualFile url = VfsUtil.findFileByIoFile(new File(executable), true); - assert url != null : "No file " + executable; - return PySdkTools.createTempSdk(url, SdkCreationType.EMPTY_SDK, null, null); - } - catch (Exception ex) { - throw new RuntimeException(ex); - } - } - - protected String getEnvType() { - return "local"; - } - - /** - * Given the path to a test environment root folder, collects and provides information about this environment. + * Wrapper for PyTestEnvironment that provides ordering and filtering logic. *

- * Instances of this class are ordered by the number of tags they provide. Typically, fewer tags - * mean fewer packages installed inside the environment. If two environments have the same Python version - * and both are suitable for a test, we prefer the one with fewer numbers of packages - * since it can improve performance when code inside is involved. + * Instances of this class are ordered by the number of tags they provide. Typically, fewer tags + * mean fewer packages installed inside the environment. If two environments have the same Python version + * and both are suitable for a test, we prefer the one with fewer numbers of packages + * since it can improve performance when code inside is involved. */ private static class EnvInfo implements Comparable { - @NotNull final String root; + @NotNull final PyTestEnvironment environment; @NotNull final List tags; @Nullable final String pythonVersion; - EnvInfo(@NotNull String root) { - this.root = root; - tags = EnvTagsKt.loadEnvTags(Path.of(root)).stream().toList(); - pythonVersion = ContainerUtil.find(tags, tag -> tag.matches("^python\\d\\.\\d+$")); + EnvInfo(@NotNull PyTestEnvironment environment) { + this.environment = environment; + this.tags = new ArrayList<>(environment.getTags()); + this.pythonVersion = ContainerUtil.find(tags, tag -> tag.matches("^python\\d\\.\\d+$")); } @Override @@ -188,20 +182,22 @@ public class PyEnvTaskRunner { } } - private @NotNull List getMinimalSetOfTestEnvs(@NotNull Set requiredTags) { - var pq = new PriorityQueue<>(ContainerUtil.map(myRoots, root -> new EnvInfo(root))); + private static @NotNull List getMinimalSetOfTestEnvs(@NotNull List allEnvironments, + @NotNull Set requiredTags) { + var pq = new PriorityQueue<>(ContainerUtil.map(allEnvironments, env -> new EnvInfo(env))); var addedPythonVersions = new HashSet(); - var result = new ArrayList(); + var result = new ArrayList(); + while (!pq.isEmpty()) { var envInfo = pq.poll(); if (isSuitableForTags(envInfo.tags, requiredTags)) { if (envInfo.pythonVersion == null) { - result.add(envInfo.root); + result.add(envInfo.environment); } else { if (!addedPythonVersions.contains(envInfo.pythonVersion)) { addedPythonVersions.add(envInfo.pythonVersion); - result.add(envInfo.root); + result.add(envInfo.environment); } } } diff --git a/python/testSrc/com/jetbrains/env/PyEnvTestCase.java b/python/testSrc/com/jetbrains/env/PyEnvTestCase.java index 84112ce54971..cedb1faee415 100644 --- a/python/testSrc/com/jetbrains/env/PyEnvTestCase.java +++ b/python/testSrc/com/jetbrains/env/PyEnvTestCase.java @@ -5,9 +5,8 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.io.FileUtil; -import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess; -import com.intellij.python.community.testFramework.testEnv.EnvTagsKt; -import com.intellij.python.community.testFramework.testEnv.PyEnvTestSettings; +import com.intellij.python.test.env.common.PredefinedPyEnvironments; +import com.intellij.python.test.env.junit4.JUnit4FactoryHolder; import com.intellij.testFramework.TestApplicationManager; import com.intellij.testFramework.UsefulTestCase; import com.intellij.util.ArrayUtil; @@ -17,15 +16,21 @@ import com.jetbrains.LoggingRule; import com.jetbrains.python.sdk.flavors.PythonSdkFlavor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.junit.*; +import org.junit.After; +import org.junit.Assume; +import org.junit.Before; +import org.junit.Rule; import org.junit.rules.TestName; import org.junit.rules.TestWatcher; -import java.io.File; import java.lang.reflect.Method; import java.nio.file.Path; -import java.util.*; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import static com.intellij.python.test.env.common.PredefinedPyEnvironments.*; import static com.intellij.testFramework.assertions.Assertions.assertThat; /** @@ -43,7 +48,6 @@ public abstract class PyEnvTestCase { @NotNull protected static final PyEnvTestSettings SETTINGS = PyEnvTestSettings.Companion.fromEnvVariables(); - /** * Rule used to capture debug logging and display it if test failed. * See also {@link PyExecutionFixtureTestTask#getClassesToEnableDebug()} and @@ -58,11 +62,6 @@ public abstract class PyEnvTestCase { */ private final String @Nullable [] myRequiredTags; - /** - * Environments and tags they provide. - */ - public static final Map> envTags = new HashMap<>(); - /** * TODO: Move to {@link EnvTestTagsRequired} as well? */ @@ -76,6 +75,22 @@ public abstract class PyEnvTestCase { LOG.info("Using following config\n" + SETTINGS.reportConfiguration()); } + /** + * All predefined environments used by PyEnvTestCase by default + */ + public static final List ALL_ENVIRONMENTS = List.of( + VENV_2_7, + VENV_3_8_FULL, + VENV_3_9, + VENV_3_10, + VENV_3_11, + VENV_3_12, + VENV_3_12_DJANGO, + VENV_3_13, + VENV_3_14 + ); + + /** * Escape test output to prevent python test be processed as test result */ @@ -116,19 +131,12 @@ public abstract class PyEnvTestCase { @NotNull private static Collection getAvailableTags() { final Collection allAvailableTags = new HashSet<>(); - for (List tags : envTags.values()) { + for (@NotNull Set<@NotNull String> tags : Companion.getENVIRONMENTS_TO_TAGS().values()) { allAvailableTags.addAll(tags); } return allAvailableTags; } - @BeforeClass - public static void collectTagsForEnvs() { - for (final String pythonRoot : getDefaultPythonRoots()) { - envTags.put(pythonRoot, EnvTagsKt.loadEnvTags(Path.of(pythonRoot)).stream().toList()); - } - } - /** * Runs task on several envs. If you care about exception thrown from task use {@link #runPythonTestWithException(PyTestTask)} */ @@ -155,36 +163,13 @@ public abstract class PyEnvTestCase { private void runTest(@NotNull PyTestTask testTask, @NotNull String testName) { Assume.assumeFalse("Running under teamcity but not by Env configuration. Test seems to be launched by accident, skip it.", UsefulTestCase.IS_UNDER_TEAMCITY && !SETTINGS.isEnvConfiguration()); - List roots = getPythonRoots(); - - /* - *

- * {@link org.junit.AssumptionViolatedException} here means this test must be skipped. - * TeamCity supports this (if not you should create and issue about that). - * Idea does not support it for JUnit 3, while JUnit 4 must be supported. - *

- *

- * It this error brakes your test, please do not revert. Instead, do the following: - *

    - *
  1. Make sure {@link com.jetbrains.env.python} tests are excluded from your configuration (unless you are - * PyCharm developer)
  2. - *
  3. Check that your environment supports {@link AssumptionViolatedException}. - * JUnit 4 was created about 10 years ago, so fixing environment is much better approach than hacky "return;" here. - *
  4. - *
- *

- */ - Assume.assumeFalse(testName + - ": environments are not defined. Skipping. \nChecks logs for settings that lead to this situation", - roots.isEmpty()); - - doRunTests(testTask, testName, roots); + doRunTests(testTask, testName); } - protected void doRunTests(PyTestTask testTask, String testName, List roots) { + protected void doRunTests(PyTestTask testTask, String testName) { Assume.assumeFalse("Tests launched in remote SDK mode, and this test is not remote", SETTINGS.useRemoteSdk()); - PyEnvTaskRunner taskRunner = new PyEnvTaskRunner(roots, myLoggingRule); + PyEnvTaskRunner taskRunner = new PyEnvTaskRunner(JUnit4FactoryHolder.INSTANCE.getOrCreate(), SETTINGS.getPythonVersion(), myLoggingRule); final EnvTestTagsRequired classAnnotation = getClass().getAnnotation(EnvTestTagsRequired.class); EnvTestTagsRequired methodAnnotation; @@ -206,7 +191,7 @@ public abstract class PyEnvTestCase { if (firstAnnotation != null) { - Assume.assumeFalse("Test skipped on this os", Arrays.stream(firstAnnotation.skipOnOSes()).anyMatch(TestEnv::isThisOs)); + Assume.assumeFalse("Test skipped on this os", ContainerUtil.exists(firstAnnotation.skipOnOSes(), TestEnv::isThisOs)); skipOnFlavors = firstAnnotation.skipOnFlavors(); } else { @@ -228,21 +213,6 @@ public abstract class PyEnvTestCase { } } - public static List getDefaultPythonRoots() { - return ContainerUtil.map(SETTINGS.getPythons(), File::getAbsolutePath); - } - - /** - * @return list of pythons to run tests against - */ - @NotNull - protected List<@NotNull String> getPythonRoots() { - var roots = getDefaultPythonRoots(); - // It is ok to access these pythons - VfsRootAccess.allowRootAccess(myDisposable, ArrayUtil.toStringArray(roots)); - return roots; - } - private final Disposable myDisposable = Disposer.newDisposable(); public Disposable getTestRootDisposable() { diff --git a/python/testSrc/com/jetbrains/env/PyEnvTestSettings.kt b/python/testSrc/com/jetbrains/env/PyEnvTestSettings.kt new file mode 100644 index 000000000000..7954af6d2511 --- /dev/null +++ b/python/testSrc/com/jetbrains/env/PyEnvTestSettings.kt @@ -0,0 +1,74 @@ +// 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.env + +import com.intellij.util.SystemProperties +import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.TestOnly + + +private val IS_UNDER_TEAMCITY: Boolean = System.getenv("TEAMCITY_VERSION") != null + +/** + * Configures env test environment using env vars and properties. + * Environment variables are also used in gradle script (setup-test-environment) + */ +@TestOnly +@ApiStatus.Internal +data class PyEnvTestSettings( + @get:JvmName("useRemoteSdk") + val useRemoteSdk: Boolean, + val isEnvConfiguration: Boolean, + val isUnderTeamCity: Boolean, + val pythonVersion: String?, +) { + /** + * Configuration in readable format + */ + fun reportConfiguration() = (PyTestEnvVars.getEnvValues() + listOf(toString())).joinToString("\n") + + companion object { + + fun fromEnvVariables(): PyEnvTestSettings { + val isUnderTeamCity = IS_UNDER_TEAMCITY + return PyEnvTestSettings( + useRemoteSdk = SystemProperties.getBooleanProperty("pycharm.run_remote", false) || PyTestEnvVars.PYCHARM_RUN_REMOTE.isSet(), + isEnvConfiguration = System.getProperty("pycharm.env") != null + || PyTestEnvVars.PYCHARM_ENV.toString() in System.getenv(), + isUnderTeamCity = isUnderTeamCity, + pythonVersion = PyTestEnvVars.PYCHARM_PY_VERSION.getValue() + ) + } + } +} + +/** + * Env variables used to configure tests + */ +@ApiStatus.Internal +enum class PyTestEnvVars(private val getVarName: (PyTestEnvVars) -> String = { it.name }) { + /** + * Set if launched using "PyEnvTests" + */ + PYCHARM_ENV, + + /** + * Only run remote-based tests + */ + PYCHARM_RUN_REMOTE, + + /** + * Run only on one PY version + */ + PYCHARM_PY_VERSION; + + + companion object { + fun getEnvValues() = entries.map { "$it : ${it.getValue()}" } + } + + override fun toString() = getVarName(this) + + fun getValue(): String? = System.getenv(toString()) + + fun isSet() = getValue() != null +} diff --git a/python/testSrc/com/jetbrains/env/PyTestEnvironment.java b/python/testSrc/com/jetbrains/env/PyTestEnvironment.java new file mode 100644 index 000000000000..af024712fdca --- /dev/null +++ b/python/testSrc/com/jetbrains/env/PyTestEnvironment.java @@ -0,0 +1,42 @@ +// 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.env; + +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.util.text.SemVer; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; + +/** + * Abstraction for a Python test environment that can be used to run tests. + * Implementations can represent either pre-built environments (legacy) or provider-based environments. + */ +public interface PyTestEnvironment extends AutoCloseable { + /** + * Get the description of this environment (e.g., root path or environment type). + */ + @NotNull + String getDescription(); + + /** + * Get the tags associated with this environment (e.g., "python3.12", "django", "pytest"). + */ + @NotNull + Set getTags(); + + /** + * Prepare the environment and create an SDK for running tests. + * + * @return the SDK to use for running tests + */ + @NotNull + Sdk prepareSdk(); + + /** + * Clean up resources associated with this environment. + */ + @Override + void close() throws Exception; + + SemVer getPythonVersion(); +} diff --git a/python/testSrc/com/jetbrains/env/debug/tests/PythonDebuggerVariablesViewTest.kt b/python/testSrc/com/jetbrains/env/debug/tests/PythonDebuggerVariablesViewTest.kt index d7f110e9d9cb..1d60ab7cd50b 100644 --- a/python/testSrc/com/jetbrains/env/debug/tests/PythonDebuggerVariablesViewTest.kt +++ b/python/testSrc/com/jetbrains/env/debug/tests/PythonDebuggerVariablesViewTest.kt @@ -407,15 +407,14 @@ class PythonDebuggerVariablesViewTest : PyEnvTestCase() { } @Throws(PyDebuggerException::class, InterruptedException::class) - fun checkVariableValue(frameVariables: List?, expected: String?, name: String?) { - val value = findDebugValueByName(frameVariables!!, name!!) - loadVariable(value) + fun checkVariableValue(frameVariables: List?, expected: String, name: String) { + val value = computeValueAsync(frameVariables, name) synchronized(this) { - while (value!!.value!!.isEmpty() || value!!.value!!.isBlank()) { - (this as Object).wait(1000) + while (value.isNullOrBlank()) { + Thread.sleep(1000) } } - Assert.assertEquals(expected, value!!.value) + Assert.assertEquals(expected, value) } }) } diff --git a/python/testSrc/com/jetbrains/env/python/PyEnvSufficiencyTest.java b/python/testSrc/com/jetbrains/env/python/PyEnvSufficiencyTest.java deleted file mode 100644 index 962cdaaf69e6..000000000000 --- a/python/testSrc/com/jetbrains/env/python/PyEnvSufficiencyTest.java +++ /dev/null @@ -1,54 +0,0 @@ -// 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.env.python; - -import com.google.common.collect.ImmutableList; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.python.community.testFramework.testEnv.EnvTagsKt; -import com.intellij.testFramework.UsefulTestCase; -import com.jetbrains.env.PyEnvTestCase; -import org.junit.Test; - -import java.nio.file.Path; -import java.util.*; - -import static com.intellij.testFramework.UsefulTestCase.assertEmpty; - -public class PyEnvSufficiencyTest extends PyEnvTestCase { - private static final List BASE_TAGS = - ImmutableList.builder().add("python3", "django", "ipython", "nose", "pytest").build(); - - @Test - public void testSufficiency() { - if (UsefulTestCase.IS_UNDER_TEAMCITY && SETTINGS.isEnvConfiguration()) { - - Set tags = new HashSet<>(); - List roots = getDefaultPythonRoots(); - if (roots.size() == 0) { - return; // not on env agent - } - for (String root : roots) { - tags.addAll(EnvTagsKt.loadEnvTags(Path.of(root))); - } - - List missing = new ArrayList<>(); - for (String tag : necessaryTags()) { - if (!tags.contains(tag)) { - missing.add(tag); - } - } - - - assertEmpty("Agent is missing environments: " + StringUtil.join(missing, ", "), missing); - } - } - - private static List necessaryTags() { - if (SystemInfo.isWindows) { - return Collections.emptyList();// ImmutableList.builder().addAll(BASE_TAGS).add("iron").build(); - } - else { - return ImmutableList.builder().addAll(BASE_TAGS).add("packaging").build(); - } - } -} diff --git a/python/testSrc/com/jetbrains/env/python/PySDKRule.kt b/python/testSrc/com/jetbrains/env/python/PySDKRule.kt index abb9589a357a..8695392c59bf 100644 --- a/python/testSrc/com/jetbrains/env/python/PySDKRule.kt +++ b/python/testSrc/com/jetbrains/env/python/PySDKRule.kt @@ -3,17 +3,18 @@ package com.jetbrains.env.python import com.intellij.execution.target.TargetEnvironmentConfiguration import com.intellij.openapi.projectRoots.Sdk -import com.jetbrains.python.tools.SdkCreationRequest.LocalPython -import com.jetbrains.python.tools.SdkCreationRequest.RemotePython -import com.jetbrains.python.tools.createSdk +import com.intellij.python.test.env.common.SdkCreationRequest.LocalPython +import com.intellij.python.test.env.common.SdkCreationRequest.RemotePython +import com.intellij.python.test.env.common.createSdk +import com.intellij.python.test.env.junit4.JUnit4FactoryHolder import kotlinx.coroutines.runBlocking import org.junit.rules.ExternalResource /** * Creates python SDK either local (if [targetConfigProducer] is null) or target. - * In case of target, it should have [com.jetbrains.python.tools.PYTHON_PATH_ON_TARGET] - * Locals are search automatically like in [com.intellij.python.community.testFramework.testEnv.PyEnvTestSettings] or using [com.jetbrains.python.sdk.flavors.PythonSdkFlavor.suggestLocalHomePaths] + * In case of target, it should have [com.intellij.python.test.env.common.PYTHON_PATH_ON_TARGET] + * Locals are search automatically like in [com.jetbrains.env.PyEnvTestSettings] or using [com.jetbrains.python.sdk.flavors.PythonSdkFlavor.suggestLocalHomePaths] */ class PySDKRule(private val targetConfigProducer: (() -> TargetEnvironmentConfiguration)?) : ExternalResource() { @@ -24,7 +25,7 @@ class PySDKRule(private val targetConfigProducer: (() -> TargetEnvironmentConfig private lateinit var autoClosable: AutoCloseable override fun before() { - val (sdk, autoClosable) = runBlocking { createSdk(targetConfigProducer?.let { RemotePython(it()) } ?: LocalPython()) } + val (sdk, autoClosable) = runBlocking { JUnit4FactoryHolder.getOrCreate().createSdk(targetConfigProducer?.let { RemotePython(it()) } ?: LocalPython) } this.sdk = sdk this.autoClosable = autoClosable } diff --git a/python/testSrc/com/jetbrains/env/python/PyToxTest.java b/python/testSrc/com/jetbrains/env/python/PyToxTest.java index a473150dbeda..cff5fe2645a4 100644 --- a/python/testSrc/com/jetbrains/env/python/PyToxTest.java +++ b/python/testSrc/com/jetbrains/env/python/PyToxTest.java @@ -7,16 +7,21 @@ import com.intellij.execution.testframework.sm.runner.SMTestProxy; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.python.test.env.core.CachingPyEnvironmentFactory; +import com.intellij.python.test.env.core.PyEnvironment; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.env.*; import com.jetbrains.python.testing.tox.PyToxConfiguration; import com.jetbrains.python.testing.tox.PyToxConfigurationFactory; import com.jetbrains.python.testing.tox.PyToxTestTools; import com.jetbrains.python.tools.sdkTools.SdkCreationType; +import kotlinx.coroutines.Dispatchers; import org.assertj.core.api.Condition; import org.hamcrest.Matchers; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import java.io.File; @@ -25,6 +30,8 @@ import java.util.*; import java.util.function.Supplier; import java.util.stream.Collectors; +import static com.intellij.python.test.env.common.PyEnvironmentFactoriesKt.createPyEnvironmentFactory; +import static kotlinx.coroutines.BuildersKt.runBlocking; import static org.assertj.core.api.Assertions.assertThat; /** @@ -34,17 +41,25 @@ import static org.assertj.core.api.Assertions.assertThat; */ @EnvTestTagsRequired(tags = "python3.8") public final class PyToxTest extends PyEnvTestCase { + + private List myRoots; + public PyToxTest() { super("tox"); } + @Before + public void setUp() throws Exception { + myRoots = getEnvironmentRoots(); + } + /** * Simply ensure tox runner works */ @Test public void testToxSimpleRun() { runPythonTest(new MyPyProcessWithConsoleTestTask("/toxtest/toxSimpleRun/", 2, - () -> new MyTestProcessRunner(), + () -> createTestProcessRunner(), Collections.singletonList( Pair.create("py39", new InterpreterExpectations("", true)) ), @@ -57,7 +72,7 @@ public final class PyToxTest extends PyEnvTestCase { @Test public void testToxPyTest() { runPythonTest(new MyPyProcessWithConsoleTestTask("/toxtest/toxPyTest/", 1, - () -> new MyTestProcessRunner(), + () -> createTestProcessRunner(), Arrays.asList( Pair.create("py39", new InterpreterExpectations("", true)), Pair.create("py38", new InterpreterExpectations("Assertion", false)) @@ -70,7 +85,7 @@ public final class PyToxTest extends PyEnvTestCase { @Test public void testToxPyTestXDist() { runPythonTest(new MyPyProcessWithConsoleTestTask("/toxtest/toxPyTestXDist/", 1, - () -> new MyTestProcessRunner(), + () -> createTestProcessRunner(), Arrays.asList( Pair.create("py38", new InterpreterExpectations("", false)), Pair.create("py39", new InterpreterExpectations("", true)) @@ -95,7 +110,7 @@ public final class PyToxTest extends PyEnvTestCase { @Test public void testToxPyTestPy3k() { runPythonTest(new MyPyProcessWithConsoleTestTask("/toxtest/toxPyTestPy3k/", 1, - () -> new MyTestProcessRunner(), + () -> createTestProcessRunner(), Collections.singletonList( Pair.create("py38", new InterpreterExpectations("", true)) ), @@ -109,7 +124,7 @@ public final class PyToxTest extends PyEnvTestCase { @Test public void testToxUnitTest() { runPythonTest(new MyPyProcessWithConsoleTestTask("/toxtest/toxUnitTest/", 1, - () -> new MyTestProcessRunner(), + () -> createTestProcessRunner(), Arrays.asList( Pair.create("py39", new InterpreterExpectations("", true)), Pair.create("py38", new InterpreterExpectations("Assertion", false)) @@ -124,7 +139,7 @@ public final class PyToxTest extends PyEnvTestCase { @Test public void textToxOneInterpreter() { runPythonTest(new MyPyProcessWithConsoleTestTask("/toxtest/toxOneInterpreter/", 0, - () -> new MyTestProcessRunner(), + () -> createTestProcessRunner(), Arrays.asList( Pair.create("py39", new InterpreterExpectations("ython 3.9", true)), Pair.create("py38", new InterpreterExpectations("", false)) @@ -140,7 +155,7 @@ public final class PyToxTest extends PyEnvTestCase { @Test public void testDoubleRun() { runPythonTest(new MyPyProcessWithConsoleTestTask("/toxtest/toxDoubleRun/", 1, - () -> new MyTestProcessRunner(), + () -> createTestProcessRunner(), Collections.singletonList( Pair.create("py39", new InterpreterExpectations("", true)) ), @@ -154,7 +169,7 @@ public final class PyToxTest extends PyEnvTestCase { @Test public void testToxSuccessTest() { runPythonTest(new MyPyProcessWithConsoleTestTask("/toxtest/toxSuccess/", 1, - () -> new MyTestProcessRunner(), + () -> createTestProcessRunner(), Arrays.asList( Pair.create("py39", new InterpreterExpectations("I am 3.9", true)), // Should have output @@ -170,7 +185,7 @@ public final class PyToxTest extends PyEnvTestCase { @Test public void testEnvRerun() { runPythonTest(new MyPyProcessWithConsoleTestTask("/toxtest/toxConcreteEnv/", 0, - () -> new MyTestProcessRunner(1), + () -> createTestProcessRunner(1), Arrays.asList( //26 and 39 only used for first time, they aren't used after rerun Pair.create("py39", new InterpreterExpectations("", true, 1)), @@ -180,6 +195,27 @@ public final class PyToxTest extends PyEnvTestCase { ); } + private @NotNull MyTestProcessRunner createTestProcessRunner() { + return createTestProcessRunner(0); + } + + private MyTestProcessRunner createTestProcessRunner(int timesToRerunFailedTests) { + return new MyTestProcessRunner(timesToRerunFailedTests, myRoots); + } + + private static List getEnvironmentRoots() { + CachingPyEnvironmentFactory factory = createPyEnvironmentFactory(); + return ContainerUtil.map(PyEnvTestCase.ALL_ENVIRONMENTS, e -> { + try { + PyEnvironment environment = runBlocking(Dispatchers.getIO(), (scope, continuation) -> factory.createEnvironment(e.getSpec(), continuation)); + return environment.getEnvPath().toString(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return null; + } + }); + } + /** * Ensures {posargs} are passed correctly @@ -192,7 +228,7 @@ public final class PyToxTest extends PyEnvTestCase { @NotNull @Override protected PyAbstractTestProcessRunner createProcessRunner() { - return new MyTestProcessRunner() { + return new MyTestProcessRunner(0, myRoots) { @Override protected void configurationCreatedAndWillLaunch(@NotNull final PyToxConfiguration configuration) throws IOException { super.configurationCreatedAndWillLaunch(configuration); @@ -229,7 +265,7 @@ public final class PyToxTest extends PyEnvTestCase { @NotNull @Override protected PyAbstractTestProcessRunner createProcessRunner() { - return new RunnerWithArguments(envsToRun, "-v"); + return new RunnerWithArguments(myRoots, envsToRun, "-v"); } @Override @@ -264,7 +300,7 @@ public final class PyToxTest extends PyEnvTestCase { @NotNull @Override protected PyAbstractTestProcessRunner createProcessRunner() { - return new RunnerWithArguments(new String[]{"py39"}, "--", "--version"); + return new RunnerWithArguments(myRoots, new String[]{"py39"}, "--", "--version"); } @Override @@ -451,22 +487,22 @@ public final class PyToxTest extends PyEnvTestCase { } private static class MyTestProcessRunner extends PyAbstractTestProcessRunner { - private MyTestProcessRunner() { - this(0); - } - private MyTestProcessRunner(final int timesToRerunFailedTests) { + private final List myRoots; + + private MyTestProcessRunner(int timesToRerunFailedTests, List roots) { super(PyToxConfigurationFactory.INSTANCE, PyToxConfiguration.class, timesToRerunFailedTests); + myRoots = roots; } @Override protected void configurationCreatedAndWillLaunch(@NotNull PyToxConfiguration configuration) throws IOException { super.configurationCreatedAndWillLaunch(configuration); - fixPathForTox(configuration); + fixPathForTox(configuration, myRoots); } } - private static void fixPathForTox(final @NotNull PyToxConfiguration configuration) { + private static void fixPathForTox(final @NotNull PyToxConfiguration configuration, List envRoots) { // To help tox with all interpreters, we add all our environments to path // Envs should have binaries like "python2.7" (with version included), // and tox will find em: see tox_get_python_executable @ interpreters.py @@ -474,7 +510,7 @@ public final class PyToxTest extends PyEnvTestCase { //On linux we also need shared libs from Anaconda, so we add it to LD_LIBRARY_PATH final List roots = new ArrayList<>(); final List libs = new ArrayList<>(); - for (final String root : getDefaultPythonRoots()) { + for (final String root : envRoots) { File bin = new File(root, "/bin/"); roots.add(bin.exists() ? bin.getAbsolutePath() : root); File lib = new File(root, "/lib/"); @@ -522,11 +558,13 @@ public final class PyToxTest extends PyEnvTestCase { private static class RunnerWithArguments extends PyAbstractTestProcessRunner { private final String[] myEnvsToRun; private final String[] myArgs; + private final List myRoots; - RunnerWithArguments(final String @NotNull [] envsToRun, final String @NotNull ... args) { + RunnerWithArguments(final List roots, final String @NotNull [] envsToRun, final String @NotNull ... args) { super(PyToxConfigurationFactory.INSTANCE, PyToxConfiguration.class, 0); myEnvsToRun = envsToRun; myArgs = args; + myRoots = roots; } @Override @@ -534,7 +572,7 @@ public final class PyToxTest extends PyEnvTestCase { super.configurationCreatedAndWillLaunch(configuration); PyToxTestTools.setArguments(configuration, myArgs); PyToxTestTools.setRunOnlyEnvs(configuration, myEnvsToRun); - fixPathForTox(configuration); + fixPathForTox(configuration, myRoots); } } } diff --git a/python/testSrc/com/jetbrains/env/python/conda/LocalCondaRule.kt b/python/testSrc/com/jetbrains/env/python/conda/LocalCondaRule.kt index d84d0cc25df3..c505678a8a27 100644 --- a/python/testSrc/com/jetbrains/env/python/conda/LocalCondaRule.kt +++ b/python/testSrc/com/jetbrains/env/python/conda/LocalCondaRule.kt @@ -4,7 +4,10 @@ package com.jetbrains.env.python.conda import com.intellij.execution.target.FullPathOnTarget import com.intellij.python.community.execService.BinOnEel import com.intellij.python.community.execService.BinaryToExec -import com.intellij.python.community.testFramework.testEnv.conda.TypeConda +import com.intellij.python.test.env.common.PredefinedPyEnvironments +import com.intellij.python.test.env.common.createEnvironment +import com.intellij.python.test.env.conda.CondaPyEnvironment +import com.intellij.python.test.env.junit4.JUnit4FactoryHolder import com.jetbrains.python.getOrThrow import com.jetbrains.python.sdk.add.v2.Version import com.jetbrains.python.sdk.add.v2.conda.getCondaVersion @@ -37,15 +40,16 @@ class LocalCondaRule : ExternalResource() { override fun before() { super.before() - val (_, autoCloseable, condaPathEnv) = runBlocking { TypeConda.createSdkClosableEnv().getOrElse { throw AssumptionViolatedException("No conda found, run gradle script to install test env") } } - - condaPath = Path.of(condaPathEnv.fullCondaPathOnTarget) + val env = runBlocking { + JUnit4FactoryHolder.getOrCreate().createEnvironment(PredefinedPyEnvironments.CONDA).unwrap() ?: error("No conda found") + } + condaPath = env.condaExecutable if (!condaPath.isExecutable()) { throw AssumptionViolatedException("$condaPath is not executable") } condaVersion = runBlocking { BinOnEel(condaPath).getCondaVersion().getOrThrow() } - this.autoCloseable = autoCloseable + this.autoCloseable = env } override fun after() {