Repository: pypa/virtualenv

Issue:
virtualenv goes wild when python install is symlinked
in my setup, python is in /opt/python/{include, lib}
and symlinked into /
like this: /bin/python -> ../opt/python/bin/python

```
Symlinking Python bootstrap modules
  Symlinking /lib/python2.7/config -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/config
  Symlinking /lib/python2.7/lib-dynload -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/lib-dynload
  Symlinking /lib/python2.7/os.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/os.py
  Ignoring built-in bootstrap module: posix
  Symlinking /lib/python2.7/posixpath.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/posixpath.py
  Cannot import bootstrap module: nt
  Symlinking /lib/python2.7/ntpath.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/ntpath.py
  Symlinking /lib/python2.7/genericpath.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/genericpath.py
  Symlinking /lib/python2.7/fnmatch.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/fnmatch.py
  Symlinking /lib/python2.7/locale.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/locale.py
  Symlinking /lib/python2.7/encodings -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/encodings
  Symlinking /lib/python2.7/codecs.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/codecs.py
  Symlinking /lib/python2.7/stat.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/stat.py
  Symlinking /lib/python2.7/UserDict.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/UserDict.py
  Symlinking /lib/python2.7/copy_reg.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/copy_reg.py
  Symlinking /lib/python2.7/types.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/types.py
  Symlinking /lib/python2.7/re.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/re.py
  Symlinking /lib/python2.7/sre.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/sre.py
  Symlinking /lib/python2.7/sre_parse.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/sre_parse.py
  Symlinking /lib/python2.7/sre_constants.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/sre_constants.py
  Symlinking /lib/python2.7/sre_compile.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/sre_compile.py
  Symlinking /lib/python2.7/warnings.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/warnings.py
  Symlinking /lib/python2.7/linecache.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/linecache.py
  Symlinking /lib/python2.7/_abcoll.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/_abcoll.py
  Symlinking /lib/python2.7/abc.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/abc.py
  Symlinking /lib/python2.7/_weakrefset.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/_weakrefset.py
Creating /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/site-packages
Writing /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/site.py
Writing /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/orig-prefix.txt
Creating parent directories for /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/include
Symlinking /include/python2.7 -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/include/python2.7
Symlinking /bin/..//opt/python/lib/python2.7/mailbox.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/mailbox.py
Symlinking /bin/..//opt/python/lib/python2.7/repr.pyo -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/repr.pyo
Symlinking /bin/..//opt/python/lib/python2.7/symbol.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/symbol.py
Symlinking /bin/..//opt/python/lib/python2.7/mimify.pyo -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/mimify.pyo
Symlinking /bin/..//opt/python/lib/python2.7/ihooks.pyo -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/ihooks.pyo
Symlinking /bin/..//opt/python/lib/python2.7/macpath.pyo -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/macpath.pyo
Symlinking /bin/..//opt/python/lib/python2.7/mimetools.pyo -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/mimetools.pyo
Symlinking /bin/..//opt/python/lib/python2.7/smtplib.pyo -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/smtplib.pyo
Symlinking /bin/..//opt/python/lib/python2.7/sched.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/sched.py
Symlinking /bin/..//opt/python/lib/python2.7/zipfile.pyo -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/zipfile.pyo
Symlinking /bin/..//opt/python/lib/python2.7/user.pyo -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/user.pyo
Symlinking /bin/..//opt/python/lib/python2.7/_threading_local.pyo -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/_threading_local.pyo
Symlinking /bin/..//opt/python/lib/python2.7/xml -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/xml
Symlinking /bin/..//opt/python/lib/python2.7/SocketServer.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/SocketServer.py
Symlinking /bin/..//opt/python/lib/python2.7/robotparser.pyo -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/robotparser.pyo
Symlinking /bin/..//opt/python/lib/python2.7/re.py -> /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/re.py
Symlinking failed, copying to /src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/re.py
copy: src:/bin/..//opt/python/lib/python2.7/re.py dest:/src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/re.py

Traceback (most recent call last):
  File "/src/build/firefox/mozilla-release/python/virtualenv/virtualenv.py", line 2573, in <module>
    main()
  File "/src/build/firefox/mozilla-release/python/virtualenv/virtualenv.py", line 974, in main
    never_download=options.never_download)
  File "/src/build/firefox/mozilla-release/python/virtualenv/virtualenv.py", line 1075, in create_environment
    site_packages=site_packages, clear=clear))
  File "/src/build/firefox/mozilla-release/python/virtualenv/virtualenv.py", line 1319, in install_python
    copyfile(join(exec_dir, fn), join(lib_dir, fn))
  File "/src/build/firefox/mozilla-release/python/virtualenv/virtualenv.py", line 447, in copyfile
    copyfileordir(src, dest)
  File "/src/build/firefox/mozilla-release/python/virtualenv/virtualenv.py", line 424, in copyfileordir
    shutil.copy2(src, dest)
  File "/lib/python2.7/shutil.py", line 128, in copy2
    copyfile(src, dst)
  File "/lib/python2.7/shutil.py", line 83, in copyfile
    with open(dst, 'wb') as fdst:
IOError: [Errno 2] No such file or directory: '/src/build/firefox/mozilla-release/firefox-build-dir/_virtualenv/lib/python2.7/re.py'
Traceback (most recent call last):
  File "/src/build/firefox/mozilla-release/build/virtualenv/populate_virtualenv.py", line 384, in <module>
    manager.ensure()
  File "/src/build/firefox/mozilla-release/build/virtualenv/populate_virtualenv.py", line 103, in ensure
    return self.build()
  File "/src/build/firefox/mozilla-release/build/virtualenv/populate_virtualenv.py", line 315, in build
    self.create()
  File "/src/build/firefox/mozilla-release/build/virtualenv/populate_virtualenv.py", line 122, in create
    raise Exception('Error creating virtualenv.')
Exception: Error creating virtualenv.


```



