Repository: conan-io/conan

Issue:
Virtualenv activate scripts cause infinite loop in Powershell when multiple are activated
To help us debug your issue please explain:

- [x] I've read the [CONTRIBUTING guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).
- [x] I've specified the Conan version, operating system version and any tool that can be relevant.
- [x] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.

## Environment
OS: Windows 10
Conan version: 1.17.0
Python: 3.7.4
Powershell: 5.1

## Problem
Running multiple virtualenv scripts in Powershell causes an infinite loop where it repeatedly prints the prompt.

## Reproducing the problem
```
> conan install cmake_installer/3.15.0@conan/stable -g virtualenv -g virtualbuildenv
> ./activate_build.ps1
> ./activate.ps1
```
Output:
```
(conanenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv)
(conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) 
(conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) 
(conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) 
(conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) 
(conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) 
(conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) 
(conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) 
(conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) 
(conanbuildenv) (conanbuildenv) (conanbuildenv) (conanbuildenv) ...
```


Discussion:
Hi @notskm and thanks a lot for reporting.

It looks like a clear bug that could be related to the variables where old environment is saved when the deactivate script is later executed. I would take a look at this. Have you found a workaround or any other hint about the reason of this issue?

Thanks!
As far as workarounds, you could either not use Powershell or edit the scripts manually. That's all I can think of. Unfortunately, the .sh scripts handle paths incorrectly #5528, so the only simple option is to use the .bat scripts.

I don't have any other information, unfortunately.
I just took a closer look at the activate scripts. The problem is that running multiple causes `_old_conan_prompt` to become recursive.

Each activate script begins with code to modify Powershell's prompt function.

```powershell
function global:_old_conan_prompt {""}
$function:_old_conan_prompt = $function:prompt
function global:prompt { write-host "(conanenv) " -nonewline; & $function:_old_conan_prompt }
```

If we insert a couple lines, we can examine the content of those functions.
```powershell
echo "_old_conan_prompt: $function:_old_conan_prompt"
echo "global:prompt: $function:global:prompt"
```

Calling this script once works as expected and produces the following output:

```powershell
_old_conan_prompt:
"PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) ";
# .Link
# https://go.microsoft.com/fwlink/?LinkID=225750
# .ExternalHelp System.Management.Automation.dll-help.xml

global:prompt:  write-host "(conanenv) " -nonewline; & $function:_old_conan_prompt
```

Calling this script a second time or running a second activate script with the same `echo` commands inserted gets stuck in the infinite loop I described after producing the following output:

```powershell
_old_conan_prompt:  write-host "(conanenv) " -nonewline; & $function:_old_conan_prompt
global:prompt:  write-host "(conanenv) " -nonewline; & $function:_old_conan_prompt
```

As you can see, `_old_conan_prompt` is now recursive. When Powershell runs `global:prompt` to print the prompt, it gets stuck running `_old_conan_prompt`.
Any news on this?

A workaround for anybody that doesn't want to use the `.bat` scripts:

```powershell
Get-ChildItem ./*activate*.ps1 | ForEach-Object {
    $content = (get-content -Path $_ | Select-String -Pattern '_old_conan_prompt' -NotMatch)
    Set-Content -Path $_ -Value $content
}
```

This snippet removes the problematic lines in the `.ps1` scripts, allowing them to work.
Hi @notskm and sorry for the late response.

I tried the steps and managed to reproduce the issue. I am not a powershell expert at all, so it is hard to evaluate the fix you proposed, but if you feel it is a good solution maybe you could contribute that as a pull request and we will include in the next releases. Thanks!

Resolving patch:
diff --git a/conans/client/envvars/environment.py b/conans/client/envvars/environment.py
--- a/conans/client/envvars/environment.py
+++ b/conans/client/envvars/environment.py
@@ -70,7 +70,7 @@
 
 ps1_activate = textwrap.dedent("""\
     {%- for it in modified_vars %}
-    $env:CONAN_OLD_{{it}}=$env:{{it}}
+    $env:CONAN_OLD_{{venv_name}}_{{it}}=$env:{{it}}
     {%- endfor %}
 
     foreach ($line in Get-Content "{{ environment_file }}") {
@@ -79,20 +79,20 @@
         Set-Item env:\\$var -Value "$value_expanded"
     }
 
-    function global:_old_conan_prompt {""}
-    $function:_old_conan_prompt = $function:prompt
+    function global:_old_conan_{{venv_name}}_prompt {""}
+    $function:_old_conan_{{venv_name}}_prompt = $function:prompt
     function global:prompt {
-        write-host "({{venv_name}}) " -nonewline; & $function:_old_conan_prompt
+        write-host "({{venv_name}}) " -nonewline; & $function:_old_conan_{{venv_name}}_prompt
     }
 """)
 
 ps1_deactivate = textwrap.dedent("""\
-    $function:prompt = $function:_old_conan_prompt
-    remove-item function:_old_conan_prompt
+    $function:prompt = $function:_old_conan_{{venv_name}}_prompt
+    remove-item function:_old_conan_{{venv_name}}_prompt
 
     {% for it in modified_vars %}
-    $env:{{it}}=$env:CONAN_OLD_{{it}}
-    Remove-Item env:CONAN_OLD_{{it}}
+    $env:{{it}}=$env:CONAN_OLD_{{venv_name}}_{{it}}
+    Remove-Item env:CONAN_OLD_{{venv_name}}_{{it}}
     {%- endfor %}
     {%- for it in new_vars %}
     Remove-Item env:{{it}}
@@ -174,7 +174,8 @@ def _files(env_vars, vars_with_spaces, flavor, activate_tpl, deactivate_tpl, ven
     activate_content = activate_tpl.render(environment_file=env_filepath,
                                            modified_vars=modified_vars, new_vars=new_vars,
                                            venv_name=venv_name)
-    deactivate_content = deactivate_tpl.render(modified_vars=modified_vars, new_vars=new_vars)
+    deactivate_content = deactivate_tpl.render(modified_vars=modified_vars, new_vars=new_vars, 
+                                               venv_name=venv_name)
 
     environment_lines = ["{}={}".format(name, value) for name, value, _ in ret]
     # This blank line is important, otherwise the script doens't process last line


Test patch: