Repository: pypa/build
Pull request #296 (2021-05-19 02:55:48)

Better error output


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 2f9858e..debdc03 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -11,12 +11,14 @@ Unreleased
 - Use symlinks on creating venv's when available (`PR #274`_, Fixes `#271`_)
 - Error sooner if pip upgrade is required and fails (`PR #288`_, Fixes `#256`_)
 - Add a ``runner`` argument to ``ProjectBuilder`` (`PR #290`_, Fixes `#289`_)
+- Hide irrelevant ``pep517`` error traceback and improve error messages (`PR #296`_)
 
 .. _PR #260: https://github.com/pypa/build/pull/260
 .. _PR #267: https://github.com/pypa/build/pull/267
 .. _PR #274: https://github.com/pypa/build/pull/274
 .. _PR #288: https://github.com/pypa/build/pull/288
 .. _PR #290: https://github.com/pypa/build/pull/290
+.. _PR #296: https://github.com/pypa/build/pull/296
 .. _#256: https://github.com/pypa/build/issues/256
 .. _#259: https://github.com/pypa/build/issues/259
 .. _#263: https://github.com/pypa/build/issues/263
diff --git a/setup.cfg b/setup.cfg
index f3831d6..7848ffb 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -31,7 +31,7 @@ install_requires =
     pep517>=0.9.1
     toml>=0.10.0
     importlib-metadata>=0.22;python_version < "3.8"
-    typing>=3.5.2;python_version < "3"
+    typing>=3.5.3.0;python_version < "3"
     virtualenv>=20.0.35;python_version < "3"
 python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
 package_dir =
diff --git a/src/build/__init__.py b/src/build/__init__.py
index 4345623..228b479 100644
--- a/src/build/__init__.py
+++ b/src/build/__init__.py
@@ -9,11 +9,13 @@ import contextlib
 import difflib
 import io
 import os
+import subprocess
 import sys
+import types
 import warnings
 
 from collections import OrderedDict
-from typing import AbstractSet, Any, Callable, Dict, Iterator, Mapping, Optional, Sequence, Set, Text, Tuple, Union
+from typing import AbstractSet, Any, Callable, Dict, Iterator, Mapping, Optional, Sequence, Set, Text, Tuple, Type, Union
 
 import pep517.wrappers
 import toml
@@ -28,6 +30,10 @@ if sys.version_info < (3,):
 
 
 ConfigSettings = Mapping[str, Union[str, Sequence[str]]]
