Repository: pypa/virtualenv

Issue:
lib64 symlink can break subtly since 16.1
## tldr
When creating a virtualenv on a path that contains a symlink, the virtualenv break if this symlink is removed due to the changes made in https://github.com/pypa/virtualenv/commit/5d560144432642a91f6e1661bed22e637226c8fe

 ## context
When running tests, we create a workspace folder for each test, which has a symlink to a shared virtual env. The first test case populates the virtualenv. Then the first testcase's workspace is removed. 

Prior to 16.1 this worked as expected. However, in 16.1 the lib64 symlink is absolute (but not canonical). So when the workspace of the first test is removed, the shared virtual env breaks. The next test run produces the error "module pip not found". 

## fix 
revert the change on line 1622 (https://github.com/pypa/virtualenv/commit/5d560144432642a91f6e1661bed22e637226c8fe#diff-51642b24ac9529f4baa0f483fa257d2dL1622)


Discussion:

Resolving patch:
diff --git a/src/virtualenv.py b/src/virtualenv.py
--- a/src/virtualenv.py
+++ b/src/virtualenv.py
@@ -1478,8 +1478,10 @@ def install_python(home_dir, lib_dir, inc_dir, bin_dir, site_packages, clear, sy
             raise e
 
     proc_stdout = proc_stdout.strip().decode("utf-8")
-    proc_stdout = os.path.normcase(os.path.abspath(proc_stdout))
-    norm_home_dir = os.path.normcase(os.path.abspath(home_dir))
+    # normalize paths using realpath to ensure that a virtualenv correctly identifies itself even
+    # when adressed over a symlink
+    proc_stdout = os.path.normcase(os.path.realpath(proc_stdout))
+    norm_home_dir = os.path.normcase(os.path.realpath(home_dir))
     if hasattr(norm_home_dir, "decode"):
         norm_home_dir = norm_home_dir.decode(sys.getfilesystemencoding())
     if proc_stdout != norm_home_dir:
@@ -1635,9 +1637,9 @@ def fix_lib64(lib_dir, symlink=True):
     if os.path.lexists(lib64_link):
         return
     if symlink:
-        os.symlink(lib_dir, lib64_link)
+        os.symlink("lib", lib64_link)
     else:
-        copyfile(lib_dir, lib64_link)
+        copyfile(lib_dir, lib64_link, symlink=False)
 
 
 def resolve_interpreter(exe):


Test patch:
diff --git a/tests/test_virtualenv.py b/tests/test_virtualenv.py
--- a/tests/test_virtualenv.py
+++ b/tests/test_virtualenv.py
@@ -304,3 +304,31 @@ def test_always_copy_option():
                 )
     finally:
         shutil.rmtree(tmp_virtualenv)
+
+
+@pytest.mark.skipif(not hasattr(os, "symlink"), reason="requires working symlink implementation")
+def test_relative_symlink(tmpdir):
+    """ Test if a virtualenv works correctly if it was created via a symlink and this symlink is removed """
+
+    tmpdir = str(tmpdir)
+    ve_path = os.path.join(tmpdir, "venv")
+    os.mkdir(ve_path)
+
+    workdir = os.path.join(tmpdir, "work")
+    os.mkdir(workdir)
+
+    ve_path_linked = os.path.join(workdir, "venv")
+    os.symlink(ve_path, ve_path_linked)
+
+    lib64 = os.path.join(ve_path, "lib64")
+
+    virtualenv.create_environment(ve_path_linked, symlink=True)
+    if not os.path.lexists(lib64):
+        # no lib 64 on this platform
+        return
+
+    assert os.path.exists(lib64)
+
+    shutil.rmtree(workdir)
+
+    assert os.path.exists(lib64)