Repository: pypa/setuptools (python)

Issue:
36.2.1 appears to ignore install_requires with extras
Travis, Appveyor, and local builds of magic-wormhole started failing about an hour ago, when 36.2.1 was released. Our setup.py has:

```python
      install_requires=[
          "spake2==0.7", "pynacl",
          "six",
          "twisted[tls] >= 17.5.0", # 17.5.0 adds failAfterFailures=
          "autobahn[twisted] >= 0.14.1",
          "automat",
          "hkdf",
          "tqdm >= 4.13.0", # 4.13.0 fixes crash on NetBSD
          "click",
          "humanize",
          "ipaddress",
          "txtorcon >= 0.19.3",
      ],
```

When I run `pip install -e .` in a virtualenv with setuptools-36.2.0, I see Autobahn-17.7.1 installed as expected. If I have setuptools-36.2.1, then Autobahn does *not* get installed. (Twisted *does* get installed, but it's a dependency of txtorcon).


Pull request:
Revert "fix `install_requires` handling of extras"
This reverts commit a3ec721ec1e70f1f7aec6c3349ad85b446410809.

My interpretation was wrong, fix #1099.

Patch:
diff --git a/setuptools/dist.py b/setuptools/dist.py
index 50f5c18f1..f6bf2601d 100644
--- a/setuptools/dist.py
+++ b/setuptools/dist.py
@@ -376,7 +376,7 @@ class Distribution(Distribution_parse_config_files, _Distribution):
     def _move_install_requirements_markers(self):
         """
         Move requirements in `install_requires` that are using environment
-        markers or extras to `extras_require`.
+        markers `extras_require`.
         """
 
         # divide the install_requires into two sets, simple ones still
@@ -384,7 +384,7 @@ class Distribution(Distribution_parse_config_files, _Distribution):
         # by extras_require.
 
         def is_simple_req(req):
-            return not req.marker and not req.extras
+            return not req.marker
 
         spec_inst_reqs = getattr(self, 'install_requires', None) or ()
         simple_reqs = filter(
@@ -398,9 +398,7 @@ class Distribution(Distribution_parse_config_files, _Distribution):
         self.install_requires = list(map(str, simple_reqs))
 
         for r in complex_reqs:
-            suffix = ':' + str(r.marker) if r.marker else ''
-            for section in r.extras or ('',):
-                self._tmp_extras_require[section + suffix].append(r)
+            self._tmp_extras_require[':' + str(r.marker)].append(r)
         self.extras_require = dict(
             (k, [str(r) for r in map(self._clean_req, v)])
             for k, v in self._tmp_extras_require.items()
@@ -408,9 +406,8 @@ class Distribution(Distribution_parse_config_files, _Distribution):
 
     def _clean_req(self, req):
         """
-        Given a Requirement, remove extras and markers and return it.
+        Given a Requirement, remove environment markers and return it.
         """
-        req.extras = ()
         req.marker = None
         return req
 


Test patch:
diff --git a/setuptools/tests/test_egg_info.py b/setuptools/tests/test_egg_info.py
index 5ea55d618..d9d4ec3b3 100644
--- a/setuptools/tests/test_egg_info.py
+++ b/setuptools/tests/test_egg_info.py
@@ -207,14 +207,13 @@ class TestEggInfo(object):
     def test_install_requires_with_extra(self, tmpdir_cwd, env):
         req = 'install_requires=["barbazquux [test]"],'
         self._setup_script_with_requires(req)
-        self._run_install_command(tmpdir_cwd, env)
-        egg_info_dir = self._find_egg_info_files(env.paths['lib']).base
+        self._run_install_command(tmpdir_cwd, env, cmd=['egg_info'])
+        egg_info_dir = os.path.join('.', 'foo.egg-info')
         requires_txt = os.path.join(egg_info_dir, 'requires.txt')
         with open(requires_txt) as fp:
             install_requires = fp.read()
         expected_requires = DALS('''
-             [test]
-             barbazquux
+             barbazquux[test]
              ''')
         assert install_requires.lstrip() == expected_requires
         assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
@@ -229,8 +228,8 @@ class TestEggInfo(object):
         with open(requires_txt) as fp:
             install_requires = fp.read()
         expected_requires = DALS('''
-             [test:{marker}]
-             barbazquux
+             [:{marker}]
+             barbazquux[test]
              ''').format(marker=self.mismatch_marker_alternate)
         assert install_requires.lstrip() == expected_requires
         assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
@@ -250,6 +249,37 @@ class TestEggInfo(object):
             tmpdir_cwd, env, cmd=['test'], output="Ran 0 tests in")
         assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
 
+    def test_extras_require_with_extra(self, tmpdir_cwd, env):
+        req = 'extras_require={"extra": ["barbazquux [test]"]},'
+        self._setup_script_with_requires(req)
+        self._run_install_command(tmpdir_cwd, env, cmd=['egg_info'])
+        egg_info_dir = os.path.join('.', 'foo.egg-info')
+        requires_txt = os.path.join(egg_info_dir, 'requires.txt')
+        with open(requires_txt) as fp:
+            install_requires = fp.read()
+        expected_requires = DALS('''
+             [extra]
+             barbazquux[test]
+             ''')
+        assert install_requires.lstrip() == expected_requires
+        assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
+
+    def test_extras_require_with_extra_and_marker_in_req(self, tmpdir_cwd, env):
+        tmpl = 'extras_require={{"extra": ["barbazquux [test]; {marker}"]}},'
+        req = tmpl.format(marker=self.mismatch_marker)
+        self._setup_script_with_requires(req)
+        self._run_install_command(tmpdir_cwd, env)
+        egg_info_dir = self._find_egg_info_files(env.paths['lib']).base
+        requires_txt = os.path.join(egg_info_dir, 'requires.txt')
+        with open(requires_txt) as fp:
+            install_requires = fp.read()
+        expected_requires = DALS('''
+             [extra:{marker}]
+             barbazquux[test]
+             ''').format(marker=self.mismatch_marker_alternate)
+        assert install_requires.lstrip() == expected_requires
+        assert glob.glob(os.path.join(env.paths['lib'], 'barbazquux*')) == []
+
     def test_extras_require_with_marker(self, tmpdir_cwd, env):
         tmpl = 'extras_require={{":{marker}": ["barbazquux"]}},'
         req = tmpl.format(marker=self.mismatch_marker)