Discussion:
```
/src/build/firefox/mozilla-release # la firefox-build-dir/_virtualenv/lib/
total 12
drwxr-xr-x    3 root     root          4096 Oct 30 04:01 .
drwxr-xr-x    4 root     root          4096 Oct 30 04:01 ..
drwxr-xr-x    3 root     root          4096 Oct 30 04:01 python2.7
/src/build/firefox/mozilla-release # la firefox-build-dir/_virtualenv/lib/python2.7/
total 44
drwxr-xr-x    3 root     root          4096 Oct 30 04:01 .
drwxr-xr-x    3 root     root          4096 Oct 30 04:01 ..
lrwxrwxrwx    1 root     root            41 Oct 30 04:01 SocketServer.py -> /opt/python/lib/python2.7/SocketServer.py
lrwxrwxrwx    1 root     root            43 Oct 30 04:01 UserDict.py -> ../..//opt/python/lib/python2.7/UserDict.py
lrwxrwxrwx    1 root     root            42 Oct 30 04:01 _abcoll.py -> ../..//opt/python/lib/python2.7/_abcoll.py
lrwxrwxrwx    1 root     root            46 Oct 30 04:01 _threading_local.pyo -> /opt/python/lib/python2.7/_threading_local.pyo
lrwxrwxrwx    1 root     root            46 Oct 30 04:01 _weakrefset.py -> ../..//opt/python/lib/python2.7/_weakrefset.py
lrwxrwxrwx    1 root     root            38 Oct 30 04:01 abc.py -> ../..//opt/python/lib/python2.7/abc.py
lrwxrwxrwx    1 root     root            41 Oct 30 04:01 codecs.py -> ../..//opt/python/lib/python2.7/codecs.py
lrwxrwxrwx    1 root     root            21 Oct 30 04:01 config -> /lib/python2.7/config
lrwxrwxrwx    1 root     root            43 Oct 30 04:01 copy_reg.py -> ../..//opt/python/lib/python2.7/copy_reg.py
lrwxrwxrwx    1 root     root            24 Oct 30 04:01 encodings -> /lib/python2.7/encodings
lrwxrwxrwx    1 root     root            42 Oct 30 04:01 fnmatch.py -> ../..//opt/python/lib/python2.7/fnmatch.py
lrwxrwxrwx    1 root     root            46 Oct 30 04:01 genericpath.py -> ../..//opt/python/lib/python2.7/genericpath.py
lrwxrwxrwx    1 root     root            36 Oct 30 04:01 ihooks.pyo -> /opt/python/lib/python2.7/ihooks.pyo
lrwxrwxrwx    1 root     root            26 Oct 30 04:01 lib-dynload -> /lib/python2.7/lib-dynload
lrwxrwxrwx    1 root     root            44 Oct 30 04:01 linecache.py -> ../..//opt/python/lib/python2.7/linecache.py
lrwxrwxrwx    1 root     root            41 Oct 30 04:01 locale.py -> ../..//opt/python/lib/python2.7/locale.py
lrwxrwxrwx    1 root     root            37 Oct 30 04:01 macpath.pyo -> /opt/python/lib/python2.7/macpath.pyo
lrwxrwxrwx    1 root     root            36 Oct 30 04:01 mailbox.py -> /opt/python/lib/python2.7/mailbox.py
lrwxrwxrwx    1 root     root            39 Oct 30 04:01 mimetools.pyo -> /opt/python/lib/python2.7/mimetools.pyo
lrwxrwxrwx    1 root     root            36 Oct 30 04:01 mimify.pyo -> /opt/python/lib/python2.7/mimify.pyo
lrwxrwxrwx    1 root     root            41 Oct 30 04:01 ntpath.py -> ../..//opt/python/lib/python2.7/ntpath.py
-rw-r--r--    1 root     root             1 Oct 30 04:01 orig-prefix.txt
lrwxrwxrwx    1 root     root            37 Oct 30 04:01 os.py -> ../..//opt/python/lib/python2.7/os.py
lrwxrwxrwx    1 root     root            44 Oct 30 04:01 posixpath.py -> ../..//opt/python/lib/python2.7/posixpath.py
lrwxrwxrwx    1 root     root            37 Oct 30 04:01 re.py -> ../..//opt/python/lib/python2.7/re.py
lrwxrwxrwx    1 root     root            34 Oct 30 04:01 repr.pyo -> /opt/python/lib/python2.7/repr.pyo
lrwxrwxrwx    1 root     root            41 Oct 30 04:01 robotparser.pyo -> /opt/python/lib/python2.7/robotparser.pyo
lrwxrwxrwx    1 root     root            34 Oct 30 04:01 sched.py -> /opt/python/lib/python2.7/sched.py
drwxr-xr-x    2 root     root          4096 Oct 30 04:01 site-packages
-rw-r--r--    1 root     root         27321 Oct 30 04:01 site.py
lrwxrwxrwx    1 root     root            37 Oct 30 04:01 smtplib.pyo -> /opt/python/lib/python2.7/smtplib.pyo
lrwxrwxrwx    1 root     root            38 Oct 30 04:01 sre.py -> ../..//opt/python/lib/python2.7/sre.py
lrwxrwxrwx    1 root     root            46 Oct 30 04:01 sre_compile.py -> ../..//opt/python/lib/python2.7/sre_compile
```

