Repository: pantsbuild/pants

Issue:
How to create a wheel file?
Hi all,

I'm trying to figure out how to create a wheel file based on the code. I already have a target that creates a `tar.gz` file:

```python
# Generate an sdist
# has to be separate from the actual target that collects source files (in this case :src)
# because otherwise it'll error out with a cryptic exception
python_library(
  name="sdist",
  dependencies=[
    ":src",
  ],
  provides=setup_py(
    name="barbequeue",
  ...
  )
)
```

and then running `/pants setup-py :sdist` creates a source dist inside the `dist` folder.

Now I'm trying to create a `whl` file, and looks like the option `--setup-py-run` is the ticket. Unfortunately, running:
```bash
./pants setup-py --setup-py-run="bdist_wheel" :sdist
```

returns an error:
```
arbequeue/dist/barbequeue-0.0.1**** Failed to install barbequeue-0.0.1 (caused by: NonZeroExit("received exit code 1 during execution of `[u'/usr/bin/python', '-', 'bdist_wheel']` while trying to execute `[u'/usr/bin/python', '-', 'bdist_wheel']`",)
):
stdout:

stderr:
usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
   or: setup.py --help [cmd1 cmd2 ...]
   or: setup.py --help-commands
   or: setup.py cmd --help

error: invalid command 'bdist_wheel'
```

I've tried to make the `wheel` package a dependency of the `:sdist` target, to no avail. Anything I can do here? Do I need to add another dependency, or mess with the current `setuptools`?


Discussion:
For completeness, what `./pants --version` are you running @aronasorman?

...also, can you elaborate on the BUILD file comment above in another issue? A `python_library` with both `sources` and `provides` should work.
I'm pretty sure this will be related to the fix in #4753 for #4752 which did not go far enough and add `wheel` to the `mixins`.
Yup! I'm using `1.3.0`. I tried to upgrade to `1.4.0.dev8` on PyPI and still no dice (although I might stick with it thanks to the test caching!)

>...also, can you elaborate on the BUILD file comment above in another issue? A python_library with both sources and provides should work.

Yup, will open another issue for it.

Resolving patch:
diff --git a/src/python/pants/backend/python/tasks2/setup_py.py b/src/python/pants/backend/python/tasks2/setup_py.py
--- a/src/python/pants/backend/python/tasks2/setup_py.py
+++ b/src/python/pants/backend/python/tasks2/setup_py.py
@@ -47,21 +47,26 @@
 
 
 class SetupPyRunner(InstallerBase):
+  _EXTRAS = ('setuptools', 'wheel')
+
   def __init__(self, source_dir, setup_command, **kw):
     self.__setup_command = setup_command.split()
     super(SetupPyRunner, self).__init__(source_dir, **kw)
 
   def mixins(self):
     mixins = super(SetupPyRunner, self).mixins().copy()
+    extras = set(self._EXTRAS)
     for (key, version) in self._interpreter.extras:
-      if key == 'setuptools':
-        mixins['setuptools'] = 'setuptools=={}'.format(version)
-        break
+      if key in extras:
+        mixins[key] = '{}=={}'.format(key, version)
+        extras.remove(key)
+        if not extras:
+          break
     else:
-      # We know Pants sets up python interpreters with wheel and setuptools via the `PythonSetup`
+      # We know Pants sets up python interpreters with setuptools and wheel via the `PythonSetup`
       # subsystem; so this should never happen
-      raise AssertionError("Expected interpreter {} to have the extra 'setuptools'"
-                           .format(self._interpreter))
+      raise AssertionError("Expected interpreter {} to have the extras {}"
+                           .format(self._interpreter, self._EXTRAS))
     return mixins
 
   def _setup_command(self):


Test patch:
diff --git a/tests/python/pants_test/backend/python/tasks2/test_setup_py.py b/tests/python/pants_test/backend/python/tasks2/test_setup_py.py
--- a/tests/python/pants_test/backend/python/tasks2/test_setup_py.py
+++ b/tests/python/pants_test/backend/python/tasks2/test_setup_py.py
@@ -117,13 +117,18 @@ def run(self):
     with environment_as(PYTHONPATH=sdist_srcdir):
       with self.run_execute(foo):
         with open(os.path.join(sdist_srcdir, 'foo', 'commands', 'sys_path.txt')) as fp:
-          # The 1st element of the sys.path should be our custom SetupPyRunner Installer's
-          # setuptools mixin, which should match the setuptools version specified by the
-          # PythonSetup subsystem.
-          package = Package.from_href(fp.readline().strip())
-          self.assertEqual('setuptools', package.name)
+          def assert_extra(name, expected_version):
+            package = Package.from_href(fp.readline().strip())
+            self.assertEqual(name, package.name)
+            self.assertEqual(expected_version, package.raw_version)
+
+          # The 1st two elements of the sys.path should be our custom SetupPyRunner Installer's
+          # setuptools and wheel mixins, which should match the setuptools and wheel versions
+          # specified by the PythonSetup subsystem.
           init_subsystem(PythonSetup)
-          self.assertEqual(PythonSetup.global_instance().setuptools_version, package.raw_version)
+          python_setup = PythonSetup.global_instance()
+          assert_extra('setuptools', python_setup.setuptools_version)
+          assert_extra('wheel', python_setup.wheel_version)
 
 
 class TestSetupPy(SetupPyTestBase):