+_ExcInfo = Union[
+    Tuple[Type[BaseException], BaseException, types.TracebackType],
+    Tuple[None, None, None],
+]
 
 
 _DEFAULT_BACKEND = {
@@ -47,11 +53,16 @@ class BuildBackendException(Exception):
     Exception raised when the backend fails
     """
 
-    def __init__(self, exception):  # type: (Exception) -> None
+    def __init__(self, exception, description=None, exc_info=(None, None, None)):
+        # type: (Exception, Optional[str], _ExcInfo) -> None
         super(BuildBackendException, self).__init__()
         self.exception = exception  # type: Exception
+        self.exc_info = exc_info
+        self._description = description
 
     def __str__(self):  # type: () -> str
+        if self._description:
+            return self._description
         return 'Backend operation failed: {!r}'.format(self.exception)
 
 
@@ -264,15 +275,11 @@ class ProjectBuilder(object):
             (``sdist`` or ``wheel``)
         :param config_settings: Config settings for the build backend
         """
-        get_requires = getattr(self._hook, 'get_requires_for_build_{}'.format(distribution))
+        hook_name = 'get_requires_for_build_{}'.format(distribution)
+        get_requires = getattr(self._hook, hook_name)
 
-        with _working_directory(self.srcdir):
-            try:
-                return set(get_requires(config_settings))
-            except pep517.wrappers.BackendUnavailable:
-                raise BuildException("Backend '{}' is not available.".format(self._backend))
-            except Exception as e:
-                raise BuildBackendException(e)
+        with self._handle_backend(hook_name):
+            return set(get_requires(config_settings))
 
     def check_dependencies(self, distribution, config_settings=None):
         # type: (str, Optional[ConfigSettings]) -> Set[Tuple[str, ...]]
@@ -332,14 +339,26 @@ class ProjectBuilder(object):
         else:
             os.mkdir(outdir)
 
+        with self._handle_backend(callable.__name__):
+            basename = callback(outdir, config_settings, **kwargs)  # type: str
+
+        return os.path.join(outdir, basename)
+
+    @contextlib.contextmanager
+    def _handle_backend(self, hook):  # type: (str) -> Iterator[None]
         with _working_directory(self.srcdir):
             try:
-                basename = callback(outdir, config_settings, **kwargs)  # type: str
-                return os.path.join(outdir, basename)
-            except pep517.wrappers.BackendUnavailable:
-                raise BuildException("Backend '{}' is not available.".format(self._backend))
+                yield
+            except pep517.wrappers.BackendUnavailable as exception:
+                raise BuildBackendException(
+                    exception,
+                    "Backend '{}' is not available.".format(self._backend),
+                    sys.exc_info(),
+                )
+            except subprocess.CalledProcessError as exception:
+                raise BuildBackendException(exception, 'Backend subproccess exited when trying to invoke {}'.format(hook))
             except Exception as exception:
-                raise BuildBackendException(exception)
+                raise BuildBackendException(exception, exc_info=sys.exc_info())
 
 
 __all__ = (
diff --git a/src/build/__main__.py b/src/build/__main__.py
index 11c0666..62a095b 100644
--- a/src/build/__main__.py
+++ b/src/build/__main__.py
@@ -1,7 +1,10 @@
 # SPDX-License-Identifier: MIT
 
+from __future__ import print_function
+
 import argparse
 import os
+import subprocess
 import sys
 import traceback
 import warnings
@@ -97,10 +100,21 @@ def build_package(srcdir, outdir, distributions, config_settings=None, isolation
     except BuildException as e:
         _error(str(e))
     except BuildBackendException as e:
-        if sys.version_info >= (3, 5):
-            print(traceback.format_exc(-1))
+        if isinstance(e.exception, subprocess.CalledProcessError):
+            print()
         else:
-            print(traceback.format_exc())
+            if e.exc_info:
+                traceback.print_exception(
+                    e.exc_info[0],
+                    e.exc_info[1],
+                    e.exc_info[2],
+                    limit=-1,
+                )
+            else:
+                if sys.version_info >= (3, 5):
+                    print(traceback.format_exc(-1))
+                else:
+                    print(traceback.format_tb())
         _error(str(e))
 
 


Test patch:
diff --git a/tests/constraints.txt b/tests/constraints.txt
index 5712fd3..062e7c7 100644
--- a/tests/constraints.txt
+++ b/tests/constraints.txt
@@ -2,5 +2,5 @@ importlib-metadata==0.22;python_version < "3.8"
 packaging==19.0
 pep517==0.9.1
 toml==0.10.0
-typing==3.5.2.2;python_version < "3"
+typing==3.5.3.0;python_version < "3"
 virtualenv==20.0.35;python_version < "3"
diff --git a/tests/test_projectbuilder.py b/tests/test_projectbuilder.py
index 9c1f8d8..51a309d 100644
--- a/tests/test_projectbuilder.py
+++ b/tests/test_projectbuilder.py
@@ -199,7 +199,7 @@ def test_get_dependencies_missing_backend(packages_path, distribution):
     bad_backend_path = os.path.join(packages_path, 'test-bad-backend')
     builder = build.ProjectBuilder(bad_backend_path)
 
-    with pytest.raises(build.BuildException):
+    with pytest.raises(build.BuildBackendException):
         builder.get_dependencies(distribution)
 
 
@@ -215,7 +215,7 @@ def test_build_missing_backend(packages_path, distribution, tmpdir):
     bad_backend_path = os.path.join(packages_path, 'test-bad-backend')
     builder = build.ProjectBuilder(bad_backend_path)
 
-    with pytest.raises(build.BuildException):
+    with pytest.raises(build.BuildBackendException):
         builder.build(distribution, str(tmpdir))