diff --git a/plugins/terminal/resources/shell-integrations/powershell/powershell-integration.ps1 b/plugins/terminal/resources/shell-integrations/powershell/powershell-integration.ps1 index 4a38429ef7a8..c9078e724901 100644 --- a/plugins/terminal/resources/shell-integrations/powershell/powershell-integration.ps1 +++ b/plugins/terminal/resources/shell-integrations/powershell/powershell-integration.ps1 @@ -16,8 +16,12 @@ Get-ChildItem env:_INTELLIJ_FORCE_PREPEND_* | ForEach-Object { } # `JEDITERM_SOURCE` is executed in its own scope now. That means, it can only run code, and export env vars. It can't export PS variables. # It might be better to source it. See MSDN for the difference between "Call operator &" and "Script scope and dot sourcing" -if (($Env:JEDITERM_SOURCE -ne $null) -and (Test-Path $Env:JEDITERM_SOURCE)) { - & $Env:JEDITERM_SOURCE +if ($Env:JEDITERM_SOURCE -ne $null) { + if (Test-Path "$Env:JEDITERM_SOURCE" -ErrorAction SilentlyContinue) { + & "$Env:JEDITERM_SOURCE" + } else { # If file doesn't exist it might be a script + Invoke-Expression "$Env:JEDITERM_SOURCE" + } Remove-Item "env:JEDITERM_SOURCE" } 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 index 657e2bbdea22..84f3f4cc1a46 100644 --- a/python/junit5Tests-framework/conda/intellij.python.community.junit5Tests.framework.conda.iml +++ b/python/junit5Tests-framework/conda/intellij.python.community.junit5Tests.framework.conda.iml @@ -13,5 +13,7 @@ + + \ 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/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/condaEnvTool.kt new file mode 100644 index 000000000000..e03f116bf579 --- /dev/null +++ b/python/junit5Tests-framework/conda/src/com/intellij/python/community/junit5Tests/framework/conda/condaEnvTool.kt @@ -0,0 +1,30 @@ +// 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.execution.processTools.getResultStdoutStr +import com.jetbrains.python.psi.LanguageLevel +import com.jetbrains.python.sdk.flavors.conda.NewCondaEnvRequest +import com.jetbrains.python.sdk.flavors.conda.PyCondaCommand +import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv +import com.jetbrains.python.sdk.flavors.conda.PyCondaEnvIdentity +import org.jetbrains.annotations.ApiStatus +import java.nio.file.Path +import kotlin.io.path.pathString + +@ApiStatus.Internal +/** + * Create conda env in [pathToCreateNewEnvIn] using [existingEnv] as a base + */ +suspend fun createCondaEnv( + existingEnv: PyCondaEnv, + pathToCreateNewEnvIn: Path, +): PyCondaEnv { + val process = PyCondaEnv.createEnv( + PyCondaCommand(existingEnv.fullCondaPathOnTarget, null), + NewCondaEnvRequest.EmptyUnnamedEnv(LanguageLevel.PYTHON311, pathToCreateNewEnvIn.pathString) + ).getOrThrow() + process.getResultStdoutStr().getOrThrow() + + val env = PyCondaEnv(PyCondaEnvIdentity.UnnamedEnv(pathToCreateNewEnvIn.pathString, false), existingEnv.fullCondaPathOnTarget) + return env +} \ No newline at end of file diff --git a/python/python-terminal/intellij.python.terminal.iml b/python/python-terminal/intellij.python.terminal.iml index f75a1ea448ed..e840b9201c0e 100644 --- a/python/python-terminal/intellij.python.terminal.iml +++ b/python/python-terminal/intellij.python.terminal.iml @@ -25,5 +25,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 d150f0b42a8e..99bf5765e409 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 @@ -2,19 +2,27 @@ package com.intellij.python.junit5Tests.env.terminal import com.intellij.execution.configurations.PathEnvironmentVariableUtil +import com.intellij.openapi.application.edtWriteAction +import com.intellij.openapi.projectRoots.ProjectJdkTable +import com.intellij.openapi.projectRoots.Sdk +import com.intellij.openapi.roots.ModuleRootModificationUtil import com.intellij.platform.eel.EelExecApi import com.intellij.platform.eel.getOrThrow 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.python.community.impl.venv.tests.pyVenvFixture -import com.intellij.python.junit5Tests.framework.env.PyEnvTestCase +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.terminal.PyVirtualEnvTerminalCustomizer import com.intellij.testFramework.common.timeoutRunBlocking import com.intellij.testFramework.junit5.fixture.moduleFixture import com.intellij.testFramework.junit5.fixture.projectFixture import com.intellij.testFramework.junit5.fixture.tempPathFixture +import com.jetbrains.python.sdk.flavors.conda.PyCondaEnv +import com.jetbrains.python.sdk.persist import com.jetbrains.python.venvReader.VirtualEnvReader import kotlinx.coroutines.async import kotlinx.coroutines.launch @@ -24,11 +32,15 @@ import org.jetbrains.plugins.terminal.ShellStartupOptions import org.jetbrains.plugins.terminal.runner.LocalShellIntegrationInjector import org.jetbrains.plugins.terminal.util.ShellIntegration import org.jetbrains.plugins.terminal.util.ShellType +import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test import org.junit.jupiter.api.condition.EnabledOnOs import org.junit.jupiter.api.condition.OS +import org.junit.jupiter.api.io.TempDir +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource import java.io.IOException +import java.nio.file.Path import kotlin.io.path.Path import kotlin.io.path.name import kotlin.io.path.pathString @@ -39,7 +51,7 @@ private const val WHERE_EXE = "where.exe" /** * Run `powershell.exe` with a venv activation script and make sure there are no errors and python is correct */ -@PyEnvTestCase +@PyEnvTestCaseWithConda class PyVirtualEnvTerminalCustomizerTest { private val projectFixture = projectFixture() private val tempDirFixture = tempPathFixture(prefix = "some dir with spaces") @@ -52,15 +64,38 @@ class PyVirtualEnvTerminalCustomizerTest { moduleFixture = moduleFixture ) + private var sdkToDelete: Sdk? = null + + @AfterEach + fun tearDown(): Unit = timeoutRunBlocking { + sdkToDelete?.let { sdk -> + edtWriteAction { + ProjectJdkTable.getInstance().removeJdk(sdk) + } + } + } private val powerShell = PathEnvironmentVariableUtil.findInPath("powershell.exe")?.toPath() ?: Path((System.getenv("SystemRoot") ?: "c:\\windows"), "system32", "WindowsPowerShell", "v1.0", "powershell.exe") @EnabledOnOs(value = [OS.WINDOWS]) - @Test - fun powershellActivationTest(): Unit = timeoutRunBlocking(10.minutes) { - val pythonBinary = VirtualEnvReader.Instance.findPythonInPythonRoot(tempDirFixture.get())!! + @ParameterizedTest + @ValueSource(booleans = [true, false]) + fun powershellActivationTest(useConda: Boolean, @CondaEnv condaEnv: PyCondaEnv, @TempDir path: Path): Unit = timeoutRunBlocking(10.minutes) { + val (pythonBinary, venvDirName) = + if (useConda) { + val envDir = path.resolve("some path with spaces") + val sdk = createCondaEnv(condaEnv, envDir).createSdkFromThisEnv(null, emptyList()) + sdkToDelete = sdk + sdk.persist() + ModuleRootModificationUtil.setModuleSdk(moduleFixture.get(), sdk) + Pair(Path(sdk.homePath!!), envDir.toRealPath().pathString) + } + else { + val venv = VirtualEnvReader.Instance.findPythonInPythonRoot(tempDirFixture.get())!! + Pair(venv, tempDirFixture.get().name) + } // binary might be like ~8.3, we need to expand it as venv might report both val pythonBinaryReal = try { @@ -69,7 +104,7 @@ class PyVirtualEnvTerminalCustomizerTest { catch (_: IOException) { pythonBinary } - val shellOptions = getShellStartupOptions() + val shellOptions = getShellStartupOptions(pythonBinary.parent) val command = shellOptions.shellCommand!! val exe = command[0] val args = if (command.size == 1) emptyList() else command.subList(1, command.size) @@ -95,8 +130,7 @@ class PyVirtualEnvTerminalCustomizerTest { assertThat("We ran `$where`, so we there should be python path", output, anyOf(hasItem(pythonBinary.pathString), hasItem(pythonBinaryReal.pathString))) - val vendDirName = tempDirFixture.get().name - assertThat("There must be a line with ($vendDirName)", output, hasItem(containsString("($vendDirName)"))) + assertThat("There must be a line with ($venvDirName)", output, hasItem(containsString("($venvDirName)"))) process.exitCode.await() } @@ -107,12 +141,12 @@ class PyVirtualEnvTerminalCustomizerTest { } } - private fun getShellStartupOptions(): ShellStartupOptions { + private fun getShellStartupOptions(workDir: Path): ShellStartupOptions { val sut = PyVirtualEnvTerminalCustomizer() val env = mutableMapOf() val command = sut.customizeCommandAndEnvironment( projectFixture.get(), - tempDirFixture.get().pathString, + workDir.pathString, arrayOf(powerShell.pathString), env)