diff --git a/crates/uv-resolver/src/lock.rs b/crates/uv-resolver/src/lock.rs index f04151699..f58f0bcc9 100644 --- a/crates/uv-resolver/src/lock.rs +++ b/crates/uv-resolver/src/lock.rs @@ -40,7 +40,7 @@ use crate::{ExcludeNewer, PrereleaseMode, RequiresPython, ResolutionGraph, Resol /// The current version of the lockfile format. const VERSION: u32 = 1; -#[derive(Clone, Debug, serde::Deserialize, PartialEq, Eq)] +#[derive(Clone, Debug, serde::Deserialize)] #[serde(try_from = "LockWire")] pub struct Lock { version: u32, diff --git a/crates/uv/src/commands/project/add.rs b/crates/uv/src/commands/project/add.rs index 46da8e053..3a35c685a 100644 --- a/crates/uv/src/commands/project/add.rs +++ b/crates/uv/src/commands/project/add.rs @@ -441,7 +441,7 @@ pub(crate) async fn add( ) .await { - Ok(lock) => lock, + Ok(result) => result.into_lock(), Err(ProjectError::Operation(pip::operations::Error::Resolve( uv_resolver::ResolveError::NoSolution(err), ))) => { @@ -463,8 +463,8 @@ pub(crate) async fn add( if !raw_sources { // Extract the minimum-supported version for each dependency. let mut minimum_version = - FxHashMap::with_capacity_and_hasher(lock.lock.packages().len(), FxBuildHasher); - for dist in lock.lock.packages() { + FxHashMap::with_capacity_and_hasher(lock.packages().len(), FxBuildHasher); + for dist in lock.packages() { let name = dist.name(); let version = dist.version(); match minimum_version.entry(name) { @@ -563,7 +563,7 @@ pub(crate) async fn add( project::sync::do_sync( &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 0c64a2f6c..21eb37a1e 100644 --- a/crates/uv/src/commands/project/lock.rs +++ b/crates/uv/src/commands/project/lock.rs @@ -9,13 +9,16 @@ use rustc_hash::{FxBuildHasher, FxHashMap}; use tracing::debug; use distribution_types::{ - FlatIndexLocation, IndexUrl, UnresolvedRequirementSpecification, UrlString, + FlatIndexLocation, IndexLocations, IndexUrl, UnresolvedRequirementSpecification, UrlString, }; use pep440_rs::Version; +use pypi_types::Requirement; use uv_auth::store_credentials_from_url; use uv_cache::Cache; use uv_client::{Connectivity, FlatIndexClient, RegistryClientBuilder}; -use uv_configuration::{Concurrency, ExtrasSpecification, PreviewMode, Reinstall, SetupPyStrategy}; +use uv_configuration::{ + Concurrency, ExtrasSpecification, PreviewMode, Reinstall, SetupPyStrategy, Upgrade, +}; use uv_dispatch::BuildDispatch; use uv_distribution::DistributionDatabase; use uv_fs::CWD; @@ -25,10 +28,10 @@ use uv_python::{Interpreter, PythonDownloads, PythonEnvironment, PythonPreferenc use uv_requirements::upgrade::{read_lock_requirements, LockedRequirements}; use uv_requirements::NamedRequirementsResolver; use uv_resolver::{ - FlatIndex, Lock, OptionsBuilder, PythonRequirement, RequiresPython, ResolverManifest, + FlatIndex, Lock, Options, OptionsBuilder, PythonRequirement, RequiresPython, ResolverManifest, ResolverMarkers, }; -use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy}; +use uv_types::{BuildContext, BuildIsolation, EmptyInstalledPackages, HashStrategy}; use uv_warnings::{warn_user, warn_user_once}; use uv_workspace::{DiscoveryOptions, Workspace}; @@ -41,11 +44,27 @@ 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, +pub(crate) enum LockResult { + /// The lock was unchanged. + Unchanged(Lock), + /// The lock was changed. + Changed(Option, Lock), +} + +impl LockResult { + pub(crate) fn lock(&self) -> &Lock { + match self { + LockResult::Unchanged(lock) => lock, + LockResult::Changed(_, lock) => lock, + } + } + + pub(crate) fn into_lock(self) -> Lock { + match self { + LockResult::Unchanged(lock) => lock, + LockResult::Changed(_, lock) => lock, + } + } } /// Resolve the project requirements into a lockfile. @@ -102,8 +121,8 @@ pub(crate) async fn lock( .await { Ok(lock) => { - if let Some(previous) = lock.previous.as_ref() { - report_upgrades(previous, &lock.lock, printer)?; + if let LockResult::Changed(Some(previous), lock) = &lock { + report_upgrades(previous, lock, printer)?; } Ok(ExitStatus::Success) } @@ -149,10 +168,7 @@ pub(super) async fn do_safe_lock( let existing = read(workspace) .await? .ok_or_else(|| ProjectError::MissingLockfile)?; - Ok(LockResult { - previous: None, - lock: existing, - }) + Ok(LockResult::Unchanged(existing)) } else if locked { // Read the existing lockfile. let existing = read(workspace) @@ -160,10 +176,10 @@ pub(super) async fn do_safe_lock( .ok_or_else(|| ProjectError::MissingLockfile)?; // Perform the lock operation, but don't write the lockfile to disk. - let lock = do_lock( + let result = do_lock( workspace, interpreter, - Some(&existing), + Some(existing), settings, &state, logger, @@ -176,24 +192,21 @@ pub(super) async fn do_safe_lock( ) .await?; - // If the locks disagree, return an error. - if lock != existing { + // If the lockfile changed, return an error. + if matches!(result, LockResult::Changed(_, _)) { return Err(ProjectError::LockMismatch); } - Ok(LockResult { - previous: Some(existing), - lock, - }) + Ok(result) } else { // Read the existing lockfile. let existing = read(workspace).await?; // Perform the lock operation. - let lock = do_lock( + let result = do_lock( workspace, interpreter, - existing.as_ref(), + existing, settings, &state, logger, @@ -206,14 +219,12 @@ pub(super) async fn do_safe_lock( ) .await?; - if !existing.as_ref().is_some_and(|existing| *existing == lock) { - commit(&lock, workspace).await?; + // If the lockfile changed, write it to disk. + if let LockResult::Changed(_, lock) = &result { + commit(lock, workspace).await?; } - Ok(LockResult { - previous: existing, - lock, - }) + Ok(result) } } @@ -221,7 +232,7 @@ pub(super) async fn do_safe_lock( async fn do_lock( workspace: &Workspace, interpreter: &Interpreter, - existing_lock: Option<&Lock>, + existing_lock: Option, settings: ResolverSettingsRef<'_>, state: &SharedState, logger: Box, @@ -231,7 +242,9 @@ async fn do_lock( native_tls: bool, cache: &Cache, printer: Printer, -) -> Result { +) -> Result { + let start = std::time::Instant::now(); + // Extract the project settings. let ResolverSettingsRef { index_locations, @@ -392,198 +405,74 @@ async fn do_lock( .await?; // If any of the resolution-determining settings changed, invalidate the lock. - let existing_lock = existing_lock.filter(|lock| { - if lock.resolution_mode() != options.resolution_mode { - let _ = writeln!( - printer.stderr(), - "Ignoring existing lockfile due to change in resolution mode: `{}` vs. `{}`", - lock.resolution_mode().cyan(), - options.resolution_mode.cyan() - ); - return false; - } - if lock.prerelease_mode() != options.prerelease_mode { - let _ = writeln!( - printer.stderr(), - "Ignoring existing lockfile due to change in pre-release mode: `{}` vs. `{}`", - lock.prerelease_mode().cyan(), - options.prerelease_mode.cyan() - ); - return false; - } - match (lock.exclude_newer(), options.exclude_newer) { - (None, None) => (), - (Some(existing), Some(provided)) if existing == provided => (), - (Some(existing), Some(provided)) => { - let _ = writeln!( - printer.stderr(), - "Ignoring existing lockfile due to change in timestamp cutoff: `{}` vs. `{}`", - existing.cyan(), - provided.cyan() - ); - return false; - } - (Some(existing), None) => { - let _ = writeln!( - printer.stderr(), - "Ignoring existing lockfile due to removal of timestamp cutoff: `{}`", - existing.cyan(), - ); - return false; - } - (None, Some(provided)) => { - let _ = writeln!( - printer.stderr(), - "Ignoring existing lockfile due to addition of timestamp cutoff: `{}`", - provided.cyan() - ); - return false; - } - } - true - }); - - // If an existing lockfile exists, build up a set of preferences. - let LockedRequirements { preferences, git } = existing_lock - .as_ref() - .map(|lock| read_lock_requirements(lock, upgrade)) - .unwrap_or_default(); - - // Populate the Git resolver. - for ResolvedRepositoryReference { reference, sha } in git { - debug!("Inserting Git reference into resolver: `{reference:?}` at `{sha}`"); - state.git.insert(reference, sha); - } - - let start = std::time::Instant::now(); - - let existing_lock = existing_lock.filter(|lock| { - match (lock.requires_python(), requires_python) { - // If the Requires-Python bound in the lockfile is weaker or equivalent to the - // Requires-Python bound in the workspace, we should have the necessary wheels to perform - // a locked resolution. - (None, _) => true, - (Some(locked), specified) => { - if locked.bound() == specified.bound() { - true - } else { - // On the other hand, if the bound in the lockfile is stricter, meaning the - // bound has since been weakened, we have to perform a clean resolution to ensure - // we fetch the necessary wheels. - debug!("Ignoring existing lockfile due to change in `requires-python`"); - false - } - } - } - }); - - // When we run the same resolution from the lockfile again, we could get a different result the - // second time due to the preferences causing us to skip a fork point (see - // "preferences-dependent-forking" packse scenario). To avoid this, we store the forks in the - // lockfile. We read those after all the lockfile filters, to allow the forks to change when - // the environment changed, e.g. the python bound check above can lead to different forking. - let resolver_markers = ResolverMarkers::universal(if upgrade.is_all() { - // We're discarding all preferences, so we're also discarding the existing forks. - vec![] - } else { - existing_lock - .map(|lock| lock.fork_markers().to_vec()) - .unwrap_or_default() - }); - - // If any upgrades are specified, don't use the existing lockfile. - let existing_lock = existing_lock.filter(|_| { - debug!("Ignoring existing lockfile due to `--upgrade`"); - upgrade.is_none() - }); - - // If the user provided at least one index URL (from the command line, or from a configuration - // file), don't use the existing lockfile if it references any registries that are no longer - // included in the current configuration. - let existing_lock = existing_lock.filter(|lock| { - // If _no_ indexes were provided, we assume that the user wants to reuse the existing - // distributions, even though a failure to reuse the lockfile will result in re-resolving - // against PyPI by default. - if settings.index_locations.is_none() { - return true; - } - - // Collect the set of available indexes (both `--index-url` and `--find-links` entries). - let indexes = settings - .index_locations - .indexes() - .map(IndexUrl::redacted) - .chain( - settings - .index_locations - .flat_index() - .map(FlatIndexLocation::redacted), + let existing_lock = if let Some(existing_lock) = existing_lock { + Some( + ValidatedLock::validate( + existing_lock, + workspace, + &members, + &constraints, + &overrides, + interpreter, + &requires_python, + index_locations, + upgrade, + &options, + &database, + printer, ) - .map(UrlString::from) - .collect::>(); - - // Find any packages in the lockfile that reference a registry that is no longer included in - // the current configuration. - for package in lock.packages() { - let Some(index) = package.index() else { - continue; - }; - if !indexes.contains(index) { - let _ = writeln!( - printer.stderr(), - "Ignoring existing lockfile due to removal of referenced registry: {index}" - ); - return false; - } - } - - true - }); - - let existing_lock = match existing_lock { - None => None, - - // Try to resolve using metadata in the lockfile. - // - // When resolving from the lockfile we can still download and install new distributions, - // but we rely on the lockfile for the metadata of any existing distributions. If we have - // any outdated metadata we fall back to a clean resolve. - Some(lock) => { - if lock - .satisfies( - workspace, - &members, - &constraints, - &overrides, - interpreter.tags()?, - &database, - ) - .await? - { - debug!("Existing `uv.lock` satisfies workspace requirements"); - Some(lock) - } else { - debug!("Existing `uv.lock` does not satisfy workspace requirements; ignoring..."); - None - } - } + .await?, + ) + } else { + None }; match existing_lock { // Resolution from the lockfile succeeded. - Some(lock) => { + Some(ValidatedLock::Satisfies(lock)) => { // Print the success message after completing resolution. logger.on_complete(lock.len(), start, printer)?; - // TODO(charlie): Avoid cloning here. - Ok(lock.clone()) + Ok(LockResult::Unchanged(lock)) } // The lockfile did not contain enough information to obtain a resolution, fallback // to a fresh resolve. - None => { + _ => { debug!("Starting clean resolution"); + // If an existing lockfile exists, build up a set of preferences. + let LockedRequirements { preferences, git } = existing_lock + .as_ref() + .and_then(|lock| match &lock { + ValidatedLock::Preferable(lock) => Some(lock), + ValidatedLock::Satisfies(lock) => Some(lock), + ValidatedLock::Unusable(_) => None, + }) + .map(|lock| read_lock_requirements(lock, upgrade)) + .unwrap_or_default(); + + // Populate the Git resolver. + for ResolvedRepositoryReference { reference, sha } in git { + debug!("Inserting Git reference into resolver: `{reference:?}` at `{sha}`"); + state.git.insert(reference, sha); + } + + // When we run the same resolution from the lockfile again, we could get a different result the + // second time due to the preferences causing us to skip a fork point (see + // "preferences-dependent-forking" packse scenario). To avoid this, we store the forks in the + // lockfile. We read those after all the lockfile filters, to allow the forks to change when + // the environment changed, e.g. the python bound check above can lead to different forking. + let resolver_markers = ResolverMarkers::universal(if upgrade.is_all() { + // We're discarding all preferences, so we're also discarding the existing forks. + vec![] + } else { + existing_lock + .as_ref() + .map(|existing_lock| existing_lock.lock().fork_markers().to_vec()) + .unwrap_or_default() + }); + // Resolve the requirements. let resolution = pip::operations::resolve( requirements, @@ -624,13 +513,187 @@ async fn do_lock( // Notify the user of any resolution diagnostics. pip::operations::diagnose_resolution(resolution.diagnostics(), printer)?; - Ok( - Lock::from_resolution_graph(&resolution)?.with_manifest(ResolverManifest::new( - members, - constraints, - overrides, - )), + let previous = existing_lock.map(ValidatedLock::into_lock); + let lock = Lock::from_resolution_graph(&resolution)? + .with_manifest(ResolverManifest::new(members, constraints, overrides)); + + Ok(LockResult::Changed(previous, lock)) + } + } +} + +#[derive(Debug)] +enum ValidatedLock { + /// An existing lockfile was provided, but its contents should be ignored. + Unusable(Lock), + /// An existing lockfile was provided, and it satisfies the workspace requirements. + Satisfies(Lock), + /// An existing lockfile was provided, and the locked versions should be preferred if possible, + /// even though the lockfile does not satisfy the workspace requirements. + Preferable(Lock), +} + +impl ValidatedLock { + /// Validate a [`Lock`] against the workspace requirements. + async fn validate( + lock: Lock, + workspace: &Workspace, + members: &[PackageName], + constraints: &[Requirement], + overrides: &[Requirement], + interpreter: &Interpreter, + requires_python: &RequiresPython, + index_locations: &IndexLocations, + upgrade: &Upgrade, + options: &Options, + database: &DistributionDatabase<'_, Context>, + printer: Printer, + ) -> Result { + // Start with the most severe condition: a fundamental option changed between resolutions. + if lock.resolution_mode() != options.resolution_mode { + let _ = writeln!( + printer.stderr(), + "Ignoring existing lockfile due to change in resolution mode: `{}` vs. `{}`", + lock.resolution_mode().cyan(), + options.resolution_mode.cyan() + ); + return Ok(Self::Unusable(lock)); + } + if lock.prerelease_mode() != options.prerelease_mode { + let _ = writeln!( + printer.stderr(), + "Ignoring existing lockfile due to change in pre-release mode: `{}` vs. `{}`", + lock.prerelease_mode().cyan(), + options.prerelease_mode.cyan() + ); + return Ok(Self::Unusable(lock)); + } + match (lock.exclude_newer(), options.exclude_newer) { + (None, None) => (), + (Some(existing), Some(provided)) if existing == provided => (), + (Some(existing), Some(provided)) => { + let _ = writeln!( + printer.stderr(), + "Ignoring existing lockfile due to change in timestamp cutoff: `{}` vs. `{}`", + existing.cyan(), + provided.cyan() + ); + return Ok(Self::Unusable(lock)); + } + (Some(existing), None) => { + let _ = writeln!( + printer.stderr(), + "Ignoring existing lockfile due to removal of timestamp cutoff: `{}`", + existing.cyan(), + ); + return Ok(Self::Unusable(lock)); + } + (None, Some(provided)) => { + let _ = writeln!( + printer.stderr(), + "Ignoring existing lockfile due to addition of timestamp cutoff: `{}`", + provided.cyan() + ); + return Ok(Self::Unusable(lock)); + } + } + + // If the user specified `--upgrade`, then at best we can prefer some of the existing + // versions. + if !upgrade.is_none() { + debug!("Ignoring existing lockfile due to `--upgrade`"); + return Ok(Self::Preferable(lock)); + } + + // If the Requires-Python bound in the lockfile is weaker or equivalent to the + // Requires-Python bound in the workspace, we should have the necessary wheels to perform + // a locked resolution. + if let Some(locked) = lock.requires_python() { + if locked.bound() != requires_python.bound() { + // On the other hand, if the bound in the lockfile is stricter, meaning the + // bound has since been weakened, we have to perform a clean resolution to ensure + // we fetch the necessary wheels. + debug!("Ignoring existing lockfile due to change in `requires-python`"); + + // It's fine to prefer the existing versions, though. + return Ok(Self::Preferable(lock)); + } + } + + // If the user provided at least one index URL (from the command line, or from a configuration + // file), don't use the existing lockfile if it references any registries that are no longer + // included in the current configuration. + // + // However, iIf _no_ indexes were provided, we assume that the user wants to reuse the existing + // distributions, even though a failure to reuse the lockfile will result in re-resolving + // against PyPI by default. + if !index_locations.is_none() { + // Collect the set of available indexes (both `--index-url` and `--find-links` entries). + let indexes = index_locations + .indexes() + .map(IndexUrl::redacted) + .chain( + index_locations + .flat_index() + .map(FlatIndexLocation::redacted), + ) + .map(UrlString::from) + .collect::>(); + + // Find any packages in the lockfile that reference a registry that is no longer included in + // the current configuration. + for package in lock.packages() { + let Some(index) = package.index() else { + continue; + }; + if !indexes.contains(index) { + let _ = writeln!( + printer.stderr(), + "Ignoring existing lockfile due to removal of referenced registry: {index}" + ); + + // It's fine to prefer the existing versions, though. + return Ok(Self::Preferable(lock)); + } + } + } + + // Determine whether the lockfile satisfies the workspace requirements. + if lock + .satisfies( + workspace, + members, + constraints, + overrides, + interpreter.tags()?, + database, ) + .await? + { + debug!("Existing `uv.lock` satisfies workspace requirements"); + Ok(Self::Satisfies(lock)) + } else { + debug!("Existing `uv.lock` does not satisfy workspace requirements; ignoring..."); + Ok(Self::Preferable(lock)) + } + } + + /// Return the inner [`Lock`]. + fn lock(&self) -> &Lock { + match self { + ValidatedLock::Unusable(lock) => lock, + ValidatedLock::Satisfies(lock) => lock, + ValidatedLock::Preferable(lock) => lock, + } + } + + /// Convert the [`ValidatedLock`] into a [`Lock`]. + #[must_use] + fn into_lock(self) -> Lock { + match self { + ValidatedLock::Unusable(lock) => lock, + ValidatedLock::Satisfies(lock) => lock, + ValidatedLock::Preferable(lock) => lock, } } } diff --git a/crates/uv/src/commands/project/remove.rs b/crates/uv/src/commands/project/remove.rs index a8641edba..f9ac64fff 100644 --- a/crates/uv/src/commands/project/remove.rs +++ b/crates/uv/src/commands/project/remove.rs @@ -174,7 +174,8 @@ pub(crate) async fn remove( cache, printer, ) - .await?; + .await? + .into_lock(); if no_sync { return Ok(ExitStatus::Success); @@ -191,7 +192,7 @@ pub(crate) async fn remove( project::sync::do_sync( &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 e736a9fef..24239e3f0 100644 --- a/crates/uv/src/commands/project/run.rs +++ b/crates/uv/src/commands/project/run.rs @@ -36,7 +36,7 @@ use crate::commands::pip::operations::Modifications; use crate::commands::project::environment::CachedEnvironment; use crate::commands::project::{ProjectError, WorkspacePython}; use crate::commands::reporters::PythonDownloadReporter; -use crate::commands::{pip, project, ExitStatus, SharedState}; +use crate::commands::{project, ExitStatus, SharedState}; use crate::printer::Printer; use crate::settings::ResolverInstallerSettings; @@ -387,7 +387,7 @@ pub(crate) async fn run( .await? }; - let lock = match project::lock::do_safe_lock( + let result = match project::lock::do_safe_lock( locked, frozen, project.workspace(), @@ -407,8 +407,8 @@ pub(crate) async fn run( ) .await { - Ok(lock) => lock, - Err(ProjectError::Operation(pip::operations::Error::Resolve( + Ok(result) => result, + Err(ProjectError::Operation(operations::Error::Resolve( uv_resolver::ResolveError::NoSolution(err), ))) => { let report = miette::Report::msg(format!("{err}")).context(err.header()); @@ -421,7 +421,7 @@ pub(crate) async fn run( project::sync::do_sync( &project, &venv, - &lock.lock, + result.lock(), &extras, dev, Modifications::Sufficient, diff --git a/crates/uv/src/commands/project/sync.rs b/crates/uv/src/commands/project/sync.rs index 4f92f92d8..e8c017f61 100644 --- a/crates/uv/src/commands/project/sync.rs +++ b/crates/uv/src/commands/project/sync.rs @@ -89,7 +89,7 @@ pub(crate) async fn sync( ) .await { - Ok(lock) => lock, + Ok(result) => result.into_lock(), Err(ProjectError::Operation(pip::operations::Error::Resolve( uv_resolver::ResolveError::NoSolution(err), ))) => { @@ -107,7 +107,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 61b54d8b9..1fb9f7c89 100644 --- a/crates/uv/src/commands/project/tree.rs +++ b/crates/uv/src/commands/project/tree.rs @@ -79,7 +79,8 @@ pub(crate) async fn tree( cache, printer, ) - .await?; + .await? + .into_lock(); // Apply the platform tags to the markers. let markers = match (python_platform, python_version) { @@ -93,7 +94,7 @@ pub(crate) async fn tree( // Render the tree. let tree = TreeDisplay::new( - &lock.lock, + &lock, (!universal).then(|| markers.as_ref()), depth.into(), prune, diff --git a/crates/uv/tests/lock_scenarios.rs b/crates/uv/tests/lock_scenarios.rs index 66e6857af..af8da27c7 100644 --- a/crates/uv/tests/lock_scenarios.rs +++ b/crates/uv/tests/lock_scenarios.rs @@ -128,19 +128,6 @@ fn fork_allows_non_conflicting_non_overlapping_dependencies() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -254,19 +241,6 @@ fn fork_allows_non_conflicting_repeated_dependencies() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -388,19 +362,6 @@ fn fork_basic() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -741,19 +702,6 @@ fn fork_filter_sibling_dependencies() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -869,19 +817,6 @@ fn fork_upgrade() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -1038,19 +973,6 @@ fn fork_incomplete_markers() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -1186,19 +1108,6 @@ fn fork_marker_accrue() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -1444,19 +1353,6 @@ fn fork_marker_inherit_combined_allowed() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -1626,19 +1522,6 @@ fn fork_marker_inherit_combined_disallowed() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -1809,19 +1692,6 @@ fn fork_marker_inherit_combined() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -1965,19 +1835,6 @@ fn fork_marker_inherit_isolated() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -2139,19 +1996,6 @@ fn fork_marker_inherit_transitive() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -2285,19 +2129,6 @@ fn fork_marker_inherit() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -2460,19 +2291,6 @@ fn fork_marker_limited_inherit() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -2617,19 +2435,6 @@ fn fork_marker_selection() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -2798,19 +2603,6 @@ fn fork_marker_track() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -2945,19 +2737,6 @@ fn fork_non_fork_marker_transitive() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -3241,19 +3020,6 @@ fn fork_overlapping_markers_basic() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -3495,19 +3261,6 @@ fn preferences_dependent_forking_bistable() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -3936,19 +3689,6 @@ fn preferences_dependent_forking_tristable() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -4151,19 +3891,6 @@ fn preferences_dependent_forking() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -4347,19 +4074,6 @@ fn fork_remaining_universe_partitioning() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -4445,19 +4159,6 @@ fn fork_requires_python_full_prerelease() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -4543,19 +4244,6 @@ fn fork_requires_python_full() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -4657,19 +4345,6 @@ fn fork_requires_python_patch_overlap() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } @@ -4752,18 +4427,5 @@ fn fork_requires_python() -> Result<()> { .assert() .success(); - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); - Ok(()) } diff --git a/scripts/scenarios/templates/lock.mustache b/scripts/scenarios/templates/lock.mustache index 01c33e0aa..d720901f4 100644 --- a/scripts/scenarios/templates/lock.mustache +++ b/scripts/scenarios/templates/lock.mustache @@ -80,19 +80,6 @@ fn {{module_name}}() -> Result<()> { .arg(packse_index_url()) .assert() .success(); - - // Assert the idempotence of `uv lock` when resolving with the lockfile preferences, - // by upgrading an irrelevant package. - context - .lock() - .arg("--locked") - .arg("--upgrade-package") - .arg("packse") - .env_remove("UV_EXCLUDE_NEWER") - .arg("--index-url") - .arg(packse_index_url()) - .assert() - .success(); {{/expected.satisfiable}} Ok(())