diff --git a/.coveragerc b/.coveragerc index ad3d8fd04..8076ebd38 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,6 +2,7 @@ branch = true source = xonsh/ + xontrib/ omit = */__amalgam__.py xonsh/lazyasd.py diff --git a/AUTHORS.rst b/AUTHORS.rst index fdf928e00..75322b906 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -207,5 +207,6 @@ Authors are sorted by number of commits. * chengxuncc * goodboy * Atsushi Morimoto +* Caleb Hattingh diff --git a/news/vox-activate-absolute-path.rst b/news/vox-activate-absolute-path.rst new file mode 100644 index 000000000..e23334c14 --- /dev/null +++ b/news/vox-activate-absolute-path.rst @@ -0,0 +1,24 @@ +**Added:** + +* + +**Changed:** + +* ``vox activate`` will now prepend the absolute path of the virtualenv ``bin/`` directory (or ``Scripts/`` on Windows) to ``$PATH``; before this was a relative path. + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* + diff --git a/tests/test_vox.py b/tests/test_vox.py index 80ee59803..9d84f390b 100644 --- a/tests/test_vox.py +++ b/tests/test_vox.py @@ -1,9 +1,10 @@ """Vox tests""" -import builtins import stat import os +import subprocess as sp import pytest +import sys from xontrib.voxapi import Vox from tools import skip_if_on_conda, skip_if_on_msys @@ -80,6 +81,55 @@ def test_activate(xonsh_builtins, tmpdir): assert last_event == ("deactivate", "spam") +@skip_if_on_msys +@skip_if_on_conda +def test_activate_non_vox_venv(xonsh_builtins, tmpdir): + """ + Create a virtual environment using Python's built-in venv module + (not in VIRTUALENV_HOME) and verify that vox can activate it correctly. + """ + xonsh_builtins.__xonsh__.env.setdefault("PATH", []) + + last_event = None + + @xonsh_builtins.events.vox_on_activate + def activate(name, path, **_): + nonlocal last_event + last_event = "activate", name, path + + @xonsh_builtins.events.vox_on_deactivate + def deactivate(name, path, **_): + nonlocal last_event + last_event = "deactivate", name, path + + with tmpdir.as_cwd(): + venv_dirname = 'venv' + sp.run([sys.executable, '-m', 'venv', venv_dirname]) + vox = Vox() + vox.activate(venv_dirname) + vxv = vox[venv_dirname] + + env = xonsh_builtins.__xonsh__.env + assert os.path.isabs(vxv.bin) + assert env["PATH"][0] == vxv.bin + assert os.path.isabs(vxv.env) + assert env["VIRTUAL_ENV"] == vxv.env + assert last_event == ( + "activate", + venv_dirname, + str(pathlib.Path(str(tmpdir)) / 'venv') + ) + + vox.deactivate() + assert not env["PATH"] + assert "VIRTUAL_ENV" not in env + assert last_event == ( + "deactivate", + tmpdir.join(venv_dirname), + str(pathlib.Path(str(tmpdir)) / 'venv') + ) + + @skip_if_on_msys @skip_if_on_conda def test_path(xonsh_builtins, tmpdir): diff --git a/xontrib/voxapi.py b/xontrib/voxapi.py index 348757a3e..920e7bef6 100644 --- a/xontrib/voxapi.py +++ b/xontrib/voxapi.py @@ -4,8 +4,8 @@ API for Vox, the Python virtual environment manager for xonsh. Vox defines several events related to the life cycle of virtual environments: * ``vox_on_create(env: str) -> None`` -* ``vox_on_activate(env: str) -> None`` -* ``vox_on_deactivate(env: str) -> None`` +* ``vox_on_activate(env: str, path: pathlib.Path) -> None`` +* ``vox_on_deactivate(env: str, path: pathlib.Path) -> None`` * ``vox_on_delete(env: str) -> None`` """ import os @@ -37,7 +37,7 @@ Fired after an environment is created. events.doc( "vox_on_activate", """ -vox_on_activate(env: str) -> None +vox_on_activate(env: str, path: pathlib.Path) -> None Fired after an environment is activated. """, @@ -46,7 +46,7 @@ Fired after an environment is activated. events.doc( "vox_on_deactivate", """ -vox_on_deactivate(env: str) -> None +vox_on_deactivate(env: str, path: pathlib.Path) -> None Fired after an environment is deactivated. """, @@ -88,7 +88,7 @@ def _mkvenv(env_dir): This only cares about the platform. No filesystem calls are made. """ - env_dir = os.path.normpath(env_dir) + env_dir = os.path.abspath(env_dir) if ON_WINDOWS: binname = os.path.join(env_dir, "Scripts") incpath = os.path.join(env_dir, "Include") @@ -363,7 +363,7 @@ class Vox(collections.abc.Mapping): if "PYTHONHOME" in env: type(self).oldvars["PYTHONHOME"] = env.pop("PYTHONHOME") - events.vox_on_activate.fire(name=name) + events.vox_on_activate.fire(name=name, path=ve.env) def deactivate(self): """ @@ -382,7 +382,7 @@ class Vox(collections.abc.Mapping): env.pop("VIRTUAL_ENV") - events.vox_on_deactivate.fire(name=env_name) + events.vox_on_deactivate.fire(name=env_name, path=self[env_name].env) return env_name def __delitem__(self, name):