Repository: pypa/virtualenv

Issue:
Long env path names cause weird errors after activiting virtual environment
After creating a virtualenv in the path

`/home/harald/git-repos/sandbox/stackoverflow/41454203-how-do-you-import-the-library-being-tested-when-running-a-nosetest/env`

I could not run the env's pip,

```
nosetest/env$ ./bin/pip
bash: ./bin/pip: /home/harald/git-repos/sandbox/stackoverflow/41454203-how-do-you-import-the-l: bad interpreter: Permission denied
```

and running just pip gives me the system's pip, with all of its packages.


Discussion:
Not virtualenv's fault. Shebangs have length limits, see http://www.in-ulm.de/~mascheck/various/shebang/.

This is basically a duplicate of #994 - virtualenv are broken with fragile kernel shebang parsing.
Where is the shebang written to file?

And is there maybe some workaround we could employ to make virtual environments more sturdy?
> Where is the shebang written to file?

Do you mean the location of the shebang or the codes that generate a shebang? 1. In the first line of the file ```bin/pip``` 2. In [distlib](https://bitbucket.org/pypa/distlib/src/f702f6afc1d03717fa88a742b9fb99bedd599ff2/distlib/scripts.py?at=default&fileviewer=file-view-default#scripts.py-139)

> And is there maybe some workaround we could employ to make virtual environments more sturdy?

I don't think there are simple workarounds. A possibility is writing a user space shebang parser just like py.exe on Windows.
Using pip as an example, one thing we could do is move the current `pip` to `.pip`, remove the shebang line and create a new `pip` file with the following content:

```
#!/bin/bash
/path/to/env/bin/python /path/to/env/bin/.pip $@
```

Basically, wrap each program in a shell script calling the original file, passing on all of its arguments.

That will get around the problem of long shebangs.

And if we quote these paths in Bash we could probably solve the problem with space characters in paths as well.
OK maybe my definition for "workarounds" is too strict. I thought it shouldn't break existing things. In this case, using /bin/bash usually works but not always. Some systems do not install bash in /bin/ (FreeBSD install it at /usr/local/bin/bash), and some systems do not provide bash at all. (Most Android uses /system/bin/sh)

A similar idea was proposed 3 years ago: https://github.com/pypa/pip/issues/923#issuecomment-19655227. Apparently no PyPA core developers are interested in this approach :(
No, you're right. The workaround shouldn't break things. As long as `env` lives in the same place in every Unix system, this should work just as well:

    #!/usr/bin/env sh
    "/path/to/env/bin/python" "/path/to/env/bin/.pip" $@

It's nice to see that someone else brought up the same idea. However, @cellularmitosis phrased it in negative terms back then, so it only makes sense that no one took it seriously. It doesn't seem like any of the developers actually commented on it.

Let us give it a positive spin this time around and present it as waterproof solution to long paths and spaces. I'm not exactly sure how I would go about to putting this in the virtualenv source code though.
Looks better. If it's possible to use a single file, I believe it will be ready for a pull request!
@yan12125 I created a pull request.
I've updated `distlib` to handle long paths and paths with spaces. I didn't use Harald Nordgren's patch directly (it had some problems - e.g. no tests) but the approach is the same - using '/bin/sh' as the executable. `pip` / `virtualenv` maintainers might want to test after locally vendoring the current version of the `distlib` repository.
@HaraldNordgren ah, I should have been clearer that my dismay was directed at Apple, not at the virtualenv guys!  On Linux, this isn't an issue at all, and I had assumed this was an insurmountable shortcoming of OS X shebang parsing.  Glad to see there's a solution!

Is it really fixed? 
I just run into the same issue using latest pip 20.0.2 and virtualenv 20.0.1 and python 3.7.4 . And it still generates the long path.
It seems here we've regressed as we're not using ``distlib`` to generate the paths.

Resolving patch:
diff --git a/src/virtualenv/seed/via_app_data/pip_install/base.py b/src/virtualenv/seed/via_app_data/pip_install/base.py
--- a/src/virtualenv/seed/via_app_data/pip_install/base.py
+++ b/src/virtualenv/seed/via_app_data/pip_install/base.py
@@ -7,14 +7,12 @@
 import zipfile
 from abc import ABCMeta, abstractmethod
 from tempfile import mkdtemp
-from textwrap import dedent
 
 import six
 from six import PY3
 
-from virtualenv.info import IS_WIN
 from virtualenv.util import ConfigParser
-from virtualenv.util.path import Path, make_exe
+from virtualenv.util.path import Path
 
 
 @six.add_metaclass(ABCMeta)
@@ -131,49 +129,16 @@ def _console_scripts(self):
 
     def _create_console_entry_point(self, name, value, to_folder):
         result = []
-        if IS_WIN:
-            # windows doesn't support simple script files, so fallback to more complicated exe generator
-            from distlib.scripts import ScriptMaker
-
-            maker = ScriptMaker(None, str(to_folder))
-            maker.clobber = True  # overwrite
-            maker.variants = {"", "X", "X.Y"}  # create all variants
-            maker.set_mode = True  # ensure they are executable
-            maker.executable = str(self._creator.exe)
-            specification = "{} = {}".format(name, value)
-            new_files = maker.make(specification)
-            result.extend(Path(i) for i in new_files)
-        else:
-            module, func = value.split(":")
-            content = (
-                dedent(
-                    """
-            #!{0}
-            # -*- coding: utf-8 -*-
-            import re
-            import sys
-
-            from {1} import {2}
-
-            if __name__ == "__main__":
-                sys.argv[0] = re.sub(r"(-script.pyw?|.exe)?$", "", sys.argv[0])
-                sys.exit({2}())
-            """
-                )
-                .lstrip()
-                .format(self._creator.exe, module, func)
-            )
-
-            version = self._creator.interpreter.version_info
-            for new_name in (
-                name,
-                "{}{}".format(name, version.major),
-                "{}-{}.{}".format(name, version.major, version.minor),
-            ):
-                exe = to_folder / new_name
-                exe.write_text(content, encoding="utf-8")
-                make_exe(exe)
-                result.append(exe)
+        from distlib.scripts import ScriptMaker
+
+        maker = ScriptMaker(None, str(to_folder))
+        maker.clobber = True  # overwrite
+        maker.variants = {"", "X", "X.Y"}  # create all variants
+        maker.set_mode = True  # ensure they are executable
+        maker.executable = str(self._creator.exe)
+        specification = "{} = {}".format(name, value)
+        new_files = maker.make(specification)
+        result.extend(Path(i) for i in new_files)
         return result
 
     def clear(self):


Test patch:
diff --git a/tests/unit/create/test_creator.py b/tests/unit/create/test_creator.py
--- a/tests/unit/create/test_creator.py
+++ b/tests/unit/create/test_creator.py
@@ -297,3 +297,18 @@ def test_creator_input_passed_is_abs(tmp_path, monkeypatch):
     monkeypatch.chdir(tmp_path)
     result = Creator.validate_dest("venv")
     assert str(result) == str(tmp_path / "venv")
+
+
+def test_create_long_path(current_fastest, tmp_path):
+    if sys.platform == "darwin":
+        max_shebang_length = 512
+    else:
+        max_shebang_length = 127
+    # filenames can be at most 255 long on macOS, so split to to levels
+    count = max_shebang_length - len(str(tmp_path))
+    folder = tmp_path / ("a" * (count // 2)) / ("b" * (count // 2)) / "c"
+    folder.mkdir(parents=True)
+
+    cmd = [str(folder)]
+    result = run_via_cli(cmd)
+    subprocess.check_call([str(result.creator.exe.parent / "pip"), "--version"])