Running uv lock --check with outdated lockfile will print that --check was passed, instead of --locked (#16322)

Hello,

# Summary

This PR fixes the confusing error message when running `uv lock --check`
with an outdated lockfile.

Previously, the error message incorrectly stated that `--locked` was
provided, even when the user used `--check`.

Now, the error message correctly indicates which flag was used: either
`--check` or `--locked`.

This closes #14105.

# Test plan

- I updated the existing integration test (`check_outdated_lock` in
`lock.rs`) to verify the new error message includes the correct flag.
- I ran existing tests to ensure I have no introduced regressions for
other commands.
This commit is contained in:
Charles-Meldhine Madi Mnemoi 2025-10-21 19:46:10 +02:00 committed by GitHub
parent 509a1e8ff6
commit 225bffbb6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 194 additions and 105 deletions

View file

@ -3719,20 +3719,34 @@ pub struct LockArgs {
/// missing or needs to be updated, uv will exit with an error. /// missing or needs to be updated, uv will exit with an error.
/// ///
/// Equivalent to `--locked`. /// Equivalent to `--locked`.
#[arg(long, alias = "locked", env = EnvVars::UV_LOCKED, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["check_exists", "upgrade"])] #[arg(long, env = EnvVars::UV_LOCKED, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["check_exists", "upgrade", "locked"])]
pub check: bool, pub check: bool,
/// Check if the lockfile is up-to-date.
///
/// Asserts that the `uv.lock` would remain unchanged after a resolution. If the lockfile is
/// missing or needs to be updated, uv will exit with an error.
///
/// Equivalent to `--check`.
#[arg(long, env = EnvVars::UV_LOCKED, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["check_exists", "upgrade", "check"], hide = true)]
pub locked: bool,
/// Assert that a `uv.lock` exists without checking if it is up-to-date. /// Assert that a `uv.lock` exists without checking if it is up-to-date.
/// ///
/// Equivalent to `--frozen`. /// Equivalent to `--frozen`.
#[arg(long, alias = "frozen", env = EnvVars::UV_FROZEN, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with = "check")] #[arg(long, alias = "frozen", env = EnvVars::UV_FROZEN, value_parser = clap::builder::BoolishValueParser::new(), conflicts_with_all = ["check", "locked"])]
pub check_exists: bool, pub check_exists: bool,
/// Perform a dry run, without writing the lockfile. /// Perform a dry run, without writing the lockfile.
/// ///
/// In dry-run mode, uv will resolve the project's dependencies and report on the resulting /// In dry-run mode, uv will resolve the project's dependencies and report on the resulting
/// changes, but will not write the lockfile to disk. /// changes, but will not write the lockfile to disk.
#[arg(long, conflicts_with = "check_exists", conflicts_with = "check")] #[arg(
long,
conflicts_with = "check_exists",
conflicts_with = "check",
conflicts_with = "locked"
)]
pub dry_run: bool, pub dry_run: bool,
/// Lock the specified Python script, rather than the current project. /// Lock the specified Python script, rather than the current project.

View file

