diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 39ed793f0..414b5ca54 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -244,7 +244,7 @@ pub(crate) async fn add( project::sync::do_sync( &VirtualProject::Project(project), &venv, - &lock, + &lock.lock, &extras, dev, Modifications::Sufficient, diff --git a/crates/uv/src/commands/project/lock.rs b/crates/uv/src/commands/project/lock.rs index b6311e963..bddac1d2f 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -32,6 +32,15 @@ use crate::commands::{pip, ExitStatus}; use crate::printer::Printer; use crate::settings::{ResolverSettings, ResolverSettingsRef}; +/// The result of running a lock operation. +#[derive(Debug, Clone)] +pub(crate) struct LockResult { + /// The previous lock, if any. + pub(crate) previous: Option, + /// The updated lock. + pub(crate) lock: Lock, +} + /// Resolve the project requirements into a lockfile. pub(crate) async fn lock( locked: bool, @@ -86,7 +95,12 @@ pub(crate) async fn lock( ) .await { - Ok(_) => Ok(ExitStatus::Success), + Ok(lock) => { + if let Some(previous) = lock.previous.as_ref() { + report_upgrades(previous, &lock.lock, printer)?; + } + Ok(ExitStatus::Success) + } Err(ProjectError::Operation(pip::operations::Error::Resolve( uv_resolver::ResolveError::NoSolution(err), ))) => { @@ -112,12 +126,16 @@ pub(super) async fn do_safe_lock( native_tls: bool, cache: &Cache, printer: Printer, -) -> Result { +) -> Result { if frozen { // Read the existing lockfile, but don't attempt to lock the project. - read(workspace) + let existing = read(workspace) .await? - .ok_or_else(|| ProjectError::MissingLockfile) + .ok_or_else(|| ProjectError::MissingLockfile)?; + Ok(LockResult { + previous: None, + lock: existing, + }) } else if locked { // Read the existing lockfile. let existing = read(workspace) @@ -145,7 +163,10 @@ pub(super) async fn do_safe_lock( return Err(ProjectError::LockMismatch); } - Ok(lock) + Ok(LockResult { + previous: Some(existing), + lock, + }) } else { // Read the existing lockfile. let existing = read(workspace).await?; @@ -166,16 +187,19 @@ pub(super) async fn do_safe_lock( ) .await?; - if !existing.is_some_and(|existing| existing == lock) { + if !existing.as_ref().is_some_and(|existing| *existing == lock) { commit(&lock, workspace).await?; } - Ok(lock) + Ok(LockResult { + previous: existing, + lock, + }) } } /// Lock the project requirements into a lockfile. -pub(super) async fn do_lock( +async fn do_lock( workspace: &Workspace, interpreter: &Interpreter, existing_lock: Option<&Lock>, @@ -506,16 +530,7 @@ pub(super) async fn do_lock( // Notify the user of any resolution diagnostics. pip::operations::diagnose_resolution(resolution.diagnostics(), printer)?; - let new_lock = Lock::from_resolution_graph(&resolution)?; - - // Notify the user of any dependency updates - if !upgrade.is_none() { - if let Some(existing_lock) = existing_lock { - report_upgrades(existing_lock, &new_lock, printer)?; - } - } - - Ok(new_lock) + Ok(Lock::from_resolution_graph(&resolution)?) } /// Write the lockfile to disk. diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index 1f2b643cf..2abd969dc 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -131,7 +131,7 @@ pub(crate) async fn remove( project::sync::do_sync( &VirtualProject::Project(project), &venv, - &lock, + &lock.lock, &extras, dev, Modifications::Exact, diff --git a/crates/uv/src/commands/project/run.rs b/crates/uv/src/commands/project/run.rs index 7a41b0f01..34348a8f0 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -238,7 +238,7 @@ pub(crate) async fn run( project::sync::do_sync( &project, &venv, - &lock, + &lock.lock, &extras, dev, Modifications::Sufficient, diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 9d1e55805..e27f6b9b1 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -95,7 +95,7 @@ pub(crate) async fn sync( do_sync( &project, &venv, - &lock, + &lock.lock, &extras, dev, modifications, diff --git a/crates/uv/src/commands/project/tree.rs b/crates/uv/src/commands/project/tree.rs index 905017bc2..601761ea7 100644 --- a/crates/uv/src/commands/project/tree.rs +++ b/crates/uv/src/commands/project/tree.rs @@ -83,7 +83,7 @@ pub(crate) async fn tree( // Read packages from the lockfile. let mut packages: IndexMap<_, Vec<_>> = IndexMap::new(); - for dist in lock.into_distributions() { + for dist in lock.lock.into_distributions() { let name = dist.name().clone(); let metadata = dist.to_metadata(workspace.install_path())?; packages.entry(name).or_default().push(metadata); diff --git a/crates/uv/tests/lock.rs b/crates/uv/tests/lock.rs index b344f37ce..ffd99710c 100644 --- a/crates/uv/tests/lock.rs +++ b/crates/uv/tests/lock.rs @@ -3569,23 +3569,23 @@ fn lock_new_extras() -> Result<()> { "#, )?; - deterministic! { context => - uv_snapshot!(context.filters(), context.lock().arg("--preview"), @r###" + uv_snapshot!(context.filters(), context.lock().arg("--preview"), @r###" success: true exit_code: 0 ----- stdout ----- ----- stderr ----- Resolved 7 packages in [TIME] + Added pysocks v1.7.1 "###); - let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap(); + let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap(); - insta::with_settings!({ - filters => context.filters(), - }, { - assert_snapshot!( - lock, @r###" + insta::with_settings!({ + filters => context.filters(), + }, { + assert_snapshot!( + lock, @r###" version = 1 requires-python = ">=3.12" exclude-newer = "2024-03-25 00:00:00 UTC" @@ -3678,9 +3678,8 @@ fn lock_new_extras() -> Result<()> { { url = "https://files.pythonhosted.org/packages/a2/73/a68704750a7679d0b6d3ad7aa8d4da8e14e151ae82e6fee774e6e0d05ec8/urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", size = 121067 }, ] "### - ); - }); - } + ); + }); Ok(()) } @@ -3863,14 +3862,15 @@ fn lock_resolution_mode() -> Result<()> { // Locking with `lowest-direct` should ignore the existing lockfile. uv_snapshot!(context.filters(), context.lock().arg("--resolution").arg("lowest-direct"), @r###" - success: true - exit_code: 0 - ----- stdout ----- + success: true + exit_code: 0 + ----- stdout ----- - ----- stderr ----- - warning: `uv lock` is experimental and may change without warning - Ignoring existing lockfile due to change in resolution mode: `highest` vs. `lowest-direct` - Resolved 4 packages in [TIME] + ----- stderr ----- + warning: `uv lock` is experimental and may change without warning + Ignoring existing lockfile due to change in resolution mode: `highest` vs. `lowest-direct` + Resolved 4 packages in [TIME] + Updated anyio v4.3.0 -> v3.0.0 "###); let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();