Commit f127bd27 by Sébastien Eustace Committed by Randy Döring

Improve error reporting for build backend errors

Co-authored-by: Randy Döring <30527984+radoering@users.noreply.github.com>
parent 6c0ac386
...@@ -110,6 +110,8 @@ class Chef: ...@@ -110,6 +110,8 @@ class Chef:
def _prepare( def _prepare(
self, directory: Path, destination: Path, *, editable: bool = False self, directory: Path, destination: Path, *, editable: bool = False
) -> Path: ) -> Path:
from subprocess import CalledProcessError
with ephemeral_environment(self._env.python) as venv: with ephemeral_environment(self._env.python) as venv:
env = IsolatedEnv(venv, self._config) env = IsolatedEnv(venv, self._config)
builder = ProjectBuilder( builder = ProjectBuilder(
...@@ -119,21 +121,38 @@ class Chef: ...@@ -119,21 +121,38 @@ class Chef:
runner=quiet_subprocess_runner, runner=quiet_subprocess_runner,
) )
env.install(builder.build_system_requires) env.install(builder.build_system_requires)
env.install(
builder.build_system_requires | builder.get_requires_for_build("wheel")
)
stdout = StringIO() stdout = StringIO()
with redirect_stdout(stdout): error: Exception | None = None
try: try:
return Path( with redirect_stdout(stdout):
env.install(
builder.build_system_requires
| builder.get_requires_for_build("wheel")
)
path = Path(
builder.build( builder.build(
"wheel" if not editable else "editable", "wheel" if not editable else "editable",
destination.as_posix(), destination.as_posix(),
) )
) )
except BuildBackendException as e: except BuildBackendException as e:
raise ChefBuildError(str(e)) message_parts = [str(e)]
if isinstance(e.exception, CalledProcessError) and (
e.exception.stdout is not None or e.exception.stderr is not None
):
message_parts.append(
e.exception.stderr.decode()
if e.exception.stderr is not None
else e.exception.stdout.decode()
)
error = ChefBuildError("\n\n".join(message_parts))
if error is not None:
raise error from None
return path
def _prepare_sdist(self, archive: Path, destination: Path | None = None) -> Path: def _prepare_sdist(self, archive: Path, destination: Path | None = None) -> Path:
from poetry.core.packages.utils.link import Link from poetry.core.packages.utils.link import Link
......
...@@ -18,6 +18,7 @@ from cleo.io.null_io import NullIO ...@@ -18,6 +18,7 @@ from cleo.io.null_io import NullIO
from poetry.core.packages.utils.link import Link from poetry.core.packages.utils.link import Link
from poetry.installation.chef import Chef from poetry.installation.chef import Chef
from poetry.installation.chef import ChefBuildError
from poetry.installation.chooser import Chooser from poetry.installation.chooser import Chooser
from poetry.installation.operations import Install from poetry.installation.operations import Install
from poetry.installation.operations import Uninstall from poetry.installation.operations import Uninstall
...@@ -295,6 +296,19 @@ class Executor: ...@@ -295,6 +296,19 @@ class Executor:
with self._lock: with self._lock:
trace = ExceptionTrace(e) trace = ExceptionTrace(e)
trace.render(io) trace.render(io)
if isinstance(e, ChefBuildError):
pkg = operation.package
requirement = pkg.to_dependency().to_pep_508()
io.write_line("")
io.write_line(
"<info>"
"Note: This error originates from the build backend,"
" and is likely not a problem with poetry"
f" but with {pkg.pretty_name} ({pkg.full_pretty_version})"
" not supporting PEP 517 builds. You can verify this by"
f" running 'pip wheel --use-pep517 \"{requirement}\"'."
"</info>"
)
io.write_line("") io.write_line("")
finally: finally:
with self._lock: with self._lock:
......
...@@ -7,6 +7,7 @@ import shutil ...@@ -7,6 +7,7 @@ import shutil
import tempfile import tempfile
from pathlib import Path from pathlib import Path
from subprocess import CalledProcessError
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from typing import Any from typing import Any
from typing import Callable from typing import Callable
...@@ -14,12 +15,15 @@ from urllib.parse import urlparse ...@@ -14,12 +15,15 @@ from urllib.parse import urlparse
import pytest import pytest
from build import BuildBackendException
from build import ProjectBuilder
from cleo.formatters.style import Style from cleo.formatters.style import Style
from cleo.io.buffered_io import BufferedIO from cleo.io.buffered_io import BufferedIO
from cleo.io.outputs.output import Verbosity from cleo.io.outputs.output import Verbosity
from poetry.core.packages.package import Package from poetry.core.packages.package import Package
from poetry.core.packages.utils.link import Link from poetry.core.packages.utils.link import Link
from poetry.factory import Factory
from poetry.installation.chef import Chef as BaseChef from poetry.installation.chef import Chef as BaseChef
from poetry.installation.executor import Executor from poetry.installation.executor import Executor
from poetry.installation.operations import Install from poetry.installation.operations import Install
...@@ -903,3 +907,71 @@ Package operations: 1 install, 0 updates, 0 removals ...@@ -903,3 +907,71 @@ Package operations: 1 install, 0 updates, 0 removals
assert mock_pip_install.call_count == 1 assert mock_pip_install.call_count == 1
assert mock_pip_install.call_args[1].get("upgrade") is True assert mock_pip_install.call_args[1].get("upgrade") is True
assert mock_pip_install.call_args[1].get("editable") is False assert mock_pip_install.call_args[1].get("editable") is False
@pytest.mark.parametrize("failing_method", ["build", "get_requires_for_build"])
def test_build_backend_errors_are_reported_correctly_if_caused_by_subprocess(
failing_method: str,
mocker: MockerFixture,
config: Config,
pool: RepositoryPool,
io: BufferedIO,
tmp_dir: str,
mock_file_downloads: None,
env: MockEnv,
):
mocker.patch.object(Factory, "create_pool", return_value=pool)
error = BuildBackendException(
CalledProcessError(1, ["pip"], output=b"Error on stdout")
)
mocker.patch.object(ProjectBuilder, failing_method, side_effect=error)
io.set_verbosity(Verbosity.NORMAL)
executor = Executor(env, pool, config, io)
package_name = "simple-project"
package_version = "1.2.3"
directory_package = Package(
package_name,
package_version,
source_type="directory",
source_url=Path(__file__)
.parent.parent.joinpath("fixtures/simple_project")
.resolve()
.as_posix(),
)
return_code = executor.execute(
[
Install(directory_package),
]
)
assert return_code == 1
package_url = directory_package.source_url
expected_start = f"""
Package operations: 1 install, 0 updates, 0 removals
• Installing {package_name} ({package_version} {package_url})
ChefBuildError
Backend operation failed: CalledProcessError(1, ['pip'])
\
Error on stdout
"""
requirement = directory_package.to_dependency().to_pep_508()
expected_end = f"""
Note: This error originates from the build backend, and is likely not a problem with \
poetry but with {package_name} ({package_version} {package_url}) not supporting \
PEP 517 builds. You can verify this by running 'pip wheel --use-pep517 "{requirement}"'.
"""
output = io.fetch_output()
assert output.startswith(expected_start)
assert output.endswith(expected_end)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment