diff --git a/docs/changelog/1596.feature.rst b/docs/changelog/1596.feature.rst new file mode 100644 index 000000000..45d8cf744 --- /dev/null +++ b/docs/changelog/1596.feature.rst @@ -0,0 +1 @@ +When aliasing interpreters, use relative symlinks - by :user:`asottile`. diff --git a/src/virtualenv/create/via_global_ref/builtin/ref.py b/src/virtualenv/create/via_global_ref/builtin/ref.py index d75e3d70a..a579c4b37 100644 --- a/src/virtualenv/create/via_global_ref/builtin/ref.py +++ b/src/virtualenv/create/via_global_ref/builtin/ref.py @@ -7,8 +7,8 @@ from six import add_metaclass -from virtualenv.info import PY3, fs_is_case_sensitive, fs_supports_symlink -from virtualenv.util.path import copy, link, make_exe, symlink +from virtualenv.info import fs_is_case_sensitive, fs_supports_symlink +from virtualenv.util.path import copy, make_exe, symlink from virtualenv.util.six import ensure_text @@ -95,9 +95,6 @@ def run(self, creator, symlinks): method(self.src, dst) -alias_via = link if PY3 else (symlink if PathRef.FS_SUPPORTS_SYMLINK else copy) - - class ExePathRefToDest(PathRefToDest, ExePathRef): def __init__(self, src, targets, dest, must_copy=False): ExePathRef.__init__(self, src) @@ -119,5 +116,8 @@ def run(self, creator, symlinks): link_file = bin_dir / extra if link_file.exists(): link_file.unlink() - alias_via(dest, link_file) + if symlinks: + link_file.symlink_to(self.base) + else: + copy(self.src, link_file) make_exe(link_file) diff --git a/src/virtualenv/util/path/__init__.py b/src/virtualenv/util/path/__init__.py index acc65f14e..2d9d56a38 100644 --- a/src/virtualenv/util/path/__init__.py +++ b/src/virtualenv/util/path/__init__.py @@ -2,11 +2,10 @@ from ._pathlib import Path from ._permission import make_exe -from ._sync import copy, copytree, ensure_dir, link, symlink +from ._sync import copy, copytree, ensure_dir, symlink __all__ = ( "ensure_dir", - "link", "symlink", "copy", "copytree", diff --git a/src/virtualenv/util/path/_sync.py b/src/virtualenv/util/path/_sync.py index df687a929..09fd006bb 100644 --- a/src/virtualenv/util/path/_sync.py +++ b/src/virtualenv/util/path/_sync.py @@ -4,14 +4,11 @@ import os import shutil -from six import PY2, PY3 +from six import PY2 from virtualenv.info import IS_CPYTHON, IS_WIN from virtualenv.util.six import ensure_text -if PY3: - from os import link as os_link - if PY2 and IS_CPYTHON and IS_WIN: # CPython2 on Windows supports unicode paths if passed as unicode norm = lambda src: ensure_text(str(src)) # noqa else: @@ -62,12 +59,6 @@ def copytree(src, dest): shutil.copy(src_f, dest_f) -def link(src, dest): - ensure_safe_to_do(src, dest) - logging.debug("hard link %s", _Debug(src, dest.name)) - os_link(norm(src), norm(dest)) - - class _Debug(object): def __init__(self, src, dest): self.src = src @@ -83,8 +74,6 @@ def __str__(self): "ensure_dir", "symlink", "copy", - "link", "symlink", - "link", "copytree", ) diff --git a/tests/unit/create/test_creator.py b/tests/unit/create/test_creator.py index e17ea130b..1570c2b08 100644 --- a/tests/unit/create/test_creator.py +++ b/tests/unit/create/test_creator.py @@ -170,6 +170,26 @@ def list_to_str(iterable): assert common, "\n".join(difflib.unified_diff(list_to_str(sys_path), list_to_str(system_sys_path))) + # test that the python executables in the bin directory are either: + # - files + # - absolute symlinks outside of the venv + # - relative symlinks inside of the venv + if sys.platform == "win32": + exes = ("python.exe",) + else: + # TODO: also test "python{}.{}" once fixed + exes = ("python", "python{}".format(sys.version_info[0])) + for exe in exes: + exe_path = result.creator.bin_dir / exe + assert exe_path.exists() + if not exe_path.is_symlink(): # option 1: a real file + continue # it was a file + link = os.readlink(str(exe_path)) + if not os.path.isabs(link): # option 2: a relative symlink + continue + # option 3: an absolute symlink, should point outside the venv + assert not link.startswith(str(result.creator.dest)) + @pytest.mark.skipif(not CURRENT.has_venv, reason="requires interpreter with venv") def test_venv_fails_not_inline(tmp_path, capsys, mocker):