Repository: abantos/bolt

Issue:
Setup Task Swallows Errors Building Distribution
### Description
The current implementation of the Setup task does not evaluate the return object from `distutils.core.run_setup()` and it just assumes it works. Unfortunately, some environments will not have the right tools to build a distribution; therefore, the task will silently fail.

I found this problem in another project's pipeline where building a `wheel` inside the official Python 2.7 Docker container silently failed because the `wheel` package is not installed by default like when creating a virtual environment.

### Steps to Reproduce
- Create a setup task that builds a simple distribution wheel. 
- Create a virtual environment, activate it, and uninstall the `wheel` package.
- Run the setup task within the environment. 
- Check the dist folder, which will be empty.

The current behavior shows that the return code from `distutils.core.run_setup()` invoked by the task is not evaluated, and the task seems to succeed; however, the `.whl` file has not been created.

An exception is expected to be raised if the wheel doesn't succeed.

### Fix Analysis
Looking at the implementation of `distutils.core.run_setup()`, you can see the function returns a `Distribution` instance. The instance exposes a `dist_files` property, which is empty if the distribution failed to build. If it succeeds, the return value is a list of tuples containing information about each distribution. This allow us to fix the problem, or at list report an error if no distribution was built, by checking the contents of `dist_files`; something like the following code:

```python
# dcore is an alias to distutils.core.
dist_info = dcore.run_setup(self.setup_script, self.args)
if not dist_info.dist_files:
    raise DistributionBuildError()
```
> NOTE:I need to think on how to handle multiple distributions; although, I believe right now the task only supports one distribution per configuration.

The following is an example of the contents of the `dist_files` property:

```
[('bdist_wheel', '3.5', 'D:\\Projects\\Abantos\\bolt\\dist\\bolt_ta-0.2.1-py2.py3-none-any.whl')]
```

### Acceptance Criteria
Raises exception if building the distribution fails.

Discussion:

Patch:
diff --git a/bolt/about.py b/bolt/about.py
index 3c285ba..9b6ae95 100644
--- a/bolt/about.py
+++ b/bolt/about.py
@@ -8,4 +8,4 @@ A task runner written in Python
 copyright = u'2016 Abantos'
 author = u'Isaac Rodriguez'
 version = u'0.2'
-release = u'0.2.2'
+release = u'0.2.3'
diff --git a/bolt/tasks/bolt_setup.py b/bolt/tasks/bolt_setup.py
index 6190eb5..e10aa8b 100644
--- a/bolt/tasks/bolt_setup.py
+++ b/bolt/tasks/bolt_setup.py
@@ -26,8 +26,12 @@ configure the task. ::
 import distutils.core as dcore
 import logging
 
+import bolt.errors as errors
 import bolt.utils as utilities
 
+
+class BuildSetupError(errors.TaskError): pass
+
 DEFAULT_ARGUMENTS = ['build']
 DEFAULT_SETUP_SCRIPT = 'setup.py'
 
@@ -50,11 +54,13 @@ class ExecuteSetupTask(object):
             self.setup_script = DEFAULT_SETUP_SCRIPT
         generator = _SetupArgumentGenerator()
         self.args = generator.generate_from(config)
-        self._execute_setup()
+        result = self._execute_setup()
+        if not result.dist_files:
+            raise BuildSetupError()
 
 
     def _execute_setup(self):
-        dcore.run_setup(self.setup_script, self.args)
+        return dcore.run_setup(self.setup_script, self.args)
 
 
 


Test patch:
diff --git a/test/test_tasks/test_bolt_setup.py b/test/test_tasks/test_bolt_setup.py
index d9a03b9..9e874cc 100644
--- a/test/test_tasks/test_bolt_setup.py
+++ b/test/test_tasks/test_bolt_setup.py
@@ -7,7 +7,7 @@ import _mocks as mck
 class TestExecuteSetupTask(unittest.TestCase):
 
     def setUp(self):
-        self. subject = ExecuteSetupTaskSpy()
+        self.subject = ExecuteSetupTaskSpy()
         return super(TestExecuteSetupTask, self).setUp()
 
 
@@ -25,6 +25,12 @@ class TestExecuteSetupTask(unittest.TestCase):
         self.assertEqual(self.subject.setup_script, script)
 
 
+    def test_raises_exception_if_building_setup_fails(self):
+        self.subject.dist_files = []
+        with self.assertRaises(bsetup.BuildSetupError):
+            self.given({})
+
+
     def given(self, config):
         self.subject(config=config)
 
@@ -36,9 +42,13 @@ class TestExecuteSetupTask(unittest.TestCase):
 
 
 class ExecuteSetupTaskSpy(bsetup.ExecuteSetupTask):
+
+    def __init__(self):
+        super(ExecuteSetupTaskSpy, self).__init__()
+        self.dist_files = [('bdist_wheel', '3.5', '/some/colation/the.whl')]
     
     def _execute_setup(self):
-        pass
+        return self