Repository: pypa/pip

Issue:
Regression: pip 8.1.2 does not handle "-e .[EXTRA]" correctly.
- Pip version: 8.1.2
- Python version: 3.4.1
- Operating System: linux
### Description:

I have a requirements_devel.txt with one line in my project

-e .[DOC,TST]

The idea is to install the doc and tst dependencies only when needed. But it does not work with pip 8.1.2. It works with pip 7.1.2.
### What I've run:

```
venv/bin/pip install -e '.[DOC,TST]'
Obtaining file:///home/mjansen/projects/python3_setup
Requirement already satisfied (use --upgrade to upgrade): sphinx in ./venv/lib/python3.4/site-packages (from mjlib==0.0.1)
Requirement already satisfied (use --upgrade to upgrade): pytest in ./venv/lib/python3.4/site-packages (from mjlib==0.0.1)
Requirement already satisfied (use --upgrade to upgrade): pytest-cov in ./venv/lib/python3.4/site-packages (from mjlib==0.0.1)
Requirement already satisfied (use --upgrade to upgrade): Pygments>=2.0 in ./venv/lib/python3.4/site-packages (from sphinx->mjlib==0.0.1)
Requirement already satisfied (use --upgrade to upgrade): Jinja2>=2.3 in ./venv/lib/python3.4/site-packages (from sphinx->mjlib==0.0.1)
Requirement already satisfied (use --upgrade to upgrade): alabaster<0.8,>=0.7 in ./venv/lib/python3.4/site-packages (from sphinx->mjlib==0.0.1)
Requirement already satisfied (use --upgrade to upgrade): babel!=2.0,>=1.3 in ./venv/lib/python3.4/site-packages (from sphinx->mjlib==0.0.1)
Requirement already satisfied (use --upgrade to upgrade): six>=1.4 in ./venv/lib/python3.4/site-packages (from sphinx->mjlib==0.0.1)
Requirement already satisfied (use --upgrade to upgrade): docutils>=0.11 in ./venv/lib/python3.4/site-packages (from sphinx->mjlib==0.0.1)
Requirement already satisfied (use --upgrade to upgrade): snowballstemmer>=1.1 in ./venv/lib/python3.4/site-packages (from sphinx->mjlib==0.0.1)
Requirement already satisfied (use --upgrade to upgrade): imagesize in ./venv/lib/python3.4/site-packages (from sphinx->mjlib==0.0.1)
Requirement already satisfied (use --upgrade to upgrade): py>=1.4.29 in ./venv/lib/python3.4/site-packages (from pytest->mjlib==0.0.1)
Requirement already satisfied (use --upgrade to upgrade): coverage>=3.7.1 in ./venv/lib/python3.4/site-packages (from pytest-cov->mjlib==0.0.1)
Requirement already satisfied (use --upgrade to upgrade): MarkupSafe in ./venv/lib/python3.4/site-packages (from Jinja2>=2.3->sphinx->mjlib==0.0.1)
Requirement already satisfied (use --upgrade to upgrade): pytz>=0a in ./venv/lib/python3.4/site-packages (from babel!=2.0,>=1.3->sphinx->mjlib==0.0.1)
Installing collected packages: mjlib
  Running setup.py develop for mjlib
Successfully installed mjlib-0.0.1
You are using pip version 7.1.2, however version 8.1.2 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
```

A new version. thats nice. lets update and try again

```
venv/bin/pip install pip --upgrad
Collecting pip
  Using cached pip-8.1.2-py2.py3-none-any.whl
Installing collected packages: pip
  Found existing installation: pip 7.1.2
    Uninstalling pip-7.1.2:
      Successfully uninstalled pip-7.1.2
Successfully installed pip-8.1.2
mjansen@linux-8523:~/projects/python3_setup> venv/bin/pip install -e '.[DOC,TST]'
Obtaining file:///home/mjansen/projects/python3_setup
  mjlib 0.0.1 does not provide the extra 'DOC'
  mjlib 0.0.1 does not provide the extra 'TST'
Installing collected packages: mjlib
  Running setup.py develop for mjlib
Successfully installed mjlib-0.0.1
```

