-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Conversation
use uv_normalize::{ExtraName, GroupName, PackageName}; | ||
use uv_pep440::Version; | ||
use version_ranges::Ranges; | ||
|
||
/// Orphan-rule workaround for inspecting build frontend errors. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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>); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AnyBuildError
?
There was a problem hiding this comment.
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".
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? |
There was a problem hiding this 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`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean IsBuildBackendError
?
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use uv_normalize::{ExtraName, GroupName, PackageName}; | ||
use uv_pep440::Version; | ||
use version_ranges::Ranges; | ||
|
||
/// Orphan-rule workaround for inspecting build frontend errors. |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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. :)
afef55c
to
f6985ca
Compare
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.
f6985ca
to
11922eb
Compare
There was a problem hiding this 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice thank you!
Awesome. Sorry for the delay! |
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` ([#​9949](astral-sh/uv#9949)) - Improve retry mechanisms on Windows for `copy_atomic` and `write_atomic` ([#​10026](astral-sh/uv#10026)) - Add nuance to prefetch logging ([#​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 ([#​10027](astral-sh/uv#10027)) - Build backend: Handle case where `metadata_directory` already contains `dist-info` directory ([#​10005](astral-sh/uv#10005)) ##### Performance - Batch resolver pre-fetches per fork ([#​10029](astral-sh/uv#10029)) ##### Bug fixes - Allow `--script` to be provided with `uv run -` ([#​10035](astral-sh/uv#10035)) - Allow `uv run` arguments when reading from `stdin` ([#​10034](astral-sh/uv#10034)) - Prefer higher Python lower-bounds when forking ([#​10007](astral-sh/uv#10007)) - Remove references to deprecated `first-match` ([#​10036](astral-sh/uv#10036)) ##### Documentation - Add `uv python install --preview` to the documentation ([#​10010](astral-sh/uv#10010)) - Fix `uv python install --default` note about multiple requests ([#​10011](astral-sh/uv#10011)) - Fix typo in Caching docs ([#​10032](astral-sh/uv#10032)) - Remove remaining references to deprecated `first-match` ([#​10038](astral-sh/uv#10038)) - Supplement missing separators for `UV_INSTALL_DIR` directions on Windows ([#​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 ([#​9843](astral-sh/uv#9843)) - Patch Python `sysconfig` values such as `AR` at `ar` install time ([#​9905](astral-sh/uv#9905)) - Patch Python `sysconfig` values such as `clang` to `cc` at install time ([#​9916](astral-sh/uv#9916)) - Skip `--native-tls` in `pip compile` header ([#​9913](astral-sh/uv#9913)) - Add resolver error hint for no-binary and no-build failures ([#​9948](astral-sh/uv#9948)) - Improve build error messages ([#​9660](astral-sh/uv#9660)) - Reduce redundant Python version incompatibilities in resolver error message ([#​9957](astral-sh/uv#9957)) - Reduce redundant enumeration of all package versions in some resolver errors ([#​9885](astral-sh/uv#9885)) - Improve display of ranges when pre-releases are not allowed ([#​9944](astral-sh/uv#9944)) - Improve error messages for `uv remove` ([#​9959](astral-sh/uv#9959)) - Improve phrasing for single term incompatibilities ([#​9953](astral-sh/uv#9953)) - Improve styling of `uv remove` dependency hints ([#​9960](astral-sh/uv#9960)) - Omit trailing zeros on Python requirements inferred from versions ([#​9952](astral-sh/uv#9952)) - Show a concise error message for missing `version` field ([#​9912](astral-sh/uv#9912)) - Use the build options value to improve hints for no wheel / source distribution errors ([#​9950](astral-sh/uv#9950)) ##### Bug fixes - Allow multiple disjoint URLs in overrides ([#​9893](astral-sh/uv#9893)) - Include explicit indexes in publish index choice ([#​9932](astral-sh/uv#9932)) - Fix Python interpreter detection for 32-bit operating systems on 64-bit hosts ([#​9970](astral-sh/uv#9970)) ##### Documentation - Fix typo "operation system" ([#​9971](astral-sh/uv#9971)) - Clarify uninstallation docs ([#​9938](astral-sh/uv#9938)) - Add a note to say that dependencies between workspace members are editable ([#​9363](astral-sh/uv#9363)) - Correctly document default value of `fork-strategy` setting ([#​9931](astral-sh/uv#9931)) - Use double quotes for Windows support in examples ([#​9946](astral-sh/uv#9946)) - Remove `pypy` from top-level pin example ([#​9896](astral-sh/uv#9896)) - Update references to `python-build-standalone` to reflect the transferred project ([#​9977](astral-sh/uv#9977)) - Use a different Ruff version in documentation ([#​9943](astral-sh/uv#9943)) - Change example so it works as-is on `powershell` and `cmd.exe` ([#​9903](astral-sh/uv#9903)) - Clarify best practice for Python matrix strategy in GitHub Actions ([#​9454](astral-sh/uv#9454)) - Add documentation for `uv-lock` and `uv-export` pre-commit hooks ([#​9872](astral-sh/uv#9872)) ##### Preview features - Build backend: Fix pre-PEP 639 license files ([#​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 ([#​9827](astral-sh/uv#9827)) - Patch `sysconfig` data at install time ([#​9857](astral-sh/uv#9857)) - Remove `-isysroot` when patching sysconfig ([#​9860](astral-sh/uv#9860)) ##### Configuration - Introduce a `--fork-strategy` preference mode ([#​9868](astral-sh/uv#9868)) - Add support for `UV_OFFLINE` ([#​9795](astral-sh/uv#9795)) ##### Bug fixes - Avoid `panic!()` when current directory does not exist ([#​9876](astral-sh/uv#9876)) - Avoid reusing interpreter metadata when running under Rosetta ([#​9846](astral-sh/uv#9846)) - Avoid trailing slash when deserializing from lockfile ([#​9848](astral-sh/uv#9848)) - Fix bug in terms when collapsing unavailable versions in resolver errors ([#​9877](astral-sh/uv#9877)) - Fix suggestion to use `uv help python` on invalid install requests ([#​9820](astral-sh/uv#9820)) - Skip root when assessing prefix viability ([#​9823](astral-sh/uv#9823)) - Avoid spurious 'Upgraded tool environment' in `uv tool upgrade` ([#​9870](astral-sh/uv#9870)) ##### Rust API - Upgrade minimum Rust version to 1.83 ([#​9815](astral-sh/uv#9815)) ##### Documentation - Document the `--fork-strategy` setting ([#​9887](astral-sh/uv#9887)) ##### Preview features - Build backend: Allow underscores in entrypoints ([#​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-->
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: