Repository: pypa/pip

Issue:
pip install --use-wheel doesn't respect --user for installing scripts
For example:

```
habnabit@bach:~$ pip install --use-wheel -i [redacted] -r requirements.txt --user
Downloading/unpacking zope.interface==4.0.5 (from -r requirements.txt (line 2))
  Downloading zope.interface-4.0.5-cp27-none-linux_x86_64.whl (144kB): 144kB downloaded
Downloading/unpacking Twisted==12.3.0 (from -r requirements.txt (line 3))
  Downloading Twisted-12.3.0-cp27-none-linux_x86_64.whl (2.9MB): 2.9MB downloaded
Downloading/unpacking py-bcrypt==0.3 (from -r requirements.txt (line 4))
  Downloading py_bcrypt-0.3-cp27-none-linux_x86_64.whl
Downloading/unpacking web.py==0.37 (from -r requirements.txt (line 5))
  Downloading web.py-0.37-py27-none-any.whl (100kB): 100kB downloaded
Downloading/unpacking SQLAlchemy==0.7.10 (from -r requirements.txt (line 6))
  Downloading SQLAlchemy-0.7.10-cp27-none-linux_x86_64.whl (727kB): 727kB downloaded
Downloading/unpacking psycopg2==2.5 (from -r requirements.txt (line 7))
  Downloading psycopg2-2.5-cp27-none-linux_x86_64.whl (309kB): 309kB downloaded
Downloading/unpacking sanpera==0.1.1.dev1 (from -r requirements.txt (line 8))
  Downloading sanpera-0.1.1.dev1.tar.gz (239kB): 239kB downloaded
  Running setup.py egg_info for package sanpera
    Package ImageMagick was not found in the pkg-config search path.
    Perhaps you should add the directory containing `ImageMagick.pc'
    to the PKG_CONFIG_PATH environment variable
    No package 'ImageMagick' found
    Package ImageMagick was not found in the pkg-config search path.
    Perhaps you should add the directory containing `ImageMagick.pc'
    to the PKG_CONFIG_PATH environment variable
    No package 'ImageMagick' found

Downloading/unpacking anyjson==0.3.3 (from -r requirements.txt (line 9))
  Downloading anyjson-0.3.3-py27-none-any.whl
Downloading/unpacking yajl==0.3.5 (from -r requirements.txt (line 10))
  Downloading yajl-0.3.5-cp27-none-linux_x86_64.whl (56kB): 56kB downloaded
Requirement already satisfied (use --upgrade to upgrade): setuptools in ./.local/lib/python2.7/site-packages/setuptools-1.0-py2.7.egg (from zope.interface==4.0.5->-r requirements.txt (line 2))
Installing collected packages: zope.interface, Twisted, py-bcrypt, web.py, SQLAlchemy, psycopg2, sanpera, anyjson, yajl
Cleaning up...
Exception:
Traceback (most recent call last):
  File "/home/habnabit/.local/lib/python2.7/site-packages/pip-1.4.1-py2.7.egg/pip/basecommand.py", line 134, in main
    status = self.run(options, args)
  File "/home/habnabit/.local/lib/python2.7/site-packages/pip-1.4.1-py2.7.egg/pip/commands/install.py", line 241, in run
    requirement_set.install(install_options, global_options, root=options.root_path)
  File "/home/habnabit/.local/lib/python2.7/site-packages/pip-1.4.1-py2.7.egg/pip/req.py", line 1298, in install
    requirement.install(install_options, global_options, *args, **kwargs)
  File "/home/habnabit/.local/lib/python2.7/site-packages/pip-1.4.1-py2.7.egg/pip/req.py", line 595, in install
    self.move_wheel_files(self.source_dir)
  File "/home/habnabit/.local/lib/python2.7/site-packages/pip-1.4.1-py2.7.egg/pip/req.py", line 815, in move_wheel_files
    move_wheel_files(self.name, self.req, wheeldir, user=self.use_user_site, home=self.target_dir)
  File "/home/habnabit/.local/lib/python2.7/site-packages/pip-1.4.1-py2.7.egg/pip/wheel.py", line 184, in move_wheel_files
    clobber(source, dest, False, fixer=fixer)
  File "/home/habnabit/.local/lib/python2.7/site-packages/pip-1.4.1-py2.7.egg/pip/wheel.py", line 166, in clobber
    shutil.move(srcfile, destfile)
  File "/usr/lib/python2.7/shutil.py", line 301, in move
    copy2(src, real_dst)
  File "/usr/lib/python2.7/shutil.py", line 130, in copy2
    copyfile(src, dst)
  File "/usr/lib/python2.7/shutil.py", line 83, in copyfile
    with open(dst, 'wb') as fdst:
IOError: [Errno 13] Permission denied: '/usr/bin/trial'
```