etc.
all files which are a symlink to ../../opt.... are messed up. the symlink target does not exist.

this fixes it:

``` diff
--- org.py      2013-10-30 04:29:36.049913046 +0000
+++ python/virtualenv/virtualenv.py     2013-10-30 04:33:10.797897289 +0000
@@ -438,6 +438,9 @@
         srcpath = os.path.abspath(src)
     else:
         srcpath = os.readlink(src)
+        if not srcpath[0] == '/':
+               # relative symlink, needs to be prefixed with absolute source path
+               srcpath = os.path.join(os.path.dirname(src), srcpath)
     if symlink and hasattr(os, 'symlink') and not is_win:
         logger.info('Symlinking %s :: %s -> %s', src, srcpath,  dest)
         try:
```

full patch is available here https://github.com/sabotage-linux/sabotage/blob/master/KEEP/firefox-virtualenv.patch , it fixes several other issues with virtual-env symlink handling

for starters it's a bug to symlink subdirs. this caused the distutils dir to be symlinked, pointing to the installation directory, so overwriting `__init__.py` with your own version caused the real one to be overwritten.

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Just add a comment if you want to keep it open. Thank you for your contributions.

it's only stale because nobody here even bothered to look at it
Care to create a PR to fix it?

Resolving patch:
diff --git a/virtualenv.py b/virtualenv.py
--- a/virtualenv.py
+++ b/virtualenv.py
@@ -398,7 +398,32 @@ def copy_file_or_folder(src, dest, symlink=True):
         shutil.copy2(src, dest)
 
 
+def symlink_file_or_folder(src, dest):
+    if os.path.exists(dest):
+        return
+    if os.path.isdir(src):
+        if not os.path.exists(dest):
+            os.makedirs(dest)
+        for fn in os.listdir(src):
+            symlink_file_or_folder(os.path.join(src, fn), os.path.join(dest, fn))
+    else:
+        if not os.path.exists(os.path.dirname(dest)):
+            os.makedirs(os.path.dirname(dest))
+        if not os.path.islink(src):
+            srcpath = os.path.abspath(src)
+        else:
+            srcpath = os.readlink(src)
+            if not srcpath[0] == "/":
+                # relative symlink, needs to be prefixed with absolute source path
+                srcpath = os.path.join(os.path.dirname(src), srcpath)
+                srcpath = os.path.abspath(srcpath)
+        logger.info("Symlinking %s :: %s -> %s", src, srcpath, dest)
+        os.symlink(srcpath, dest)
+
+
 def copyfile(src, dest, symlink=True):
+    if symlink and hasattr(os, "symlink") and not IS_WIN:
+        return symlink_file_or_folder(src, dest)
     if not os.path.exists(src):
         # Some bad symlink in the src
         logger.warn("Cannot find file %s (bad symlink)", src)
@@ -409,16 +434,8 @@ def copyfile(src, dest, symlink=True):
     if not os.path.exists(os.path.dirname(dest)):
         logger.info("Creating parent directories for %s", os.path.dirname(dest))
         os.makedirs(os.path.dirname(dest))
-    if symlink and hasattr(os, "symlink") and not IS_WIN:
-        logger.info("Symlinking %s", dest)
-        try:
-            os.symlink(os.path.realpath(src), dest)
-        except (OSError, NotImplementedError):
-            logger.info("Symlinking failed, copying to %s", dest)
-            copy_file_or_folder(src, dest, symlink)
-    else:
-        logger.info("Copying to %s", dest)
-        copy_file_or_folder(src, dest, symlink)
+    logger.info("Copying to %s", dest)
+    copy_file_or_folder(src, dest, symlink)
 
 
 def writefile(dest, content, overwrite=True):


Test patch:
diff --git a/tests/test_virtualenv.py b/tests/test_virtualenv.py
--- a/tests/test_virtualenv.py
+++ b/tests/test_virtualenv.py
@@ -548,6 +548,33 @@ def test_create_environment_with_exec_prefix_pointing_to_prefix(tmpdir):
         os.environ[path_key] = old_path
 
 
+@pytest.mark.skipif(not hasattr(os, "symlink"), reason="requires working symlink implementation")
+def test_relative_symlink_relative_target(tmp_path):
+    """Test that copyfile works correctly when the source is a symlink with a
+    relative target, and a symlink to a symlink."""
+
+    # Set up src/link2 -> ../src/link1 -> file.
+    # We will copy to a different directory, so misinterpreting either symlink
+    # will be detected.
+    src_dir = tmp_path / "src"
+    src_dir.mkdir()
+    with open(str(src_dir / "file"), "w") as f:
+        f.write("contents")
+    os.symlink("file", str(src_dir / "link1"))
+    os.symlink(str(Path("..") / "src" / "link1"), str(src_dir / "link2"))
+
+    # Check that copyfile works on link2.
+    # This may produce a symlink or a regular file depending on the platform --
+    # which doesn't matter as long as it has the right contents.
+    copy_path = tmp_path / "copy"
+    virtualenv.copyfile(str(src_dir / "link2"), str(copy_path))
+    with open(str(copy_path), "r") as f:
+        assert f.read() == "contents"
+
+    shutil.rmtree(str(src_dir))
+    os.remove(str(copy_path))
+
+
 @pytest.mark.skipif(not hasattr(sys, "real_prefix"), reason="requires running from inside virtualenv")
 def test_create_environment_from_virtual_environment(tmpdir):
     """Create virtual environment using Python from another virtual environment