Skip to content

Better build error messages #9660

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Dec 17, 2024
Merged

Better build error messages #9660

merged 6 commits into from
Dec 17, 2024

Conversation

konstin
Copy link
Member

@konstin konstin commented Dec 5, 2024

Build failures are one of the most common user facing failures that aren't "obivous" errors (such as typos) or resolver errors. Currently, they show more technical details than being focussed on this being an error in a subprocess that is either on the side of the package or - more likely - in the build environment, e.g. the user needs to install a dev package or their python version is incompatible.

The new error message clearly delineates the part that's important (this is a build backend problem) from the internals (we called this hook) and is consistent about which part of the dist building stage failed. We have to calibrate the exact wording of the error message some more. Most of the implementation is working around the orphan rule, (this)error rules and trait rules, so it came out more of a refactoring than intended.

Example:

image

@konstin konstin added the error messages Messaging when something goes wrong label Dec 5, 2024
@konstin konstin requested a review from zanieb December 5, 2024 12:53
use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep440::Version;
use version_ranges::Ranges;

/// Orphan-rule workaround for inspecting build frontend errors.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might need to rope in @BurntSushi to review this implementation detail

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing that would be useful here is a little more docs like, "If the orphan-rule didn't exist, we would do ... instead."

///
/// TODO(konsti): The `From<IsBuildBackendError>` blocks implementing `IsBuildBackendError` on the
/// type itself, so the wrapped
pub struct AnyErrorBuild(Box<dyn IsBuildBackendError>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AnyBuildError?

Copy link
Member Author

@konstin konstin Dec 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more like "anyhow::Error, but with an extra trait for build error querying on top".

@zanieb
Copy link
Member

zanieb commented Dec 5, 2024

Would it also be feasible to explain why we're building a package? Like are we doing it for install or just to read its metadata? Is that already obvious?

Copy link
Member

@BurntSushi BurntSushi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is probably fine. At least, I don't immediately see a simpler solution. But some extra docs (see comments) might help clear up some things.

fn is_build_backend_error(&self) -> bool;
}

/// Wrapper type to make `thiserror`'s `AsDynError` work with `IsBuildFrontendError`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean IsBuildBackendError?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I would mention that AsDynError is an internal thing in thiserror. It took me a bit to figure that out as I was confused about it not being in thiserror's public API docs.

/// Orphan-rule workaround for inspecting build frontend errors.
///
/// Normally we would match on the variants, but we have to pass this error through an opaque type
/// to avoid cyclical crate dependencies between the build frontend and resolver/installer.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to spell out the concrete cycle here? I think that would aide understanding what specifically this trait is trying to do.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave this here as context for the update later.

image

use uv_normalize::{ExtraName, GroupName, PackageName};
use uv_pep440::Version;
use version_ranges::Ranges;

/// Orphan-rule workaround for inspecting build frontend errors.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing that would be useful here is a little more docs like, "If the orphan-rule didn't exist, we would do ... instead."