As you can see 8.1.2 can't handle extras_require.



Discussion:
It seems to be a case-sensitive issue.
A simple `setup.py` makes it easy to reproduce:

```
(tmp-1ef25c2a0596072) $ cat setup.py
from setuptools import setup

setup(name='toto', extras_require={'DOC': ['pep8']})
(tmp-1ef25c2a0596072) $ pip install -e .[DOC]
Obtaining file:///home/xfernandez/tmp/hop
  toto 0.0.0 does not provide the extra 'DOC'
Installing collected packages: toto
  Running setup.py develop for toto
Successfully installed toto-0.0.0
(tmp-1ef25c2a0596072) $ pip install -e .[doc]
Obtaining file:///home/xfernandez/tmp/hop
Collecting pep8 (from toto==0.0.0)
  Using cached https://private_pypi/root/pypi/+f/6cf/e097210e2efb1/pep8-1.7.0-py2.py3-none-any.whl
Installing collected packages: pep8, toto
  Running setup.py develop for toto
Successfully installed pep8-1.7.0 toto-0.0.0
```


Resolving patch:
diff --git a/pip/req/req_install.py b/pip/req/req_install.py
--- a/pip/req/req_install.py
+++ b/pip/req/req_install.py
@@ -66,6 +66,10 @@ def _strip_extras(path):
     return path_no_extras, extras
 
 
+def _safe_extras(extras):
+    return set(pkg_resources.safe_extra(extra) for extra in extras)
+
+
 class InstallRequirement(object):
 
     def __init__(self, req, comes_from, source_dir=None, editable=False,
@@ -85,7 +89,7 @@ def __init__(self, req, comes_from, source_dir=None, editable=False,
                     add_msg = traceback.format_exc()
                 raise InstallationError(
                     "Invalid requirement: '%s'\n%s" % (req, add_msg))
-            self.extras = req.extras
+            self.extras = _safe_extras(req.extras)
 
         self.req = req
         self.comes_from = comes_from
@@ -149,7 +153,7 @@ def from_editable(cls, editable_req, comes_from=None, default_vcs=None,
                   wheel_cache=wheel_cache)
 
         if extras_override is not None:
-            res.extras = extras_override
+            res.extras = _safe_extras(extras_override)
 
         return res
 
@@ -224,7 +228,8 @@ def from_line(
                   wheel_cache=wheel_cache, constraint=constraint)
 
         if extras:
-            res.extras = Requirement('placeholder' + extras).extras
+            res.extras = _safe_extras(
+                Requirement('placeholder' + extras).extras)
 
         return res
 
@@ -1158,7 +1163,7 @@ def parse_editable(editable_req, default_vcs=None):
             return (
                 package_name,
                 url_no_extras,
-                Requirement("placeholder" + extras).extras,
+                Requirement("placeholder" + extras.lower()).extras,
             )
         else:
             return package_name, url_no_extras, None


Test patch:
diff --git a/tests/functional/test_install_extras.py b/tests/functional/test_install_extras.py
--- a/tests/functional/test_install_extras.py
+++ b/tests/functional/test_install_extras.py
@@ -1,3 +1,4 @@
+import textwrap
 import pytest
 from os.path import join
 
@@ -103,3 +104,24 @@ def test_nonexistent_options_listed_in_order(script, data):
         "  simplewheel 2.0 does not provide the extra 'nope'"
     )
     assert msg in result.stderr
+
+
+def test_install_special_extra(script, data):
+    # Check that uppercase letters and '-' are dealt with
+    # make a dummy project
+    pkga_path = script.scratch_path / 'pkga'
+    pkga_path.mkdir()
+    pkga_path.join("setup.py").write(textwrap.dedent("""
+        from setuptools import setup
+        setup(name='pkga',
+              version='0.1',
+              extras_require={'Hop_hOp-hoP': ['missing_pkg']},
+        )
+    """))
+
+    result = script.pip(
+        'install', '--no-index', '%s[Hop_hOp-hoP]' % pkga_path,
+        expect_error=True)
+    assert (
+        "Could not find a version that satisfies the requirement missing_pkg"
+    ) in result.stderr, str(result)