Repository: xonsh/xonsh

Issue:
xonsh-virtualenv
Hello, I'd like to migrate out the xonsh activator from the virtualenv project into a virtualenv-xonsh project. The reason for this is that xonsh seems is sensitive to new Pythons/package releases, and as such breaks quite often virtualenvs own CI. virtualenv is a crucial core project for the python ecosystem, and this is consuming significant time for me to handle that I'd like to avoid. So I'd like to spin out this feature from core virtualenv into a plugin. Can you setup a repository for this please under this domain so I can make a PR against it and hand over it to the project itself this maintainership? Thanks.


Discussion:
Hey @gaborbernat - It is hard for xonsh to target unreleased versions of Python. Is there any way for virtualenv to just skip the xonsh tests on unrelased versions of python? It would be a big regression for xonshusers to not be able to activate environments
It's not just about new versions, but e.g. the ci breaks on windows release, and this hasn't been the first case of this. 
Note they could still activate environments, just not out of box, they'd have to install the virtualenv plugin. 
There was a xonsh activator?
Here's the latest of these:

```
INTERNALERROR> Traceback (most recent call last):
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\_pytest\main.py", line 265, in wrap_session
INTERNALERROR>     config._do_configure()
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\_pytest\config\__init__.py", line 982, in _do_configure
INTERNALERROR>     self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\pluggy\hooks.py", line 308, in call_historic
INTERNALERROR>     res = self._hookexec(self, self.get_hookimpls(), kwargs)
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\pluggy\manager.py", line 93, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook, methods, kwargs)
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\pluggy\manager.py", line 84, in <lambda>
INTERNALERROR>     self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\pluggy\callers.py", line 208, in _multicall
INTERNALERROR>     return outcome.get_result()
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\pluggy\callers.py", line 80, in get_result
INTERNALERROR>     raise ex[1].with_traceback(ex[2])
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\pluggy\callers.py", line 187, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\xonsh\pytest_plugin.py", line 13, in pytest_configure
INTERNALERROR>     setup()
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\xonsh\main.py", line 548, in setup
INTERNALERROR>     execer = Execer(xonsh_ctx=ctx)
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\xonsh\execer.py", line 63, in __init__
INTERNALERROR>     load_builtins(execer=self, ctx=xonsh_ctx)
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\xonsh\built_ins.py", line 1346, in load_builtins
INTERNALERROR>     builtins.__xonsh__.link_builtins(execer=execer)
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\xonsh\built_ins.py", line 1480, in link_builtins
INTERNALERROR>     resetting_signal_handle(sig, _lastflush)
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\xonsh\built_ins.py", line 87, in resetting_signal_handle
INTERNALERROR>     signal.signal(sig, newh)
INTERNALERROR>   File "C:\hostedtoolcache\windows\Python\3.8.6\x64\lib\signal.py", line 47, in signal
INTERNALERROR>     handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler))
INTERNALERROR> ValueError: signal only works in main thread
INTERNALERROR> Traceback (most recent call last):
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\_pytest\main.py", line 265, in wrap_session
INTERNALERROR>     config._do_configure()
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\_pytest\config\__init__.py", line 982, in _do_configure
INTERNALERROR>     self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\pluggy\hooks.py", line 308, in call_historic
INTERNALERROR>     res = self._hookexec(self, self.get_hookimpls(), kwargs)
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\pluggy\manager.py", line 93, in _hookexec
INTERNALERROR>     return self._inner_hookexec(hook, methods, kwargs)
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\pluggy\manager.py", line 84, in <lambda>
INTERNALERROR>     self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\pluggy\callers.py", line 208, in _multicall
INTERNALERROR>     return outcome.get_result()
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\pluggy\callers.py", line 80, in get_result
INTERNALERROR>     raise ex[1].with_traceback(ex[2])
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\pluggy\callers.py", line 187, in _multicall
INTERNALERROR>     res = hook_impl.function(*args)
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\xonsh\pytest_plugin.py", line 13, in pytest_configure
INTERNALERROR>     setup()
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\xonsh\main.py", line 548, in setup
INTERNALERROR>     execer = Execer(xonsh_ctx=ctx)
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\xonsh\execer.py", line 63, in __init__
INTERNALERROR>     load_builtins(execer=self, ctx=xonsh_ctx)
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\xonsh\built_ins.py", line 1346, in load_builtins
INTERNALERROR>     builtins.__xonsh__.link_builtins(execer=execer)
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\xonsh\built_ins.py", line 1480, in link_builtins
INTERNALERROR>     resetting_signal_handle(sig, _lastflush)
INTERNALERROR>   File "D:\a\virtualenv\virtualenv\.tox\py38\lib\site-packages\xonsh\built_ins.py", line 87, in resetting_signal_handle
INTERNALERROR>     signal.signal(sig, newh)
INTERNALERROR>   File "C:\hostedtoolcache\windows\Python\3.8.6\x64\lib\signal.py", line 47, in signal
INTERNALERROR>     handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler))
INTERNALERROR> ValueError: signal only works in main thread
```