#[error("Failed to install requirements from {0}")]
RequirementsInstall(&'static str, #[source] anyhow::Error),
RequirementsInstall(&'static str, #[source] AnyErrorBuild),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as below, but this might be dropping any chain that might have been on anyhow::Error? Not 100% sure

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AnyErrorBuild preserves the chain, it passes .source() calls through to its wrapped type.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean its Display impl. The Display impl for a Box<dyn Error> doesn't emit the chain. But a anyhow::Error will.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

afaik all the error types only show their inner message, but not the chain, over which you have to iterate manually. When testing,

use thiserror::Error;

#[derive(Error, Debug)]
#[error("Wrapper Err")]
struct WrappedErr;

fn main() {
    let err: anyhow::Error = WrappedErr.into();
    println!("{}", err);
    println!("{}", err.context("2"));
}

shows

Wrapper Err
2

and we don't seem to be losing and context in our snapshot tests.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

anyhow does show the chain, but only in alternate mode:

use thiserror::Error;

#[derive(Error, Debug)]
#[error("Inner Err")]
struct InnerErr;

#[derive(Error, Debug)]
#[error("Wrapper Err")]
struct WrappedErr(#[source] InnerErr);

fn main() {
    let err = WrappedErr(InnerErr);
    println!("{:#}", err);
    let err: anyhow::Error = WrappedErr(InnerErr).into();
    println!("{:#}", err);
    println!("{:#}", err.context("2"));
}

shows

Wrapper Err
Wrapper Err: Inner Err
2: Wrapper Err: Inner Err

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay, so anyhow::Error's Display impl is the same as Box<dyn Error>. Interesting. All righty.

And yeah, I realize the snapshot tests indicate nothing untoward is happening here. I don't have a full grasp of the surrounding code and I wasn't sure how easy it would be to break something in the future. But I think you've demonstrated my concern is unfounded. Thank you. :)

Base automatically changed from konsti/unify-dist-err to main December 6, 2024 01:54
@konstin konstin marked this pull request as draft December 6, 2024 11:08
@konstin konstin force-pushed the konsti/better-build-errors branch from afef55c to f6985ca Compare December 6, 2024 12:34
Clearly delineate the part that's important (this is a build backend problem) from the internals (we called this hook).
This does not modify the build error itself much, but it starts special casing build failures. Most of the implementation is working around the orphan rule, (this)error rules and trait rules.
@konstin konstin force-pushed the konsti/better-build-errors branch from f6985ca to 11922eb Compare December 6, 2024 13:12
@konstin konstin marked this pull request as ready for review December 6, 2024 14:10
@konstin konstin requested review from zanieb and BurntSushi December 12, 2024 15:57
Copy link
Member

@BurntSushi BurntSushi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

/// `anyhow::Error` does not allow attaching more traits. The next choice would be
/// `Box<dyn std::error::Error + IsBuildFrontendError + Send + Sync + 'static>`, but `thiserror`
/// complains about the internal `AsDynError` not being implemented when being used as `#[source]`.
/// This struct is an otherwise transparent error wrapper that thiserror recognizes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice thank you!

@zanieb
Copy link
Member

zanieb commented Dec 17, 2024

Awesome. Sorry for the delay!

@zanieb zanieb merged commit ebc6d20 into main Dec 17, 2024
64 checks passed
@zanieb zanieb deleted the konsti/better-build-errors branch December 17, 2024 15:44
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Dec 20, 2024
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [astral-sh/uv](https://github.com/astral-sh/uv) | patch | `0.5.8` -> `0.5.11` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>astral-sh/uv (astral-sh/uv)</summary>

### [`v0.5.11`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0511)

[Compare Source](astral-sh/uv@0.5.10...0.5.11)

##### Enhancements

-   Normalize `platform_system` to `sys_platform` ([#&#8203;9949](astral-sh/uv#9949))
-   Improve retry mechanisms on Windows for `copy_atomic` and `write_atomic` ([#&#8203;10026](astral-sh/uv#10026))
-   Add nuance to prefetch logging ([#&#8203;9984](astral-sh/uv#9984))
-   Update to [`python-build-standalone 20241219`](https://github.com/astral-sh/python-build-standalone/releases/tag/20241219)

##### Preview features

-   Build backend: Preserve executable bits for scripts in distributions ([#&#8203;10027](astral-sh/uv#10027))
-   Build backend: Handle case where `metadata_directory` already contains `dist-info` directory ([#&#8203;10005](astral-sh/uv#10005))

##### Performance

-   Batch resolver pre-fetches per fork ([#&#8203;10029](astral-sh/uv#10029))

##### Bug fixes

-   Allow `--script` to be provided with `uv run -` ([#&#8203;10035](astral-sh/uv#10035))
-   Allow `uv run` arguments when reading from `stdin` ([#&#8203;10034](astral-sh/uv#10034))
-   Prefer higher Python lower-bounds when forking ([#&#8203;10007](astral-sh/uv#10007))
-   Remove references to deprecated `first-match` ([#&#8203;10036](astral-sh/uv#10036))

##### Documentation

-   Add `uv python install --preview` to the documentation ([#&#8203;10010](astral-sh/uv#10010))
-   Fix `uv python install --default` note about multiple requests ([#&#8203;10011](astral-sh/uv#10011))
-   Fix typo in Caching docs ([#&#8203;10032](astral-sh/uv#10032))
-   Remove remaining references to deprecated `first-match` ([#&#8203;10038](astral-sh/uv#10038))
-   Supplement missing separators for `UV_INSTALL_DIR` directions on Windows ([#&#8203;9507](astral-sh/uv#9507))

### [`v0.5.10`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0510)

[Compare Source](astral-sh/uv@0.5.9...0.5.10)

##### Enhancements

-   Improve backtracking behavior when packages conflict repeatedly ([#&#8203;9843](astral-sh/uv#9843))
-   Patch Python `sysconfig` values such as `AR` at `ar` install time ([#&#8203;9905](astral-sh/uv#9905))
-   Patch Python `sysconfig` values such as `clang` to `cc` at install time ([#&#8203;9916](astral-sh/uv#9916))
-   Skip `--native-tls` in `pip compile` header ([#&#8203;9913](astral-sh/uv#9913))
-   Add resolver error hint for no-binary and no-build failures ([#&#8203;9948](astral-sh/uv#9948))
-   Improve build error messages ([#&#8203;9660](astral-sh/uv#9660))
-   Reduce redundant Python version incompatibilities in resolver error message ([#&#8203;9957](astral-sh/uv#9957))
-   Reduce redundant enumeration of all package versions in some resolver errors ([#&#8203;9885](astral-sh/uv#9885))
-   Improve display of ranges when pre-releases are not allowed ([#&#8203;9944](astral-sh/uv#9944))
-   Improve error messages for `uv remove` ([#&#8203;9959](astral-sh/uv#9959))
-   Improve phrasing for single term incompatibilities ([#&#8203;9953](astral-sh/uv#9953))
-   Improve styling of `uv remove` dependency hints ([#&#8203;9960](astral-sh/uv#9960))
-   Omit trailing zeros on Python requirements inferred from versions ([#&#8203;9952](astral-sh/uv#9952))
-   Show a concise error message for missing `version` field ([#&#8203;9912](astral-sh/uv#9912))
-   Use the build options value to improve hints for no wheel / source distribution errors ([#&#8203;9950](astral-sh/uv#9950))

##### Bug fixes

-   Allow multiple disjoint URLs in overrides ([#&#8203;9893](astral-sh/uv#9893))
-   Include explicit indexes in publish index choice ([#&#8203;9932](astral-sh/uv#9932))
-   Fix Python interpreter detection for 32-bit operating systems on 64-bit hosts ([#&#8203;9970](astral-sh/uv#9970))

##### Documentation

-   Fix typo "operation system" ([#&#8203;9971](astral-sh/uv#9971))
-   Clarify uninstallation docs ([#&#8203;9938](astral-sh/uv#9938))
-   Add a note to say that dependencies between workspace members are editable ([#&#8203;9363](astral-sh/uv#9363))
-   Correctly document default value of `fork-strategy` setting ([#&#8203;9931](astral-sh/uv#9931))
-   Use double quotes for Windows support in examples ([#&#8203;9946](astral-sh/uv#9946))
-   Remove `pypy` from top-level pin example ([#&#8203;9896](astral-sh/uv#9896))
-   Update references to `python-build-standalone` to reflect the transferred project ([#&#8203;9977](astral-sh/uv#9977))
-   Use a different Ruff version in documentation ([#&#8203;9943](astral-sh/uv#9943))
-   Change example so it works as-is on `powershell` and `cmd.exe` ([#&#8203;9903](astral-sh/uv#9903))
-   Clarify best practice for Python matrix strategy in GitHub Actions ([#&#8203;9454](astral-sh/uv#9454))
-   Add documentation for `uv-lock` and `uv-export` pre-commit hooks ([#&#8203;9872](astral-sh/uv#9872))

##### Preview features

-   Build backend: Fix pre-PEP 639 license files ([#&#8203;9965](astral-sh/uv#9965))

### [`v0.5.9`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#059)

[Compare Source](astral-sh/uv@0.5.8...0.5.9)

##### Enhancements

-   Fork version selection based on `requires-python` requirements ([#&#8203;9827](astral-sh/uv#9827))
-   Patch `sysconfig` data at install time ([#&#8203;9857](astral-sh/uv#9857))
-   Remove `-isysroot` when patching sysconfig ([#&#8203;9860](astral-sh/uv#9860))

##### Configuration

-   Introduce a `--fork-strategy` preference mode ([#&#8203;9868](astral-sh/uv#9868))
-   Add support for `UV_OFFLINE` ([#&#8203;9795](astral-sh/uv#9795))

##### Bug fixes

-   Avoid `panic!()` when current directory does not exist ([#&#8203;9876](astral-sh/uv#9876))
-   Avoid reusing interpreter metadata when running under Rosetta ([#&#8203;9846](astral-sh/uv#9846))
-   Avoid trailing slash when deserializing from lockfile ([#&#8203;9848](astral-sh/uv#9848))
-   Fix bug in terms when collapsing unavailable versions in resolver errors ([#&#8203;9877](astral-sh/uv#9877))
-   Fix suggestion to use `uv help python` on invalid install requests ([#&#8203;9820](astral-sh/uv#9820))
-   Skip root when assessing prefix viability ([#&#8203;9823](astral-sh/uv#9823))
-   Avoid spurious 'Upgraded tool environment' in `uv tool upgrade` ([#&#8203;9870](astral-sh/uv#9870))

##### Rust API

-   Upgrade minimum Rust version to 1.83 ([#&#8203;9815](astral-sh/uv#9815))

##### Documentation

-   Document the `--fork-strategy` setting ([#&#8203;9887](astral-sh/uv#9887))

##### Preview features

-   Build backend: Allow underscores in entrypoints ([#&#8203;9825](astral-sh/uv#9825))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzOS42NC4wIiwidXBkYXRlZEluVmVyIjoiMzkuNzUuMCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90Il19-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
error messages Messaging when something goes wrong
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants