Repository: pypa/pip

Issue:
DistutilsOptionError with --target and prefix or exec-prefix
* Pip version: 9.0.1
* Python version: 2.7.12
* Operating System: macOS 10.12.1

### Description:

Having installed Python 2.7 using Homebrew, I'm unable to install packages using `--target'

### What I've run:

```
$ python2.7 -m pip install -t foo requests
Collecting requests
  Using cached requests-2.11.1-py2.py3-none-any.whl
Installing collected packages: requests
Exception:
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/pip/basecommand.py", line 215, in main
    status = self.run(options, args)
  File "/usr/local/lib/python2.7/site-packages/pip/commands/install.py", line 342, in run
    prefix=options.prefix_path,
  File "/usr/local/lib/python2.7/site-packages/pip/req/req_set.py", line 784, in install
    **kwargs
  File "/usr/local/lib/python2.7/site-packages/pip/req/req_install.py", line 851, in install
    self.move_wheel_files(self.source_dir, root=root, prefix=prefix)
  File "/usr/local/lib/python2.7/site-packages/pip/req/req_install.py", line 1064, in move_wheel_files
    isolated=self.isolated,
  File "/usr/local/lib/python2.7/site-packages/pip/wheel.py", line 247, in move_wheel_files
    prefix=prefix,
  File "/usr/local/lib/python2.7/site-packages/pip/locations.py", line 153, in distutils_scheme
    i.finalize_options()
  File "/usr/local/Cellar/python/2.7.12_2/Frameworks/Python.framework/Versions/2.7/lib/python2.7/distutils/command/install.py", line 264, in finalize_options
    "must supply either home or prefix/exec-prefix -- not both"
DistutilsOptionError: must supply either home or prefix/exec-prefix -- not both
```

Obviously, I didn't supply home nor prefix, so the error message is confusing.


Discussion:
See pull #4103 

Closing to move a bunch of related issues to a single issue: #4390.
This issue still exists.

```
$ python2.7 -m pip -V
pip 18.1 from /usr/local/lib/python2.7/site-packages/pip (python 2.7)
$ python2.7 -m pip install --target out requests
Collecting requests
  Downloading https://files.pythonhosted.org/packages/ff/17/5cbb026005115301a8fb2f9b0e3e8d32313142fe8b617070e7baad20554f/requests-2.20.1-py2.py3-none-any.whl (57kB)
    100% |████████████████████████████████| 61kB 400kB/s
Collecting certifi>=2017.4.17 (from requests)
  Using cached https://files.pythonhosted.org/packages/56/9d/1d02dd80bc4cd955f98980f28c5ee2200e1209292d5f9e9cc8d030d18655/certifi-2018.10.15-py2.py3-none-any.whl
Collecting idna<2.8,>=2.5 (from requests)
  Using cached https://files.pythonhosted.org/packages/4b/2a/0276479a4b3caeb8a8c1af2f8e4355746a97fab05a372e4a2c6a6b876165/idna-2.7-py2.py3-none-any.whl
Collecting urllib3<1.25,>=1.21.1 (from requests)
  Using cached https://files.pythonhosted.org/packages/62/00/ee1d7de624db8ba7090d1226aebefab96a2c71cd5cfa7629d6ad3f61b79e/urllib3-1.24.1-py2.py3-none-any.whl
Collecting chardet<3.1.0,>=3.0.2 (from requests)
  Using cached https://files.pythonhosted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8/chardet-3.0.4-py2.py3-none-any.whl
Installing collected packages: certifi, idna, urllib3, chardet, requests
Exception:
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/pip/_internal/cli/base_command.py", line 143, in main
    status = self.run(options, args)
  File "/usr/local/lib/python2.7/site-packages/pip/_internal/commands/install.py", line 366, in run
    use_user_site=options.use_user_site,
  File "/usr/local/lib/python2.7/site-packages/pip/_internal/req/__init__.py", line 49, in install_given_reqs
    **kwargs
  File "/usr/local/lib/python2.7/site-packages/pip/_internal/req/req_install.py", line 760, in install
    use_user_site=use_user_site, pycompile=pycompile,
  File "/usr/local/lib/python2.7/site-packages/pip/_internal/req/req_install.py", line 382, in move_wheel_files
    warn_script_location=warn_script_location,
  File "/usr/local/lib/python2.7/site-packages/pip/_internal/wheel.py", line 215, in move_wheel_files
    prefix=prefix,
  File "/usr/local/lib/python2.7/site-packages/pip/_internal/locations.py", line 165, in distutils_scheme
    i.finalize_options()
  File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/distutils/command/install.py", line 264, in finalize_options
    "must supply either home or prefix/exec-prefix -- not both"
DistutilsOptionError: must supply either home or prefix/exec-prefix -- not both
```

Note, I have not specified `prefix` nor `home`. There's something about the Homebrew install of Python that causes it to set the prefix. I think it's this:

```
$ cat /usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/distutils/distutils.cfg
[install]
prefix=/usr/local

[build_ext]
include_dirs=/usr/local/include:/usr/local/opt/openssl/include:/usr/local/opt/sqlite/include
library_dirs=/usr/local/lib:/usr/local/opt/openssl/lib:/usr/local/opt/sqlite/lib
```

As a result, `pip install --target` is simply broken on vanilla Homebrew installs (or any install that sets a prefix).
Does it work when using `pip --isolated install --target`?
> Does it work with `--isolated`?

No, same error.
From the code, it looks like `--isolated` will ignore distutils user configuration, but the global one will still get picked up.
I think, similarly to when `user` is used, `distutils_scheme` should ignore the install prefix when `home` is used:
```diff
 src/pip/_internal/locations.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git i/src/pip/_internal/locations.py w/src/pip/_internal/locations.py
index 89a3656b..72fe7c6f 100644
--- i/src/pip/_internal/locations.py
+++ w/src/pip/_internal/locations.py
@@ -170,7 +170,7 @@ def distutils_scheme(dist_name, user=False, home=None, root=None,
     # ideally, we'd prefer a scheme class that has no side-effects.
     assert not (user and prefix), "user={} prefix={}".format(user, prefix)
     i.user = user or i.user
-    if user:
+    if user or home:
         i.prefix = ""
     i.prefix = prefix or i.prefix
     i.home = home or i.home
```
The `assert` line should probably be updated too.
Confirmed - that one patch does address the issue.

```
pip 18.1 $ python2.7 -m pip -V
pip 18.1 from /Users/jaraco/code/public/pypa/pip/src/pip (python 2.7)
pip 18.1 $ python2.7 -m pip install --target out requests
Collecting requests
  Using cached https://files.pythonhosted.org/packages/ff/17/5cbb026005115301a8fb2f9b0e3e8d32313142fe8b617070e7baad20554f/requests-2.20.1-py2.py3-none-any.whl
Collecting idna<2.8,>=2.5 (from requests)
  Using cached https://files.pythonhosted.org/packages/4b/2a/0276479a4b3caeb8a8c1af2f8e4355746a97fab05a372e4a2c6a6b876165/idna-2.7-py2.py3-none-any.whl
Collecting urllib3<1.25,>=1.21.1 (from requests)
  Using cached https://files.pythonhosted.org/packages/62/00/ee1d7de624db8ba7090d1226aebefab96a2c71cd5cfa7629d6ad3f61b79e/urllib3-1.24.1-py2.py3-none-any.whl
Collecting chardet<3.1.0,>=3.0.2 (from requests)
  Using cached https://files.pythonhosted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8/chardet-3.0.4-py2.py3-none-any.whl
Collecting certifi>=2017.4.17 (from requests)
  Using cached https://files.pythonhosted.org/packages/56/9d/1d02dd80bc4cd955f98980f28c5ee2200e1209292d5f9e9cc8d030d18655/certifi-2018.10.15-py2.py3-none-any.whl
Installing collected packages: idna, urllib3, chardet, certifi, requests
Exception:
Traceback (most recent call last):
  File "/Users/jaraco/code/public/pypa/pip/src/pip/_internal/cli/base_command.py", line 143, in main
    status = self.run(options, args)
  File "/Users/jaraco/code/public/pypa/pip/src/pip/_internal/commands/install.py", line 366, in run
    use_user_site=options.use_user_site,
  File "/Users/jaraco/code/public/pypa/pip/src/pip/_internal/req/__init__.py", line 49, in install_given_reqs
    **kwargs
  File "/Users/jaraco/code/public/pypa/pip/src/pip/_internal/req/req_install.py", line 760, in install
    use_user_site=use_user_site, pycompile=pycompile,
  File "/Users/jaraco/code/public/pypa/pip/src/pip/_internal/req/req_install.py", line 382, in move_wheel_files
    warn_script_location=warn_script_location,
  File "/Users/jaraco/code/public/pypa/pip/src/pip/_internal/wheel.py", line 215, in move_wheel_files
    prefix=prefix,
  File "/Users/jaraco/code/public/pypa/pip/src/pip/_internal/locations.py", line 165, in distutils_scheme
    i.finalize_options()
  File "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/lib/python2.7/distutils/command/install.py", line 264, in finalize_options
    "must supply either home or prefix/exec-prefix -- not both"
DistutilsOptionError: must supply either home or prefix/exec-prefix -- not both
pip 18.1 $ git stash pop
HEAD detached at 18.1
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   src/pip/_internal/locations.py

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (2c685ea2bf964f8475f09552556b800a693f1201)
pip 18.1 $ git diff
diff --git a/src/pip/_internal/locations.py b/src/pip/_internal/locations.py
index 183aaa39..a4c79135 100644
--- a/src/pip/_internal/locations.py
+++ b/src/pip/_internal/locations.py
@@ -157,7 +157,7 @@ def distutils_scheme(dist_name, user=False, home=None, root=None,
     # ideally, we'd prefer a scheme class that has no side-effects.
     assert not (user and prefix), "user={} prefix={}".format(user, prefix)
     i.user = user or i.user
-    if user:
+    if user or home:
         i.prefix = ""
     i.prefix = prefix or i.prefix
     i.home = home or i.home
pip 18.1 $ python2.7 -m pip install --target out requests
Collecting requests
  Using cached https://files.pythonhosted.org/packages/ff/17/5cbb026005115301a8fb2f9b0e3e8d32313142fe8b617070e7baad20554f/requests-2.20.1-py2.py3-none-any.whl
Collecting idna<2.8,>=2.5 (from requests)
  Using cached https://files.pythonhosted.org/packages/4b/2a/0276479a4b3caeb8a8c1af2f8e4355746a97fab05a372e4a2c6a6b876165/idna-2.7-py2.py3-none-any.whl
Collecting urllib3<1.25,>=1.21.1 (from requests)
  Using cached https://files.pythonhosted.org/packages/62/00/ee1d7de624db8ba7090d1226aebefab96a2c71cd5cfa7629d6ad3f61b79e/urllib3-1.24.1-py2.py3-none-any.whl
Collecting chardet<3.1.0,>=3.0.2 (from requests)
  Using cached https://files.pythonhosted.org/packages/bc/a9/01ffebfb562e4274b6487b4bb1ddec7ca55ec7510b22e4c51f14098443b8/chardet-3.0.4-py2.py3-none-any.whl
Collecting certifi>=2017.4.17 (from requests)
  Using cached https://files.pythonhosted.org/packages/56/9d/1d02dd80bc4cd955f98980f28c5ee2200e1209292d5f9e9cc8d030d18655/certifi-2018.10.15-py2.py3-none-any.whl
Installing collected packages: idna, urllib3, chardet, certifi, requests
Successfully installed certifi-2018.10.15 chardet-3.0.4 idna-2.7 requests-2.20.1 urllib3-1.24.1
```

Resolving patch:
diff --git a/src/pip/_internal/locations.py b/src/pip/_internal/locations.py
--- a/src/pip/_internal/locations.py
+++ b/src/pip/_internal/locations.py
@@ -156,8 +156,9 @@ def distutils_scheme(dist_name, user=False, home=None, root=None,
     # or user base for installations during finalize_options()
     # ideally, we'd prefer a scheme class that has no side-effects.
     assert not (user and prefix), "user={} prefix={}".format(user, prefix)
+    assert not (home and prefix), "home={} prefix={}".format(home, prefix)
     i.user = user or i.user
-    if user:
+    if user or home:
         i.prefix = ""
     i.prefix = prefix or i.prefix
     i.home = home or i.home


Test patch:
diff --git a/tests/functional/test_install.py b/tests/functional/test_install.py
--- a/tests/functional/test_install.py
+++ b/tests/functional/test_install.py
@@ -1473,3 +1473,22 @@ def test_install_conflict_warning_can_be_suppressed(script, data):
         'install', '--no-index', pkgB_path, '--no-warn-conflicts'
     )
     assert "Successfully installed pkgB-2.0" in result2.stdout, str(result2)
+
+
+def test_target_install_ignores_distutils_config_install_prefix(script):
+    prefix = script.scratch_path / 'prefix'
+    distutils_config = Path(os.path.expanduser('~'),
+                            'pydistutils.cfg' if sys.platform == 'win32'
+                            else '.pydistutils.cfg')
+    distutils_config.write(textwrap.dedent(
+        '''
+        [install]
+        prefix=%s
+        ''' % str(prefix)))
+    target = script.scratch_path / 'target'
+    result = script.pip_install_local('simplewheel', '-t', target)
+    assert (
+        "Successfully installed simplewheel" in result.stdout and
+        (target - script.base_path) in result.files_created and
+        (prefix - script.base_path) not in result.files_created
+    ), str(result)