@ -59,13 +59,13 @@ use crate::commands::project::{
use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter}; use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
use crate::commands::{ExitStatus, ScriptPath, diagnostics, project}; use crate::commands::{ExitStatus, ScriptPath, diagnostics, project};
use crate::printer::Printer; use crate::printer::Printer;
use crate::settings::ResolverInstallerSettings; use crate::settings::{LockCheck, ResolverInstallerSettings};
/// Add one or more packages to the project requirements. /// Add one or more packages to the project requirements.
#[allow(clippy::fn_params_excessive_bools)] #[allow(clippy::fn_params_excessive_bools)]
pub(crate) async fn add( pub(crate) async fn add(
project_dir: &Path, project_dir: &Path,
locked: bool, lock_check: LockCheck,
frozen: bool, frozen: bool,
active: Option<bool>, active: Option<bool>,
no_sync: bool, no_sync: bool,
@ -177,9 +177,9 @@ pub(crate) async fn add(
"`--package` is a no-op for Python scripts with inline metadata, which always run in isolation" "`--package` is a no-op for Python scripts with inline metadata, which always run in isolation"
); );
} }
if locked { if let LockCheck::Enabled(lock_check) = lock_check {
warn_user_once!( warn_user_once!(
"`--locked` is a no-op for Python scripts with inline metadata, which always run in isolation" "`{lock_check}` is a no-op for Python scripts with inline metadata, which always run in isolation"
); );
} }
if frozen { if frozen {
@ -728,7 +728,7 @@ pub(crate) async fn add(
&edits, &edits,
lock_state, lock_state,
sync_state, sync_state,
locked, lock_check,
no_install_project, no_install_project,
no_install_workspace, no_install_workspace,
no_install_local, no_install_local,
@ -959,7 +959,7 @@ async fn lock_and_sync(
edits: &[DependencyEdit], edits: &[DependencyEdit],
lock_state: UniversalState, lock_state: UniversalState,
sync_state: PlatformState, sync_state: PlatformState,
locked: bool, lock_check: LockCheck,
no_install_project: bool, no_install_project: bool,
no_install_workspace: bool, no_install_workspace: bool,
no_install_local: bool, no_install_local: bool,
@ -977,8 +977,8 @@ async fn lock_and_sync(
preview: Preview, preview: Preview,
) -> Result<(), ProjectError> { ) -> Result<(), ProjectError> {
let mut lock = project::lock::LockOperation::new( let mut lock = project::lock::LockOperation::new(
if locked { if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(target.interpreter()) LockMode::Locked(target.interpreter(), lock_check)
} else { } else {
LockMode::Write(target.interpreter()) LockMode::Write(target.interpreter())
}, },
@ -1099,8 +1099,8 @@ async fn lock_and_sync(
// If the file was modified, we have to lock again, though the only expected change is // If the file was modified, we have to lock again, though the only expected change is
// the addition of the minimum version specifiers. // the addition of the minimum version specifiers.
lock = project::lock::LockOperation::new( lock = project::lock::LockOperation::new(
if locked { if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(target.interpreter()) LockMode::Locked(target.interpreter(), lock_check)
} else { } else {
LockMode::Write(target.interpreter()) LockMode::Write(target.interpreter())
}, },

View file

@ -30,7 +30,7 @@ use crate::commands::project::{
}; };
use crate::commands::{ExitStatus, OutputWriter, diagnostics}; use crate::commands::{ExitStatus, OutputWriter, diagnostics};
use crate::printer::Printer; use crate::printer::Printer;
use crate::settings::ResolverSettings; use crate::settings::{LockCheck, ResolverSettings};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
@ -65,7 +65,7 @@ pub(crate) async fn export(
extras: ExtrasSpecification, extras: ExtrasSpecification,
groups: DependencyGroups, groups: DependencyGroups,
editable: Option<EditableMode>, editable: Option<EditableMode>,
locked: bool, lock_check: LockCheck,
frozen: bool, frozen: bool,
include_annotations: bool, include_annotations: bool,
include_header: bool, include_header: bool,
@ -172,8 +172,8 @@ pub(crate) async fn export(
// Determine the lock mode. // Determine the lock mode.
let mode = if frozen { let mode = if frozen {
LockMode::Frozen LockMode::Frozen
} else if locked { } else if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(interpreter.as_ref().unwrap()) LockMode::Locked(interpreter.as_ref().unwrap(), lock_check)
} else if matches!(target, ExportTarget::Script(_)) } else if matches!(target, ExportTarget::Script(_))
&& !LockTarget::from(&target).lock_path().is_file() && !LockTarget::from(&target).lock_path().is_file()
{ {

View file

@ -49,7 +49,7 @@ use crate::commands::project::{
use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter}; use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
use crate::commands::{ExitStatus, ScriptPath, diagnostics, pip}; use crate::commands::{ExitStatus, ScriptPath, diagnostics, pip};
use crate::printer::Printer; use crate::printer::Printer;
use crate::settings::ResolverSettings; use crate::settings::{LockCheck, LockCheckSource, ResolverSettings};
/// The result of running a lock operation. /// The result of running a lock operation.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -81,7 +81,7 @@ impl LockResult {
#[allow(clippy::fn_params_excessive_bools)] #[allow(clippy::fn_params_excessive_bools)]
pub(crate) async fn lock( pub(crate) async fn lock(
project_dir: &Path, project_dir: &Path,
locked: bool, lock_check: LockCheck,
frozen: bool, frozen: bool,
dry_run: DryRun, dry_run: DryRun,
refresh: Refresh, refresh: Refresh,
@ -177,8 +177,8 @@ pub(crate) async fn lock(
.into_interpreter(), .into_interpreter(),
}; };
if locked { if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(&interpreter) LockMode::Locked(&interpreter, lock_check)
} else if dry_run.enabled() { } else if dry_run.enabled() {
LockMode::DryRun(&interpreter) LockMode::DryRun(&interpreter)
} else { } else {
@ -257,7 +257,7 @@ pub(super) enum LockMode<'env> {
/// Perform a resolution, but don't write the lockfile to disk. /// Perform a resolution, but don't write the lockfile to disk.
DryRun(&'env Interpreter), DryRun(&'env Interpreter),
/// Error if the lockfile is not up-to-date with the project requirements. /// Error if the lockfile is not up-to-date with the project requirements.
Locked(&'env Interpreter), Locked(&'env Interpreter, LockCheckSource),
/// Use the existing lockfile without performing a resolution. /// Use the existing lockfile without performing a resolution.
Frozen, Frozen,
} }
@ -336,7 +336,7 @@ impl<'env> LockOperation<'env> {
.ok_or_else(|| ProjectError::MissingLockfile)?; .ok_or_else(|| ProjectError::MissingLockfile)?;
Ok(LockResult::Unchanged(existing)) Ok(LockResult::Unchanged(existing))
} }
LockMode::Locked(interpreter) => { LockMode::Locked(interpreter, lock_source) => {
// Read the existing lockfile. // Read the existing lockfile.
let existing = target let existing = target
.read() .read()
@ -367,6 +367,7 @@ impl<'env> LockOperation<'env> {
return Err(ProjectError::LockMismatch( return Err(ProjectError::LockMismatch(
prev.map(Box::new), prev.map(Box::new),
Box::new(cur), Box::new(cur),
lock_source,
)); ));
} }

View file

@ -57,7 +57,9 @@ use crate::commands::project::install_target::InstallTarget;
use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter}; use crate::commands::reporters::{PythonDownloadReporter, ResolverReporter};
use crate::commands::{capitalize, conjunction, pip}; use crate::commands::{capitalize, conjunction, pip};
use crate::printer::Printer; use crate::printer::Printer;
use crate::settings::{InstallerSettingsRef, ResolverInstallerSettings, ResolverSettings}; use crate::settings::{
InstallerSettingsRef, LockCheckSource, ResolverInstallerSettings, ResolverSettings,
};
pub(crate) mod add; pub(crate) mod add;
pub(crate) mod environment; pub(crate) mod environment;
@ -76,9 +78,9 @@ pub(crate) mod version;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub(crate) enum ProjectError { pub(crate) enum ProjectError {
#[error( #[error(
"The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`." "The lockfile at `uv.lock` needs to be updated, but `{2}` was provided. To update the lockfile, run `uv lock`."
)] )]
LockMismatch(Option<Box<Lock>>, Box<Lock>), LockMismatch(Option<Box<Lock>>, Box<Lock>, LockCheckSource),
#[error( #[error(
"Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`." "Unable to find lockfile at `uv.lock`. To create a lockfile, run `uv lock` or `uv sync`."

View file

@ -36,13 +36,13 @@ use crate::commands::project::{
}; };
use crate::commands::{ExitStatus, diagnostics, project}; use crate::commands::{ExitStatus, diagnostics, project};
use crate::printer::Printer; use crate::printer::Printer;
use crate::settings::ResolverInstallerSettings; use crate::settings::{LockCheck, ResolverInstallerSettings};
/// Remove one or more packages from the project requirements. /// Remove one or more packages from the project requirements.
#[allow(clippy::fn_params_excessive_bools)] #[allow(clippy::fn_params_excessive_bools)]
pub(crate) async fn remove( pub(crate) async fn remove(
project_dir: &Path, project_dir: &Path,
locked: bool, lock_check: LockCheck,
frozen: bool, frozen: bool,
active: Option<bool>, active: Option<bool>,
no_sync: bool, no_sync: bool,
@ -70,9 +70,9 @@ pub(crate) async fn remove(
"`--package` is a no-op for Python scripts with inline metadata, which always run in isolation" "`--package` is a no-op for Python scripts with inline metadata, which always run in isolation"
); );
} }
if locked { if let LockCheck::Enabled(lock_check) = lock_check {
warn_user_once!( warn_user_once!(
"`--locked` is a no-op for Python scripts with inline metadata, which always run in isolation" "`{lock_check}` is a no-op for Python scripts with inline metadata, which always run in isolation",
); );
} }
if frozen { if frozen {
@ -291,8 +291,8 @@ pub(crate) async fn remove(
.ok(); .ok();
// Determine the lock mode. // Determine the lock mode.
let mode = if locked { let mode = if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(target.interpreter()) LockMode::Locked(target.interpreter(), lock_check)
} else { } else {
LockMode::Write(target.interpreter()) LockMode::Write(target.interpreter())
}; };

View file

@ -73,7 +73,7 @@ use crate::commands::project::{
use crate::commands::reporters::PythonDownloadReporter; use crate::commands::reporters::PythonDownloadReporter;
use crate::commands::{ExitStatus, diagnostics, project}; use crate::commands::{ExitStatus, diagnostics, project};
use crate::printer::Printer; use crate::printer::Printer;
use crate::settings::{ResolverInstallerSettings, ResolverSettings}; use crate::settings::{LockCheck, ResolverInstallerSettings, ResolverSettings};
/// Run a command. /// Run a command.
#[allow(clippy::fn_params_excessive_bools)] #[allow(clippy::fn_params_excessive_bools)]
@ -83,7 +83,7 @@ pub(crate) async fn run(
command: Option<RunCommand>, command: Option<RunCommand>,
requirements: Vec<RequirementsSource>, requirements: Vec<RequirementsSource>,
show_resolution: bool, show_resolution: bool,
locked: bool, lock_check: LockCheck,
frozen: bool, frozen: bool,
active: Option<bool>, active: Option<bool>,
no_sync: bool, no_sync: bool,
@ -264,8 +264,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
// Determine the lock mode. // Determine the lock mode.
let mode = if frozen { let mode = if frozen {
LockMode::Frozen LockMode::Frozen
} else if locked { } else if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(environment.interpreter()) LockMode::Locked(environment.interpreter(), lock_check)
} else { } else {
LockMode::Write(environment.interpreter()) LockMode::Write(environment.interpreter())
}; };
@ -356,9 +356,9 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
Some(environment.into_interpreter()) Some(environment.into_interpreter())
} else { } else {
// If no lockfile is found, warn against `--locked` and `--frozen`. // If no lockfile is found, warn against `--locked` and `--frozen`.
if locked { if let LockCheck::Enabled(lock_check) = lock_check {
warn_user!( warn_user!(
"No lockfile found for Python script (ignoring `--locked`); run `{}` to generate a lockfile", "No lockfile found for Python script (ignoring `{lock_check}`); run `{}` to generate a lockfile",
"uv lock --script".green(), "uv lock --script".green(),
); );
} }
@ -588,8 +588,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
for flag in groups.history().as_flags_pretty() { for flag in groups.history().as_flags_pretty() {
warn_user!("`{flag}` has no effect when used alongside `--no-project`"); warn_user!("`{flag}` has no effect when used alongside `--no-project`");
} }
if locked { if let LockCheck::Enabled(lock_check) = lock_check {
warn_user!("`--locked` has no effect when used alongside `--no-project`"); warn_user!("`{lock_check}` has no effect when used alongside `--no-project`");
} }
if frozen { if frozen {
warn_user!("`--frozen` has no effect when used alongside `--no-project`"); warn_user!("`--frozen` has no effect when used alongside `--no-project`");
@ -605,8 +605,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
for flag in groups.history().as_flags_pretty() { for flag in groups.history().as_flags_pretty() {
warn_user!("`{flag}` has no effect when used outside of a project"); warn_user!("`{flag}` has no effect when used outside of a project");
} }
if locked { if let LockCheck::Enabled(lock_check) = lock_check {
warn_user!("`--locked` has no effect when used outside of a project"); warn_user!("`{lock_check}` has no effect when used outside of a project",);
} }
if no_sync { if no_sync {
warn_user!("`--no-sync` has no effect when used outside of a project"); warn_user!("`--no-sync` has no effect when used outside of a project");
@ -740,8 +740,8 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
// Determine the lock mode. // Determine the lock mode.
let mode = if frozen { let mode = if frozen {
LockMode::Frozen LockMode::Frozen
} else if locked { } else if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(venv.interpreter()) LockMode::Locked(venv.interpreter(), lock_check)
} else if isolated { } else if isolated {
LockMode::DryRun(venv.interpreter()) LockMode::DryRun(venv.interpreter())
} else { } else {

View file

@ -50,13 +50,15 @@ use crate::commands::project::{
}; };
use crate::commands::{ExitStatus, diagnostics}; use crate::commands::{ExitStatus, diagnostics};
use crate::printer::Printer; use crate::printer::Printer;
use crate::settings::{InstallerSettingsRef, ResolverInstallerSettings, ResolverSettings}; use crate::settings::{
InstallerSettingsRef, LockCheck, LockCheckSource, ResolverInstallerSettings, ResolverSettings,
};
/// Sync the project environment. /// Sync the project environment.
#[allow(clippy::fn_params_excessive_bools)] #[allow(clippy::fn_params_excessive_bools)]
pub(crate) async fn sync( pub(crate) async fn sync(
project_dir: &Path, project_dir: &Path,
locked: bool, lock_check: LockCheck,
frozen: bool, frozen: bool,
dry_run: DryRun, dry_run: DryRun,
active: Option<bool>, active: Option<bool>,
@ -217,9 +219,9 @@ pub(crate) async fn sync(
)); ));
} }
if locked { if let LockCheck::Enabled(lock_check) = lock_check {
return Err(anyhow::anyhow!( return Err(anyhow::anyhow!(
"`uv sync --locked` requires a script lockfile; run `{}` to lock the script", "`uv sync {lock_check}` requires a script lockfile; run `{}` to lock the script",
format!("uv lock --script {}", script.path.user_display()).green(), format!("uv lock --script {}", script.path.user_display()).green(),
)); ));
} }
@ -304,8 +306,8 @@ pub(crate) async fn sync(
// Determine the lock mode. // Determine the lock mode.
let mode = if frozen { let mode = if frozen {
LockMode::Frozen LockMode::Frozen
} else if locked { } else if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(environment.interpreter()) LockMode::Locked(environment.interpreter(), lock_check)
} else if dry_run.enabled() { } else if dry_run.enabled() {
LockMode::DryRun(environment.interpreter()) LockMode::DryRun(environment.interpreter())
} else { } else {
@ -338,16 +340,18 @@ pub(crate) async fn sync(
.report(err) .report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into())); .map_or(Ok(ExitStatus::Failure), |err| Err(err.into()));
} }
Err(ProjectError::LockMismatch(prev, cur)) => { Err(ProjectError::LockMismatch(prev, cur, lock_source)) => {
if dry_run.enabled() { if dry_run.enabled() {
// The lockfile is mismatched, but we're in dry-run mode. We should proceed with the // The lockfile is mismatched, but we're in dry-run mode. We should proceed with the
// sync operation, but exit with a non-zero status. // sync operation, but exit with a non-zero status.
Outcome::LockMismatch(prev, cur) Outcome::LockMismatch(prev, cur, lock_source)
} else { } else {
writeln!( writeln!(
printer.stderr(), printer.stderr(),
"{}", "{}",
ProjectError::LockMismatch(prev, cur).to_string().bold() ProjectError::LockMismatch(prev, cur, lock_source)
.to_string()
.bold()
)?; )?;
return Ok(ExitStatus::Failure); return Ok(ExitStatus::Failure);
} }
@ -415,11 +419,13 @@ pub(crate) async fn sync(
match outcome { match outcome {
Outcome::Success(..) => Ok(ExitStatus::Success), Outcome::Success(..) => Ok(ExitStatus::Success),
Outcome::LockMismatch(prev, cur) => { Outcome::LockMismatch(prev, cur, lock_source) => {
writeln!( writeln!(
printer.stderr(), printer.stderr(),
"{}", "{}",
ProjectError::LockMismatch(prev, cur).to_string().bold() ProjectError::LockMismatch(prev, cur, lock_source)
.to_string()
.bold()
)?; )?;
Ok(ExitStatus::Failure) Ok(ExitStatus::Failure)
} }
@ -433,7 +439,7 @@ enum Outcome {
/// The `lock` operation was successful. /// The `lock` operation was successful.
Success(LockResult), Success(LockResult),
/// The `lock` operation successfully resolved, but failed due to a mismatch (e.g., with `--locked`). /// The `lock` operation successfully resolved, but failed due to a mismatch (e.g., with `--locked`).
LockMismatch(Option<Box<Lock>>, Box<Lock>), LockMismatch(Option<Box<Lock>>, Box<Lock>, LockCheckSource),
} }
impl Outcome { impl Outcome {
@ -444,7 +450,7 @@ impl Outcome {
LockResult::Changed(_, lock) => lock, LockResult::Changed(_, lock) => lock,
LockResult::Unchanged(lock) => lock, LockResult::Unchanged(lock) => lock,
}, },
Self::LockMismatch(_prev, cur) => cur, Self::LockMismatch(_prev, cur, _lock_source) => cur,
} }
} }
} }
@ -1272,7 +1278,7 @@ impl From<(&LockTarget<'_>, &LockMode<'_>, &Outcome)> for LockReport {
LockResult::Unchanged(..) => match mode { LockResult::Unchanged(..) => match mode {
// When `--frozen` is used, we don't check the lockfile // When `--frozen` is used, we don't check the lockfile
LockMode::Frozen => LockAction::Use, LockMode::Frozen => LockAction::Use,
LockMode::DryRun(_) | LockMode::Locked(_) | LockMode::Write(_) => { LockMode::DryRun(_) | LockMode::Locked(_, _) | LockMode::Write(_) => {
LockAction::Check LockAction::Check
} }
}, },

View file

@ -29,6 +29,7 @@ use crate::commands::project::{
use crate::commands::reporters::LatestVersionReporter; use crate::commands::reporters::LatestVersionReporter;
use crate::commands::{ExitStatus, diagnostics}; use crate::commands::{ExitStatus, diagnostics};
use crate::printer::Printer; use crate::printer::Printer;
use crate::settings::LockCheck;
use crate::settings::ResolverSettings; use crate::settings::ResolverSettings;
/// Run a command. /// Run a command.
@ -36,7 +37,7 @@ use crate::settings::ResolverSettings;
pub(crate) async fn tree( pub(crate) async fn tree(
project_dir: &Path, project_dir: &Path,
groups: DependencyGroups, groups: DependencyGroups,
locked: bool, lock_check: LockCheck,
frozen: bool, frozen: bool,
universal: bool, universal: bool,
depth: u8, depth: u8,
@ -125,8 +126,8 @@ pub(crate) async fn tree(
// Determine the lock mode. // Determine the lock mode.
let mode = if frozen { let mode = if frozen {
LockMode::Frozen LockMode::Frozen
} else if locked { } else if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(interpreter.as_ref().unwrap()) LockMode::Locked(interpreter.as_ref().unwrap(), lock_check)
} else if matches!(target, LockTarget::Script(_)) && !target.lock_path().is_file() { } else if matches!(target, LockTarget::Script(_)) && !target.lock_path().is_file() {
// If we're locking a script, avoid creating a lockfile if it doesn't already exist. // If we're locking a script, avoid creating a lockfile if it doesn't already exist.
LockMode::DryRun(interpreter.as_ref().unwrap()) LockMode::DryRun(interpreter.as_ref().unwrap())

View file

@ -38,7 +38,7 @@ use crate::commands::project::{
}; };
use crate::commands::{ExitStatus, diagnostics, project}; use crate::commands::{ExitStatus, diagnostics, project};
use crate::printer::Printer; use crate::printer::Printer;
use crate::settings::ResolverInstallerSettings; use crate::settings::{LockCheck, ResolverInstallerSettings};
/// Display version information for uv itself (`uv self version`) /// Display version information for uv itself (`uv self version`)
pub(crate) fn self_version( pub(crate) fn self_version(
@ -63,7 +63,7 @@ pub(crate) async fn project_version(
package: Option<PackageName>, package: Option<PackageName>,
explicit_project: bool, explicit_project: bool,
dry_run: bool, dry_run: bool,
locked: bool, lock_check: LockCheck,
frozen: bool, frozen: bool,
active: Option<bool>, active: Option<bool>,
no_sync: bool, no_sync: bool,
@ -297,7 +297,7 @@ pub(crate) async fn project_version(
Box::pin(lock_and_sync( Box::pin(lock_and_sync(
project, project,
project_dir, project_dir,
locked, lock_check,
frozen, frozen,
active, active,
no_sync, no_sync,
@ -502,7 +502,7 @@ async fn print_frozen_version(
async fn lock_and_sync( async fn lock_and_sync(
project: VirtualProject, project: VirtualProject,
project_dir: &Path, project_dir: &Path,
locked: bool, lock_check: LockCheck,
frozen: bool, frozen: bool,
active: Option<bool>, active: Option<bool>,
no_sync: bool, no_sync: bool,
@ -579,8 +579,8 @@ async fn lock_and_sync(
}; };
// Determine the lock mode. // Determine the lock mode.
let mode = if locked { let mode = if let LockCheck::Enabled(lock_check) = lock_check {
LockMode::Locked(target.interpreter()) LockMode::Locked(target.interpreter(), lock_check)
} else { } else {
LockMode::Write(target.interpreter()) LockMode::Write(target.interpreter())
}; };

View file

@ -1844,7 +1844,7 @@ async fn run_project(
command, command,
requirements, requirements,
args.show_resolution || globals.verbose > 0, args.show_resolution || globals.verbose > 0,
args.locked, args.lock_check,
args.frozen, args.frozen,
args.active, args.active,
args.no_sync, args.no_sync,
@ -1895,7 +1895,7 @@ async fn run_project(
Box::pin(commands::sync( Box::pin(commands::sync(
project_dir, project_dir,
args.locked, args.lock_check,
args.frozen, args.frozen,
args.dry_run, args.dry_run,
args.active, args.active,
@ -1951,7 +1951,7 @@ async fn run_project(
Box::pin(commands::lock( Box::pin(commands::lock(
project_dir, project_dir,
args.locked, args.lock_check,
args.frozen, args.frozen,
args.dry_run, args.dry_run,
args.refresh, args.refresh,
@ -2056,7 +2056,7 @@ async fn run_project(
Box::pin(commands::add( Box::pin(commands::add(
project_dir, project_dir,
args.locked, args.lock_check,
args.frozen, args.frozen,
args.active, args.active,
args.no_sync, args.no_sync,
@ -2114,7 +2114,7 @@ async fn run_project(
Box::pin(commands::remove( Box::pin(commands::remove(
project_dir, project_dir,
args.locked, args.lock_check,
args.frozen, args.frozen,
args.active, args.active,
args.no_sync, args.no_sync,
@ -2158,7 +2158,7 @@ async fn run_project(
args.package, args.package,
explicit_project, explicit_project,
args.dry_run, args.dry_run,
args.locked, args.lock_check,
args.frozen, args.frozen,
args.active, args.active,
args.no_sync, args.no_sync,
@ -2195,7 +2195,7 @@ async fn run_project(
Box::pin(commands::tree( Box::pin(commands::tree(
project_dir, project_dir,
args.groups, args.groups,
args.locked, args.lock_check,
args.frozen, args.frozen,
args.universal, args.universal,
args.depth, args.depth,
@ -2249,7 +2249,7 @@ async fn run_project(
args.extras, args.extras,
args.groups, args.groups,
args.editable, args.editable,
args.locked, args.lock_check,
args.frozen, args.frozen,
args.include_annotations, args.include_annotations,
args.include_header, args.include_header,

View file

@ -347,10 +347,37 @@ impl InitSettings {
} }
} }
/// The source of a lock check operation.
#[derive(Debug, Clone, Copy)]
pub(crate) enum LockCheckSource {
/// The user invoked `uv <command> --locked`
Locked,
/// The user invoked `uv <command> --check`
Check,
}
impl std::fmt::Display for LockCheckSource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Locked => write!(f, "--locked"),
Self::Check => write!(f, "--check"),
}
}
}
// Has lock check been enabled?
#[derive(Debug, Clone, Copy)]
pub(crate) enum LockCheck {
/// Lockfile check is enabled.
Enabled(LockCheckSource),
/// Lockfile check is disabled.
Disabled,
}
/// The resolved settings to use for a `run` invocation. /// The resolved settings to use for a `run` invocation.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct RunSettings { pub(crate) struct RunSettings {
pub(crate) locked: bool, pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool, pub(crate) frozen: bool,
pub(crate) extras: ExtrasSpecification, pub(crate) extras: ExtrasSpecification,
pub(crate) groups: DependencyGroups, pub(crate) groups: DependencyGroups,
@ -439,7 +466,11 @@ impl RunSettings {
.unwrap_or_default(); .unwrap_or_default();
Self { Self {
locked, lock_check: if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
},
frozen, frozen,
extras: ExtrasSpecification::from_args( extras: ExtrasSpecification::from_args(
extra.unwrap_or_default(), extra.unwrap_or_default(),
@ -1263,7 +1294,7 @@ impl PythonPinSettings {
#[allow(clippy::struct_excessive_bools, dead_code)] #[allow(clippy::struct_excessive_bools, dead_code)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct SyncSettings { pub(crate) struct SyncSettings {
pub(crate) locked: bool, pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool, pub(crate) frozen: bool,
pub(crate) dry_run: DryRun, pub(crate) dry_run: DryRun,
pub(crate) script: Option<PathBuf>, pub(crate) script: Option<PathBuf>,
@ -1345,10 +1376,15 @@ impl SyncSettings {
} else { } else {
DryRun::from_args(dry_run) DryRun::from_args(dry_run)
}; };
let lock_check = if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
};
Self { Self {
output_format, output_format,
locked, lock_check,
frozen, frozen,
dry_run, dry_run,
script, script,
@ -1401,7 +1437,7 @@ impl SyncSettings {
#[allow(clippy::struct_excessive_bools, dead_code)] #[allow(clippy::struct_excessive_bools, dead_code)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct LockSettings { pub(crate) struct LockSettings {
pub(crate) locked: bool, pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool, pub(crate) frozen: bool,
pub(crate) dry_run: DryRun, pub(crate) dry_run: DryRun,
pub(crate) script: Option<PathBuf>, pub(crate) script: Option<PathBuf>,
@ -1421,6 +1457,7 @@ impl LockSettings {
) -> Self { ) -> Self {
let LockArgs { let LockArgs {
check, check,
locked,
check_exists, check_exists,
dry_run, dry_run,
script, script,
@ -1435,8 +1472,16 @@ impl LockSettings {
.map(|fs| fs.install_mirrors.clone()) .map(|fs| fs.install_mirrors.clone())
.unwrap_or_default(); .unwrap_or_default();
let lock_check = if check {
LockCheck::Enabled(LockCheckSource::Check)
} else if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
};
Self { Self {
locked: check, lock_check,
frozen: check_exists, frozen: check_exists,
dry_run: DryRun::from_args(dry_run), dry_run: DryRun::from_args(dry_run),
script, script,
@ -1454,7 +1499,7 @@ impl LockSettings {
#[allow(clippy::struct_excessive_bools, dead_code)] #[allow(clippy::struct_excessive_bools, dead_code)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct AddSettings { pub(crate) struct AddSettings {
pub(crate) locked: bool, pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool, pub(crate) frozen: bool,
pub(crate) active: Option<bool>, pub(crate) active: Option<bool>,
pub(crate) no_sync: bool, pub(crate) no_sync: bool,
@ -1603,7 +1648,11 @@ impl AddSettings {
let bounds = bounds.or(filesystem.as_ref().and_then(|fs| fs.add.add_bounds)); let bounds = bounds.or(filesystem.as_ref().and_then(|fs| fs.add.add_bounds));
Self { Self {
locked, lock_check: if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
},
frozen, frozen,
active: flag(active, no_active, "active"), active: flag(active, no_active, "active"),
no_sync, no_sync,
@ -1646,7 +1695,7 @@ impl AddSettings {
#[allow(clippy::struct_excessive_bools, dead_code)] #[allow(clippy::struct_excessive_bools, dead_code)]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct RemoveSettings { pub(crate) struct RemoveSettings {
pub(crate) locked: bool, pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool, pub(crate) frozen: bool,
pub(crate) active: Option<bool>, pub(crate) active: Option<bool>,
pub(crate) no_sync: bool, pub(crate) no_sync: bool,
@ -1707,7 +1756,11 @@ impl RemoveSettings {
.collect(); .collect();
Self { Self {
locked, lock_check: if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
},
frozen, frozen,
active: flag(active, no_active, "active"), active: flag(active, no_active, "active"),
no_sync, no_sync,
@ -1737,7 +1790,7 @@ pub(crate) struct VersionSettings {
pub(crate) short: bool, pub(crate) short: bool,
pub(crate) output_format: VersionFormat, pub(crate) output_format: VersionFormat,
pub(crate) dry_run: bool, pub(crate) dry_run: bool,
pub(crate) locked: bool, pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool, pub(crate) frozen: bool,
pub(crate) active: Option<bool>, pub(crate) active: Option<bool>,
pub(crate) no_sync: bool, pub(crate) no_sync: bool,
@ -1785,7 +1838,11 @@ impl VersionSettings {
short, short,
output_format, output_format,
dry_run, dry_run,
locked, lock_check: if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
},
frozen, frozen,
active: flag(active, no_active, "active"), active: flag(active, no_active, "active"),
no_sync, no_sync,
@ -1807,7 +1864,7 @@ impl VersionSettings {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub(crate) struct TreeSettings { pub(crate) struct TreeSettings {
pub(crate) groups: DependencyGroups, pub(crate) groups: DependencyGroups,
pub(crate) locked: bool, pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool, pub(crate) frozen: bool,
pub(crate) universal: bool, pub(crate) universal: bool,
pub(crate) depth: u8, pub(crate) depth: u8,
@ -1870,7 +1927,11 @@ impl TreeSettings {
only_group, only_group,
all_groups, all_groups,
), ),
locked, lock_check: if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
},
frozen, frozen,
universal, universal,
depth: tree.depth, depth: tree.depth,
@ -1906,7 +1967,7 @@ pub(crate) struct ExportSettings {
pub(crate) hashes: bool, pub(crate) hashes: bool,
pub(crate) install_options: InstallOptions, pub(crate) install_options: InstallOptions,
pub(crate) output_file: Option<PathBuf>, pub(crate) output_file: Option<PathBuf>,
pub(crate) locked: bool, pub(crate) lock_check: LockCheck,
pub(crate) frozen: bool, pub(crate) frozen: bool,
pub(crate) include_annotations: bool, pub(crate) include_annotations: bool,
pub(crate) include_header: bool, pub(crate) include_header: bool,
@ -2001,7 +2062,11 @@ impl ExportSettings {
no_emit_package, no_emit_package,
), ),
output_file, output_file,
locked, lock_check: if locked {
LockCheck::Enabled(LockCheckSource::Locked)
} else {
LockCheck::Disabled
},
frozen, frozen,
include_annotations: flag(annotate, no_annotate, "annotate").unwrap_or(true), include_annotations: flag(annotate, no_annotate, "annotate").unwrap_or(true),
include_header: flag(header, no_header, "header").unwrap_or(true), include_header: flag(header, no_header, "header").unwrap_or(true),

View file

@ -13193,7 +13193,7 @@ fn check_outdated_lock() -> Result<()> {
----- stderr ----- ----- stderr -----
Resolved 2 packages in [TIME] Resolved 2 packages in [TIME]
The lockfile at `uv.lock` needs to be updated, but `--locked` was provided. To update the lockfile, run `uv lock`. The lockfile at `uv.lock` needs to be updated, but `--check` was provided. To update the lockfile, run `uv lock`.
"); ");
Ok(()) Ok(())
} }

View file

@ -7780,7 +7780,7 @@ fn preview_features() {
short: false, short: false,
output_format: Text, output_format: Text,
dry_run: false, dry_run: false,
locked: false, lock_check: Disabled,
frozen: false, frozen: false,
active: None, active: None,
no_sync: false, no_sync: false,
@ -7894,7 +7894,7 @@ fn preview_features() {
short: false, short: false,
output_format: Text, output_format: Text,
dry_run: false, dry_run: false,
locked: false, lock_check: Disabled,
frozen: false, frozen: false,
active: None, active: None,
no_sync: false, no_sync: false,
@ -8008,7 +8008,7 @@ fn preview_features() {
short: false, short: false,
output_format: Text, output_format: Text,
dry_run: false, dry_run: false,
locked: false, lock_check: Disabled,
frozen: false, frozen: false,
active: None, active: None,
no_sync: false, no_sync: false,
@ -8122,7 +8122,7 @@ fn preview_features() {
short: false, short: false,
output_format: Text, output_format: Text,
dry_run: false, dry_run: false,
locked: false, lock_check: Disabled,
frozen: false, frozen: false,
active: None, active: None,
no_sync: false, no_sync: false,
@ -8236,7 +8236,7 @@ fn preview_features() {
short: false, short: false,
output_format: Text, output_format: Text,
dry_run: false, dry_run: false,
locked: false, lock_check: Disabled,
frozen: false, frozen: false,
active: None, active: None,
no_sync: false, no_sync: false,
@ -8352,7 +8352,7 @@ fn preview_features() {
short: false, short: false,
output_format: Text, output_format: Text,
dry_run: false, dry_run: false,
locked: false, lock_check: Disabled,
frozen: false, frozen: false,
active: None, active: None,
no_sync: false, no_sync: false,
@ -9596,7 +9596,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
), ),
} }
LockSettings { LockSettings {
locked: false, lock_check: Disabled,
frozen: false, frozen: false,
dry_run: Disabled, dry_run: Disabled,
script: None, script: None,
@ -9715,7 +9715,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
), ),
} }
LockSettings { LockSettings {
locked: false, lock_check: Disabled,
frozen: false, frozen: false,
dry_run: Disabled, dry_run: Disabled,
script: None, script: None,
@ -9857,7 +9857,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
), ),
} }
LockSettings { LockSettings {
locked: false, lock_check: Disabled,
frozen: false, frozen: false,
dry_run: Disabled, dry_run: Disabled,
script: None, script: None,
@ -9974,7 +9974,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
), ),
} }
LockSettings { LockSettings {
locked: false, lock_check: Disabled,
frozen: false, frozen: false,
dry_run: Disabled, dry_run: Disabled,
script: None, script: None,
@ -10081,7 +10081,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
), ),
} }
LockSettings { LockSettings {
locked: false, lock_check: Disabled,
frozen: false, frozen: false,
dry_run: Disabled, dry_run: Disabled,
script: None, script: None,
@ -10189,7 +10189,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
), ),
} }
LockSettings { LockSettings {
locked: false, lock_check: Disabled,
frozen: false, frozen: false,
dry_run: Disabled, dry_run: Disabled,
script: None, script: None,

View file

@ -1634,7 +1634,7 @@ uv lock [OPTIONS]
<p>May also be set with the <code>UV_INSECURE_HOST</code> environment variable.</p></dd><dt id="uv-lock--cache-dir"><a href="#uv-lock--cache-dir"><code>--cache-dir</code></a> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p> <p>May also be set with the <code>UV_INSECURE_HOST</code> environment variable.</p></dd><dt id="uv-lock--cache-dir"><a href="#uv-lock--cache-dir"><code>--cache-dir</code></a> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<p>Defaults to <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on macOS and Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p> <p>Defaults to <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on macOS and Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
<p>To view the location of the cache directory, run <code>uv cache dir</code>.</p> <p>To view the location of the cache directory, run <code>uv cache dir</code>.</p>
<p>May also be set with the <code>UV_CACHE_DIR</code> environment variable.</p></dd><dt id="uv-lock--check"><a href="#uv-lock--check"><code>--check</code></a>, <code>--locked</code></dt><dd><p>Check if the lockfile is up-to-date.</p> <p>May also be set with the <code>UV_CACHE_DIR</code> environment variable.</p></dd><dt id="uv-lock--check"><a href="#uv-lock--check"><code>--check</code></a></dt><dd><p>Check if the lockfile is up-to-date.</p>
<p>Asserts that the <code>uv.lock</code> would remain unchanged after a resolution. If the lockfile is missing or needs to be updated, uv will exit with an error.</p> <p>Asserts that the <code>uv.lock</code> would remain unchanged after a resolution. If the lockfile is missing or needs to be updated, uv will exit with an error.</p>
<p>Equivalent to <code>--locked</code>.</p> <p>Equivalent to <code>--locked</code>.</p>
<p>May also be set with the <code>UV_LOCKED</code> environment variable.</p></dd><dt id="uv-lock--check-exists"><a href="#uv-lock--check-exists"><code>--check-exists</code></a>, <code>--frozen</code></dt><dd><p>Assert that a <code>uv.lock</code> exists without checking if it is up-to-date.</p> <p>May also be set with the <code>UV_LOCKED</code> environment variable.</p></dd><dt id="uv-lock--check-exists"><a href="#uv-lock--check-exists"><code>--check-exists</code></a>, <code>--frozen</code></dt><dd><p>Assert that a <code>uv.lock</code> exists without checking if it is up-to-date.</p>