https://github.com/pypa/virtualenv/runs/1584927387?check_suite_focus=true


> Can you setup a repository for this please under this domain so I can make a PR against it and hand over it to the project itself this maintainership?

Due to lack of progress on this, I'm simply removing xonsh from virtualenv. Thanks!
I'm not (yet) a xonsh contributor, but as I regularly make use of this feature I am willing to put int the work to update and migrate the activator from the virtualenv project. I won't be able to complete it quickly but seeing as this hasn't been worked on for six months I  would say better than nothing :).

I arrived here after I noticed `activate.xsh` was gone from my new virtualenvs :disappointed: .

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -324,6 +324,7 @@ def main():
             "xonsh.pytest",
             "xonsh.lib",
             "xonsh.webconfig",
+            "xonsh.virtualenv",
             "xompletions",
         ],
         package_dir={
@@ -337,6 +338,7 @@ def main():
             "xonsh": ["*.json", "*.githash"],
             "xontrib": ["*.xsh"],
             "xonsh.lib": ["*.xsh"],
+            "xonsh.virtualenv": ["*.xsh"],
             "xonsh.webconfig": [
                 "*.html",
                 "js/app.min.js",
@@ -356,6 +358,7 @@ def main():
             "xonshcon = xonsh.pyghooks:XonshConsoleLexer",
         ],
         "pytest11": ["xonsh = xonsh.pytest.plugin"],
+        "virtualenv.activate": ["xonsh = xonsh.virtualenv:XonshActivator"],
         "console_scripts": [
             "xonsh = xonsh.main:main",
             "xonsh-cat = xonsh.xoreutils.cat:main",
diff --git a/xonsh/virtualenv/__init__.py b/xonsh/virtualenv/__init__.py
new file mode 100644
--- /dev/null
+++ b/xonsh/virtualenv/__init__.py
@@ -0,0 +1,11 @@
+from virtualenv.activation.via_template import ViaTemplateActivator  # type: ignore
+from virtualenv.util.path import Path  # type: ignore
+
+
+class XonshActivator(ViaTemplateActivator):
+    def templates(self):
+        yield Path("activate.xsh")
+
+    @classmethod
+    def supports(cls, interpreter):
+        return interpreter.version_info >= (3, 5)


Test patch:
diff --git a/requirements/tests.txt b/requirements/tests.txt
--- a/requirements/tests.txt
+++ b/requirements/tests.txt
@@ -16,6 +16,7 @@ black==22.1.0
 isort >= 5.10
 pre-commit
 pyte>=0.8.0
+virtualenv>=20.7.2
 
 # types related
 mypy==0.941
diff --git a/tests/test_virtualenv_activator.py b/tests/test_virtualenv_activator.py
new file mode 100644
--- /dev/null
+++ b/tests/test_virtualenv_activator.py
@@ -0,0 +1,46 @@
+import sys
+from pathlib import Path
+from subprocess import check_output
+
+from xonsh.pytest.tools import ON_WINDOWS
+
+
+def test_xonsh_activator(tmp_path):
+    # Create virtualenv
+    venv_dir = tmp_path / "venv"
+    assert b"XonshActivator" in check_output(
+        [sys.executable, "-m", "virtualenv", str(venv_dir)]
+    )
+    assert venv_dir.is_dir()
+
+    # Check activation script created
+    if ON_WINDOWS:
+        bin_path = venv_dir / "Scripts"
+    else:
+        bin_path = venv_dir / "bin"
+    activate_path = bin_path / "activate.xsh"
+    assert activate_path.is_file()
+
+    # Sanity
+    original_python = check_output(
+        [sys.executable, "-m", "xonsh", "-c", "which python"]
+    ).decode()
+    assert Path(original_python).parent != bin_path
+
+    # Activate
+    venv_python = check_output(
+        [sys.executable, "-m", "xonsh", "-c", f"source {activate_path}; which python"]
+    ).decode()
+    assert Path(venv_python).parent == bin_path
+
+    # Deactivate
+    deactivated_python = check_output(
+        [
+            sys.executable,
+            "-m",
+            "xonsh",
+            "-c",
+            f"source {activate_path}; deactivate; which python",
+        ]
+    ).decode()
+    assert deactivated_python == original_python