Skip to content

Commit 3bc30fc

Browse files
committed
Use rich diagnostic formatting for install failures
1 parent de9dc39 commit 3bc30fc

File tree

17 files changed

+435
-290
lines changed

17 files changed

+435
-290
lines changed

crates/uv-dispatch/src/lib.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -275,10 +275,7 @@ impl<'a> BuildContext for BuildDispatch<'a> {
275275
remote.iter().map(ToString::to_string).join(", ")
276276
);
277277

278-
preparer
279-
.prepare(remote, self.in_flight)
280-
.await
281-
.context("Failed to prepare distributions")?
278+
preparer.prepare(remote, self.in_flight).await?
282279
};
283280

284281
// Remove any unnecessary packages.

crates/uv-installer/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
pub use compile::{compile_tree, CompileError};
22
pub use installer::{Installer, Reporter as InstallReporter};
33
pub use plan::{Plan, Planner};
4-
pub use preparer::{Preparer, Reporter as PrepareReporter};
4+
pub use preparer::{Error as PrepareError, Preparer, Reporter as PrepareReporter};
55
pub use site_packages::{SatisfiesResult, SitePackages, SitePackagesDiagnostic};
66
pub use uninstall::{uninstall, UninstallError};
77

crates/uv-installer/src/preparer.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ pub enum Error {
2323
#[error("Using pre-built wheels is disabled, but attempted to use `{0}`")]
2424
NoBinary(PackageName),
2525
#[error("Failed to download `{0}`")]
26-
Download(Box<BuiltDist>, #[source] Box<uv_distribution::Error>),
26+
Download(Box<BuiltDist>, #[source] uv_distribution::Error),
2727
#[error("Failed to download and build `{0}`")]
28-
DownloadAndBuild(Box<SourceDist>, #[source] Box<uv_distribution::Error>),
28+
DownloadAndBuild(Box<SourceDist>, #[source] uv_distribution::Error),
2929
#[error("Failed to build `{0}`")]
30-
Build(Box<SourceDist>, #[source] Box<uv_distribution::Error>),
30+
Build(Box<SourceDist>, #[source] uv_distribution::Error),
3131
#[error("Unzip failed in another thread: {0}")]
3232
Thread(String),
3333
}
@@ -146,12 +146,12 @@ impl<'a, Context: BuildContext> Preparer<'a, Context> {
146146
.get_or_build_wheel(&dist, self.tags, policy)
147147
.boxed_local()
148148
.map_err(|err| match dist.clone() {
149-
Dist::Built(dist) => Error::Download(Box::new(dist), Box::new(err)),
149+
Dist::Built(dist) => Error::Download(Box::new(dist), err),
150150
Dist::Source(dist) => {
151151
if dist.is_local() {
152-
Error::Build(Box::new(dist), Box::new(err))
152+
Error::Build(Box::new(dist), err)
153153
} else {
154-
Error::DownloadAndBuild(Box::new(dist), Box::new(err))
154+
Error::DownloadAndBuild(Box::new(dist), err)
155155
}
156156
}
157157
})
@@ -166,12 +166,12 @@ impl<'a, Context: BuildContext> Preparer<'a, Context> {
166166
wheel.hashes(),
167167
);
168168
Err(match dist {
169-
Dist::Built(dist) => Error::Download(Box::new(dist), Box::new(err)),
169+
Dist::Built(dist) => Error::Download(Box::new(dist), err),
170170
Dist::Source(dist) => {
171171
if dist.is_local() {
172-
Error::Build(Box::new(dist), Box::new(err))
172+
Error::Build(Box::new(dist), err)
173173
} else {
174-
Error::DownloadAndBuild(Box::new(dist), Box::new(err))
174+
Error::DownloadAndBuild(Box::new(dist), err)
175175
}
176176
}
177177
})

crates/uv/src/commands/diagnostics.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use owo_colors::OwoColorize;
22
use rustc_hash::FxHashMap;
33
use std::str::FromStr;
44
use std::sync::LazyLock;
5-
use uv_distribution_types::{Name, SourceDist};
5+
use uv_distribution_types::{BuiltDist, Name, SourceDist};
66
use uv_normalize::PackageName;
77

88
/// Static map of common package name typos or misconfigurations to their correct package names.
@@ -48,6 +48,34 @@ pub(crate) fn download_and_build(sdist: Box<SourceDist>, cause: uv_distribution:
4848
anstream::eprint!("{report:?}");
4949
}
5050

51+
/// Render a remote binary distribution download failure with a help message.
52+
pub(crate) fn download(sdist: Box<BuiltDist>, cause: uv_distribution::Error) {
53+
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
54+
#[error("Failed to download `{sdist}`")]
55+
#[diagnostic()]
56+
struct Error {
57+
sdist: Box<BuiltDist>,
58+
#[source]
59+
cause: uv_distribution::Error,
60+
#[help]
61+
help: Option<String>,
62+
}
63+
64+
let report = miette::Report::new(Error {
65+
help: SUGGESTIONS.get(sdist.name()).map(|suggestion| {
66+
format!(
67+
"`{}` is often confused for `{}` Did you mean to install `{}` instead?",
68+
sdist.name().cyan(),
69+
suggestion.cyan(),
70+
suggestion.cyan(),
71+
)
72+
}),
73+
sdist,
74+
cause,
75+
});
76+
anstream::eprint!("{report:?}");
77+
}
78+
5179
/// Render a local source distribution build failure with a help message.
5280
pub(crate) fn build(sdist: Box<SourceDist>, cause: uv_distribution::Error) {
5381
#[derive(Debug, miette::Diagnostic, thiserror::Error)]

crates/uv/src/commands/pip/install.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ pub(crate) async fn pip_install(
439439
};
440440

441441
// Sync the environment.
442-
operations::install(
442+
match operations::install(
443443
&resolution,
444444
site_packages,
445445
modifications,
@@ -461,7 +461,26 @@ pub(crate) async fn pip_install(
461461
dry_run,
462462
printer,
463463
)
464-
.await?;
464+
.await
465+
{
466+
Ok(_) => {}
467+
Err(operations::Error::Prepare(uv_installer::PrepareError::Build(dist, err))) => {
468+
diagnostics::build(dist, err);
469+
return Ok(ExitStatus::Failure);
470+
}
471+
Err(operations::Error::Prepare(uv_installer::PrepareError::DownloadAndBuild(
472+
dist,
473+
err,
474+
))) => {
475+
diagnostics::download_and_build(dist, err);
476+
return Ok(ExitStatus::Failure);
477+
}
478+
Err(operations::Error::Prepare(uv_installer::PrepareError::Download(dist, err))) => {
479+
diagnostics::download(dist, err);
480+
return Ok(ExitStatus::Failure);
481+
}
482+
Err(err) => return Err(err.into()),
483+
}
465484

466485
// Notify the user of any resolution diagnostics.
467486
operations::diagnose_resolution(resolution.diagnostics(), printer)?;

crates/uv/src/commands/pip/operations.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -457,10 +457,7 @@ pub(crate) async fn install(
457457
)
458458
.with_reporter(PrepareReporter::from(printer).with_length(remote.len() as u64));
459459

460-
let wheels = preparer
461-
.prepare(remote.clone(), in_flight)
462-
.await
463-
.context("Failed to prepare distributions")?;
460+
let wheels = preparer.prepare(remote.clone(), in_flight).await?;
464461

465462
logger.on_prepare(wheels.len(), start, printer)?;
466463

@@ -751,6 +748,9 @@ pub(crate) fn diagnose_environment(
751748

752749
#[derive(thiserror::Error, Debug)]
753750
pub(crate) enum Error {
751+
#[error("Failed to prepare distributions")]
752+
Prepare(#[from] uv_installer::PrepareError),
753+
754754
#[error(transparent)]
755755
Resolve(#[from] uv_resolver::ResolveError),
756756

crates/uv/src/commands/pip/sync.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ pub(crate) async fn pip_sync(
383383
};
384384

385385
// Sync the environment.
386-
operations::install(
386+
match operations::install(
387387
&resolution,
388388
site_packages,
389389
Modifications::Exact,
@@ -405,7 +405,26 @@ pub(crate) async fn pip_sync(
405405
dry_run,
406406
printer,
407407
)
408-
.await?;
408+
.await
409+
{
410+
Ok(_) => {}
411+
Err(operations::Error::Prepare(uv_installer::PrepareError::Build(dist, err))) => {
412+
diagnostics::build(dist, err);
413+
return Ok(ExitStatus::Failure);
414+
}
415+
Err(operations::Error::Prepare(uv_installer::PrepareError::DownloadAndBuild(
416+
dist,
417+
err,
418+
))) => {
419+
diagnostics::download_and_build(dist, err);
420+
return Ok(ExitStatus::Failure);
421+
}
422+
Err(operations::Error::Prepare(uv_installer::PrepareError::Download(dist, err))) => {
423+
diagnostics::download(dist, err);
424+
return Ok(ExitStatus::Failure);
425+
}
426+
Err(err) => return Err(err.into()),
427+
}
409428

410429
// Notify the user of any resolution diagnostics.
411430
operations::diagnose_resolution(resolution.diagnostics(), printer)?;

crates/uv/src/commands/project/add.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,24 @@ pub(crate) async fn add(
703703
diagnostics::build(dist, err);
704704
Ok(ExitStatus::Failure)
705705
}
706+
ProjectError::Operation(pip::operations::Error::Prepare(
707+
uv_installer::PrepareError::Build(dist, err),
708+
)) => {
709+
diagnostics::build(dist, err);
710+
Ok(ExitStatus::Failure)
711+
}
712+
ProjectError::Operation(pip::operations::Error::Prepare(
713+
uv_installer::PrepareError::DownloadAndBuild(dist, err),
714+
)) => {
715+
diagnostics::download_and_build(dist, err);
716+
Ok(ExitStatus::Failure)
717+
}
718+
ProjectError::Operation(pip::operations::Error::Prepare(
719+
uv_installer::PrepareError::Download(dist, err),
720+
)) => {
721+
diagnostics::download(dist, err);
722+
Ok(ExitStatus::Failure)
723+
}
706724
err => {
707725
// Revert the changes to the `pyproject.toml`, if necessary.
708726
if modified {

crates/uv/src/commands/project/remove.rs

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ use uv_workspace::pyproject_mut::{DependencyTarget, PyProjectTomlMut};
2121
use uv_workspace::{DiscoveryOptions, VirtualProject, Workspace};
2222

2323
use crate::commands::pip::loggers::{DefaultInstallLogger, DefaultResolveLogger};
24+
use crate::commands::pip::operations;
2425
use crate::commands::pip::operations::Modifications;
25-
use crate::commands::project::default_dependency_groups;
2626
use crate::commands::project::lock::LockMode;
27-
use crate::commands::{project, ExitStatus, SharedState};
27+
use crate::commands::project::{default_dependency_groups, ProjectError};
28+
use crate::commands::{diagnostics, project, ExitStatus, SharedState};
2829
use crate::printer::Printer;
2930
use crate::settings::ResolverInstallerSettings;
3031

@@ -213,7 +214,7 @@ pub(crate) async fn remove(
213214
let state = SharedState::default();
214215

215216
// Lock and sync the environment, if necessary.
216-
let lock = project::lock::do_safe_lock(
217+
let lock = match project::lock::do_safe_lock(
217218
mode,
218219
project.workspace(),
219220
settings.as_ref().into(),
@@ -227,8 +228,41 @@ pub(crate) async fn remove(
227228
cache,
228229
printer,
229230
)
230-
.await?
231-
.into_lock();
231+
.await
232+
{
233+
Ok(result) => result.into_lock(),
234+
Err(ProjectError::Operation(operations::Error::Resolve(
235+
uv_resolver::ResolveError::NoSolution(err),
236+
))) => {
237+
diagnostics::no_solution(&err);
238+
return Ok(ExitStatus::Failure);
239+
}
240+
Err(ProjectError::Operation(operations::Error::Resolve(
241+
uv_resolver::ResolveError::DownloadAndBuild(dist, err),
242+
))) => {
243+
diagnostics::download_and_build(dist, err);
244+
return Ok(ExitStatus::Failure);
245+
}
246+
Err(ProjectError::Operation(operations::Error::Resolve(
247+
uv_resolver::ResolveError::Build(dist, err),
248+
))) => {
249+
diagnostics::build(dist, err);
250+
return Ok(ExitStatus::Failure);
251+
}
252+
Err(ProjectError::Operation(operations::Error::Requirements(
253+
uv_requirements::Error::DownloadAndBuild(dist, err),
254+
))) => {
255+
diagnostics::download_and_build(dist, err);
256+
return Ok(ExitStatus::Failure);
257+
}
258+
Err(ProjectError::Operation(operations::Error::Requirements(
259+
uv_requirements::Error::Build(dist, err),
260+
))) => {
261+
diagnostics::build(dist, err);
262+
return Ok(ExitStatus::Failure);
263+
}
264+
Err(err) => return Err(err.into()),
265+
};
232266

233267
if no_sync {
234268
return Ok(ExitStatus::Success);
@@ -255,7 +289,7 @@ pub(crate) async fn remove(
255289
},
256290
};
257291

258-
project::sync::do_sync(
292+
match project::sync::do_sync(
259293
target,
260294
&venv,
261295
&extras,
@@ -272,7 +306,29 @@ pub(crate) async fn remove(
272306
cache,
273307
printer,
274308
)
275-
.await?;
309+
.await
310+
{
311+
Ok(()) => {}
312+
Err(ProjectError::Operation(operations::Error::Prepare(
313+
uv_installer::PrepareError::Build(dist, err),
314+
))) => {
315+
diagnostics::build(dist, err);
316+
return Ok(ExitStatus::Failure);
317+
}
318+
Err(ProjectError::Operation(operations::Error::Prepare(
319+
uv_installer::PrepareError::DownloadAndBuild(dist, err),
320+
))) => {
321+
diagnostics::download_and_build(dist, err);
322+
return Ok(ExitStatus::Failure);
323+
}
324+
Err(ProjectError::Operation(operations::Error::Prepare(
325+
uv_installer::PrepareError::Download(dist, err),
326+
))) => {
327+
diagnostics::download(dist, err);
328+
return Ok(ExitStatus::Failure);
329+
}
330+
Err(err) => return Err(err.into()),
331+
}
276332

277333
Ok(ExitStatus::Success)
278334
}

crates/uv/src/commands/project/run.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -697,7 +697,7 @@ pub(crate) async fn run(
697697

698698
let install_options = InstallOptions::default();
699699

700-
project::sync::do_sync(
700+
match project::sync::do_sync(
701701
target,
702702
&venv,
703703
&extras,
@@ -718,7 +718,29 @@ pub(crate) async fn run(
718718
cache,
719719
printer,
720720
)
721-
.await?;
721+
.await
722+
{
723+
Ok(()) => {}
724+
Err(ProjectError::Operation(operations::Error::Prepare(
725+
uv_installer::PrepareError::Build(dist, err),
726+
))) => {
727+
diagnostics::build(dist, err);
728+
return Ok(ExitStatus::Failure);
729+
}
730+
Err(ProjectError::Operation(operations::Error::Prepare(
731+
uv_installer::PrepareError::DownloadAndBuild(dist, err),
732+
))) => {
733+
diagnostics::download_and_build(dist, err);
734+
return Ok(ExitStatus::Failure);
735+
}
736+
Err(ProjectError::Operation(operations::Error::Prepare(
737+
uv_installer::PrepareError::Download(dist, err),
738+
))) => {
739+
diagnostics::download(dist, err);
740+
return Ok(ExitStatus::Failure);
741+
}
742+
Err(err) => return Err(err.into()),
743+
}
722744

723745
lock = Some(result.into_lock());
724746
}

0 commit comments

Comments
 (0)