Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions crates/uv/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,21 +100,46 @@ mod tool;
mod venv;
mod workspace;

/// The process status for a command that completed without a final error to render.
#[derive(Copy, Clone)]
pub enum ExitStatus {
/// The command succeeded.
Success,

/// The command failed due to an error in the user input.
/// The command reported a failure caused by user input.
Failure,

/// The command failed with an unexpected error.
/// The command reported an unexpected failure.
Error,

/// The command's exit status is propagated from an external command.
External(u8),
}

/// A command error propagated to the entrypoint for exit-status selection.
#[derive(Debug, thiserror::Error)]
pub(crate) enum UvError {
/// An error caused by invalid or unsatisfiable user input.
#[error(transparent)]
User(anyhow::Error),

/// An unexpected internal or environmental error.
#[error(transparent)]
Unexpected(anyhow::Error),
}

impl UvError {
/// Create a user-facing error.
fn user(error: impl Into<anyhow::Error>) -> Self {
Self::User(error.into())
}

/// Create an unexpected error.
pub(crate) fn unexpected(error: anyhow::Error) -> Self {
Self::Unexpected(error)
}
}

/// Read dotenv files into an overlay for a spawned process.
///
/// These values intentionally do not mutate uv's process environment and cannot mutate
Expand Down
8 changes: 2 additions & 6 deletions crates/uv/src/commands/project/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ use crate::commands::project::{
WorkspacePython, init_script_python_requirement, script_extra_build_requires,
};
use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
use crate::commands::{ExitStatus, ScriptPath, diagnostics, pip};
use crate::commands::{ExitStatus, ScriptPath, UvError, diagnostics, pip};
use crate::printer::Printer;
use crate::settings::{FrozenSource, LockCheck, LockCheckSource, ResolverSettings};

Expand Down Expand Up @@ -268,11 +268,7 @@ pub(crate) async fn lock(
Ok(ExitStatus::Success)
}
// Lock mismatches from `--check`/`--locked` are expected validation failures.
// Handle them here so we return exit code 1 instead of bubbling up as an error (exit code 2).
Err(err @ ProjectError::LockMismatch(..)) => {
writeln!(printer.stderr(), "{}", err.to_string().bold())?;
Ok(ExitStatus::Failure)
}
Err(err @ ProjectError::LockMismatch(..)) => Err(UvError::user(err).into()),
Err(ProjectError::Operation(err)) => diagnostics::OperationDiagnostic::default()
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into())),
Expand Down
22 changes: 5 additions & 17 deletions crates/uv/src/commands/project/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ use crate::commands::project::{
ProjectError, ScriptEnvironment, UniversalState, default_dependency_groups, detect_conflicts,
script_extra_build_requires, script_specification, update_environment,
};
use crate::commands::{ExitStatus, diagnostics};
use crate::commands::{ExitStatus, UvError, diagnostics};
use crate::printer::Printer;
use crate::settings::{
FrozenSource, InstallerSettingsRef, LockCheck, LockCheckSource, ResolverInstallerSettings,
Expand Down Expand Up @@ -373,14 +373,9 @@ pub(crate) async fn sync(
// sync operation, but exit with a non-zero status.
Outcome::LockMismatch(prev, cur, lock_source)
} else {
writeln!(
printer.stderr(),
"{}",
ProjectError::LockMismatch(prev, cur, lock_source)
.to_string()
.bold()
)?;
return Ok(ExitStatus::Failure);
return Err(
UvError::user(ProjectError::LockMismatch(prev, cur, lock_source)).into(),
);
}
}
Err(err) => return Err(err.into()),
Expand Down Expand Up @@ -474,14 +469,7 @@ pub(crate) async fn sync(
match outcome {
Outcome::Success(..) => Ok(ExitStatus::Success),
Outcome::LockMismatch(prev, cur, lock_source) => {
writeln!(
printer.stderr(),
"{}",
ProjectError::LockMismatch(prev, cur, lock_source)
.to_string()
.bold()
)?;
Ok(ExitStatus::Failure)
Err(UvError::user(ProjectError::LockMismatch(prev, cur, lock_source)).into())
}
}
}
Expand Down
9 changes: 2 additions & 7 deletions crates/uv/src/commands/workspace/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ use std::fmt::Write;
use std::path::Path;

use anyhow::{Context, Result};
use owo_colors::OwoColorize;

use uv_cache::{Cache, Refresh};
use uv_client::BaseClientBuilder;
use uv_configuration::{
Expand All @@ -25,7 +23,7 @@ use crate::commands::project::{
LinkErrorReporting, ProjectEnvironment, ProjectError, ProjectInterpreter, ScriptEnvironment,
ScriptInterpreter, UniversalState, WorkspacePython,
};
use crate::commands::{ExitStatus, diagnostics};
use crate::commands::{ExitStatus, UvError, diagnostics};
use crate::printer::Printer;
use crate::settings::{FrozenSource, LockCheck, ResolverSettings};

Expand Down Expand Up @@ -237,10 +235,7 @@ pub(crate) async fn metadata(

print_metadata(&export, printer)
}
Err(err @ ProjectError::LockMismatch(..)) => {
writeln!(printer.stderr(), "{}", err.to_string().bold())?;
Ok(ExitStatus::Failure)
}
Err(err @ ProjectError::LockMismatch(..)) => Err(UvError::user(err).into()),
Err(ProjectError::Operation(err)) => diagnostics::OperationDiagnostic::default()
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into())),
Expand Down
29 changes: 21 additions & 8 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ use uv_static::EnvVars;
use uv_warnings::{warn_user, warn_user_once};
use uv_workspace::{DiscoveryOptions, Workspace, WorkspaceCache};

use crate::commands::{ExitStatus, ParsedRunCommand, RunCommand, ScriptPath, ToolRunCommand};
use crate::commands::{
ExitStatus, ParsedRunCommand, RunCommand, ScriptPath, ToolRunCommand, UvError,
};
use crate::printer::Printer;
use crate::settings::{
CacheSettings, GlobalSettings, PipCheckSettings, PipCompileSettings, PipFreezeSettings,
Expand Down Expand Up @@ -3052,13 +3054,24 @@ where
match result {
Ok(code) => code.into(),
Err(err) => {
trace!("Error trace: {err:?}");

let hints = commands::diagnostics::hints_for_error(&err);
uv_errors::write_error_chain(err.as_ref(), hints)
.expect("writing to stderr should not fail");

ExitStatus::Error.into()
let error = match err.downcast::<UvError>() {
Ok(error) => error,
Err(err) => UvError::unexpected(err),
};
match error {
UvError::User(err) => {
trace!("Error trace: {err:?}");
anstream::eprintln!("{}", err.to_string().bold());
ExitStatus::Failure.into()
}
UvError::Unexpected(err) => {
trace!("Error trace: {err:?}");
let hints = commands::diagnostics::hints_for_error(&err);
uv_errors::write_error_chain(err.as_ref(), hints)
.expect("writing to stderr should not fail");
ExitStatus::Error.into()
}
}
}
}
}
Loading