Why is this trying to write to `/usr/bin` when `--user` is given?



Discussion:
It's probably either a bug in https://github.com/pypa/pip/blob/develop/pip/locations.py#L131 or the user flag isn't making it through to that point.

this line is throwing away the proper location for scripts.
https://github.com/pypa/pip/blob/develop/pip/locations.py#L147

the `bin_py` logic location up above can probably replaced with the newer `distutils_scheme` method.

btw, there is a `--user` test for wheels, just wasn't confirming scripts.

fyi, will get on this tomorrow.


Resolving patch:
diff --git a/pip/locations.py b/pip/locations.py
--- a/pip/locations.py
+++ b/pip/locations.py
@@ -137,15 +137,15 @@ def distutils_scheme(dist_name, user=False, home=None):
     scheme = {}
     d = Distribution({'name': dist_name})
     i = install(d)
+    # NOTE: setting user or home has the side-effect of creating the home dir or
+    # user base for installations during finalize_options()
+    # ideally, we'd prefer a scheme class that has no side-effects.
     i.user = user or i.user
     i.home = home or i.home
     i.finalize_options()
     for key in SCHEME_KEYS:
         scheme[key] = getattr(i, 'install_'+key)
 
-    #be backward-compatible with what pip has always done?
-    scheme['scripts'] = bin_py
-
     if running_under_virtualenv():
         scheme['headers'] = os.path.join(sys.prefix,
                                     'include',


Test patch:
diff --git a/tests/data/packages/has.script-1.0-py2.py3-none-any.whl b/tests/data/packages/has.script-1.0-py2.py3-none-any.whl
new file mode 100644
Binary files /dev/null and b/tests/data/packages/has.script-1.0-py2.py3-none-any.whl differ
diff --git a/tests/functional/test_install_user.py b/tests/functional/test_install_user.py
--- a/tests/functional/test_install_user.py
+++ b/tests/functional/test_install_user.py
@@ -228,13 +228,14 @@ def test_uninstall_editable_from_usersite(self):
 
     def test_install_user_wheel(self):
         """
-        Test user install from wheel
+        Test user install from wheel (that has a script)
         """
         env = reset_env(system_site_packages=True)
         pip_install_local('wheel')
-        result = run_pip('install', 'simple.dist==0.1', '--user', '--use-wheel',
+        result = run_pip('install', 'has.script==1.0', '--user', '--use-wheel',
                      '--no-index', '--find-links='+find_links)
-        egg_info_folder = env.user_site / 'simple.dist-0.1.dist-info'
-        assert egg_info_folder in result.files_created, str(result)
-
+        info_folder = env.user_site / 'has.script-1.0.dist-info'
+        assert info_folder in result.files_created, str(result)
+        script_file = env.user_bin / 'script.py'
+        assert script_file in result.files_created
 
diff --git a/tests/functional/test_install_wheel.py b/tests/functional/test_install_wheel.py
--- a/tests/functional/test_install_wheel.py
+++ b/tests/functional/test_install_wheel.py
@@ -7,16 +7,18 @@
 
 def test_install_from_wheel():
     """
-    Test installing from a wheel.
+    Test installing from a wheel (that has a script)
     """
     env = reset_env()
-    result = run_pip('install', 'simple.dist', '--use-wheel',
+    result = run_pip('install', 'has.script==1.0', '--use-wheel',
                      '--no-index', '--find-links='+find_links,
                      expect_error=False)
-    dist_info_folder = env.site_packages/'simple.dist-0.1.dist-info'
+    dist_info_folder = env.site_packages/'has.script-1.0.dist-info'
     assert dist_info_folder in result.files_created, (dist_info_folder,
                                                       result.files_created,
                                                       result.stdout)
+    script_file = env.bin / 'script.py'
+    assert script_file in result.files_created
 
 
 def test_install_from_wheel_with_extras():
@@ -94,5 +96,3 @@ def test_install_from_wheel_no_deps():
     pkg_folder = env.site_packages/'source'
     assert pkg_folder not in result.files_created
 
-
-
diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py
--- a/tests/lib/__init__.py
+++ b/tests/lib/__init__.py
@@ -320,8 +320,9 @@ def __init__(self, environ=None, sitecustomize=None, pypi_cache=True):
             self.site_packages = self.lib/'site-packages'
         self.user_base_path = self.venv_path/'user'
         self.user_site_path = self.venv_path/'user'/site_packages_suffix
-
         self.user_site = relpath(self.root_path, self.user_site_path)
+        self.user_bin_path = self.user_base_path/(self.bin_path - self.venv_path)
+        self.user_bin =  relpath(self.root_path, self.user_bin_path)
 
         self.environ["PYTHONUSERBASE"] = self.user_base_path