This commit is contained in:
Bhuminjay Soni 2025-10-04 23:08:11 +02:00 committed by GitHub
commit ec53b752ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 77 additions and 19 deletions

View file

@ -3728,6 +3728,10 @@ pub struct LockArgs {
#[arg(long, conflicts_with = "check_exists", conflicts_with = "check")] #[arg(long, conflicts_with = "check_exists", conflicts_with = "check")]
pub dry_run: bool, pub dry_run: bool,
/// Force rewrite
#[arg(long, conflicts_with = "dry_run")]
pub force: bool,
/// Lock the specified Python script, rather than the current project. /// Lock the specified Python script, rather than the current project.
/// ///
/// If provided, uv will lock the script (based on its inline metadata table, in adherence with /// If provided, uv will lock the script (based on its inline metadata table, in adherence with

View file

@ -980,7 +980,7 @@ async fn lock_and_sync(
if locked { if locked {
LockMode::Locked(target.interpreter()) LockMode::Locked(target.interpreter())
} else { } else {
LockMode::Write(target.interpreter()) LockMode::Write(target.interpreter(), false)
}, },
&settings.resolver, &settings.resolver,
client_builder, client_builder,
@ -1102,7 +1102,7 @@ async fn lock_and_sync(
if locked { if locked {
LockMode::Locked(target.interpreter()) LockMode::Locked(target.interpreter())
} else { } else {
LockMode::Write(target.interpreter()) LockMode::Write(target.interpreter(), false)
}, },
&settings.resolver, &settings.resolver,
client_builder, client_builder,

View file

@ -180,7 +180,7 @@ pub(crate) async fn export(
// 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())
} else { } else {
LockMode::Write(interpreter.as_ref().unwrap()) LockMode::Write(interpreter.as_ref().unwrap(), false)
}; };
// Initialize any shared state. // Initialize any shared state.

View file

@ -85,6 +85,7 @@ pub(crate) async fn lock(
frozen: bool, frozen: bool,
dry_run: DryRun, dry_run: DryRun,
refresh: Refresh, refresh: Refresh,
force: bool,
python: Option<String>, python: Option<String>,
install_mirrors: PythonInstallMirrors, install_mirrors: PythonInstallMirrors,
settings: ResolverSettings, settings: ResolverSettings,
@ -182,7 +183,7 @@ pub(crate) async fn lock(
} else if dry_run.enabled() { } else if dry_run.enabled() {
LockMode::DryRun(&interpreter) LockMode::DryRun(&interpreter)
} else { } else {
LockMode::Write(&interpreter) LockMode::Write(&interpreter, force)
} }
}; };
@ -253,7 +254,7 @@ pub(crate) async fn lock(
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub(super) enum LockMode<'env> { pub(super) enum LockMode<'env> {
/// Write the lockfile to disk. /// Write the lockfile to disk.
Write(&'env Interpreter), Write(&'env Interpreter, bool),
/// 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.
@ -359,6 +360,7 @@ impl<'env> LockOperation<'env> {
self.workspace_cache, self.workspace_cache,
self.printer, self.printer,
self.preview, self.preview,
false,
) )
.await?; .await?;
@ -372,7 +374,7 @@ impl<'env> LockOperation<'env> {
Ok(result) Ok(result)
} }
LockMode::Write(interpreter) | LockMode::DryRun(interpreter) => { LockMode::Write(interpreter, force) => {
// Read the existing lockfile. // Read the existing lockfile.
let existing = match target.read().await { let existing = match target.read().await {
Ok(Some(existing)) => Some(existing), Ok(Some(existing)) => Some(existing),
@ -402,15 +404,49 @@ impl<'env> LockOperation<'env> {
self.workspace_cache, self.workspace_cache,
self.printer, self.printer,
self.preview, self.preview,
force,
) )
.await?; .await?;
// If the lockfile changed, write it to disk.
if !matches!(self.mode, LockMode::DryRun(_)) {
if let LockResult::Changed(_, lock) = &result { if let LockResult::Changed(_, lock) = &result {
target.commit(lock).await?; target.commit(lock).await?;
} }
Ok(result)
} }
LockMode::DryRun(interpreter) => {
// Read the existing lockfile.
let existing = match target.read().await {
Ok(Some(existing)) => Some(existing),
Ok(None) => None,
Err(ProjectError::Lock(err)) => {
warn_user!(
"Failed to read existing lockfile; ignoring locked requirements: {err}"
);
None
}
Err(err) => return Err(err),
};
// Perform the lock operation, but don't write the lockfile to disk.
let result = do_lock(
target,
interpreter,
existing,
self.constraints,
self.refresh,
self.settings,
self.client_builder,
self.state,
self.logger,
self.concurrency,
self.cache,
self.workspace_cache,
self.printer,
self.preview,
false,
)
.await?;
Ok(result) Ok(result)
} }
@ -434,6 +470,7 @@ async fn do_lock(
workspace_cache: &WorkspaceCache, workspace_cache: &WorkspaceCache,
printer: Printer, printer: Printer,
preview: Preview, preview: Preview,
force: bool,
) -> Result<LockResult, ProjectError> { ) -> Result<LockResult, ProjectError> {
let start = std::time::Instant::now(); let start = std::time::Instant::now();
@ -767,6 +804,7 @@ async fn do_lock(
state.index(), state.index(),
&database, &database,
printer, printer,
force,
) )
.await .await
{ {
@ -986,11 +1024,16 @@ impl ValidatedLock {
index: &InMemoryIndex, index: &InMemoryIndex,
database: &DistributionDatabase<'_, Context>, database: &DistributionDatabase<'_, Context>,
printer: Printer, printer: Printer,
force: bool,
) -> Result<Self, ProjectError> { ) -> Result<Self, ProjectError> {
// Perform checks in a deliberate order, such that the most extreme conditions are tested // Perform checks in a deliberate order, such that the most extreme conditions are tested
// first (i.e., every check that returns `Self::Unusable`, followed by every check that // first (i.e., every check that returns `Self::Unusable`, followed by every check that
// returns `Self::Versions`, followed by every check that returns `Self::Preferable`, and // returns `Self::Versions`, followed by every check that returns `Self::Preferable`, and
// finally `Self::Satisfies`). // finally `Self::Satisfies`).
if force {
return Ok(Self::Preferable(lock));
}
// Start with the most severe condition: a fundamental option changed between resolutions.
if lock.resolution_mode() != options.resolution_mode { if lock.resolution_mode() != options.resolution_mode {
let _ = writeln!( let _ = writeln!(
printer.stderr(), printer.stderr(),

View file

@ -294,7 +294,7 @@ pub(crate) async fn remove(
let mode = if locked { let mode = if locked {
LockMode::Locked(target.interpreter()) LockMode::Locked(target.interpreter())
} else { } else {
LockMode::Write(target.interpreter()) LockMode::Write(target.interpreter(), false)
}; };
// Initialize any shared state. // Initialize any shared state.

View file

@ -266,7 +266,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
} else if locked { } else if locked {
LockMode::Locked(environment.interpreter()) LockMode::Locked(environment.interpreter())
} else { } else {
LockMode::Write(environment.interpreter()) LockMode::Write(environment.interpreter(), false)
}; };
// Generate a lockfile. // Generate a lockfile.
@ -744,7 +744,7 @@ hint: If you are running a script with `{}` in the shebang, you may need to incl
} else if isolated { } else if isolated {
LockMode::DryRun(venv.interpreter()) LockMode::DryRun(venv.interpreter())
} else { } else {
LockMode::Write(venv.interpreter()) LockMode::Write(venv.interpreter(), false)
}; };
let result = match project::lock::LockOperation::new( let result = match project::lock::LockOperation::new(

View file

@ -309,7 +309,7 @@ pub(crate) async fn sync(
} else if dry_run.enabled() { } else if dry_run.enabled() {
LockMode::DryRun(environment.interpreter()) LockMode::DryRun(environment.interpreter())
} else { } else {
LockMode::Write(environment.interpreter()) LockMode::Write(environment.interpreter(), false)
}; };
let lock_target = match &target { let lock_target = match &target {
@ -1272,7 +1272,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

@ -131,7 +131,7 @@ pub(crate) async fn tree(
// 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())
} else { } else {
LockMode::Write(interpreter.as_ref().unwrap()) LockMode::Write(interpreter.as_ref().unwrap(), false)
}; };
// Initialize any shared state. // Initialize any shared state.

View file

@ -582,7 +582,7 @@ async fn lock_and_sync(
let mode = if locked { let mode = if locked {
LockMode::Locked(target.interpreter()) LockMode::Locked(target.interpreter())
} else { } else {
LockMode::Write(target.interpreter()) LockMode::Write(target.interpreter(), false)
}; };
// Initialize any shared state. // Initialize any shared state.

View file

@ -1940,6 +1940,7 @@ async fn run_project(
args.frozen, args.frozen,
args.dry_run, args.dry_run,
args.refresh, args.refresh,
args.force,
args.python, args.python,
args.install_mirrors, args.install_mirrors,
args.settings, args.settings,

View file

@ -1381,6 +1381,7 @@ pub(crate) struct LockSettings {
pub(crate) locked: bool, pub(crate) locked: bool,
pub(crate) frozen: bool, pub(crate) frozen: bool,
pub(crate) dry_run: DryRun, pub(crate) dry_run: DryRun,
pub(crate) force: bool,
pub(crate) script: Option<PathBuf>, pub(crate) script: Option<PathBuf>,
pub(crate) python: Option<String>, pub(crate) python: Option<String>,
pub(crate) install_mirrors: PythonInstallMirrors, pub(crate) install_mirrors: PythonInstallMirrors,
@ -1400,6 +1401,7 @@ impl LockSettings {
check, check,
check_exists, check_exists,
dry_run, dry_run,
force,
script, script,
resolver, resolver,
build, build,
@ -1416,6 +1418,7 @@ impl LockSettings {
locked: check, locked: check,
frozen: check_exists, frozen: check_exists,
dry_run: DryRun::from_args(dry_run), dry_run: DryRun::from_args(dry_run),
force,
script, script,
python: python.and_then(Maybe::into_option), python: python.and_then(Maybe::into_option),
refresh: Refresh::from(refresh), refresh: Refresh::from(refresh),

View file

@ -9501,6 +9501,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
locked: false, locked: false,
frozen: false, frozen: false,
dry_run: Disabled, dry_run: Disabled,
force: false,
script: None, script: None,
python: None, python: None,
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
@ -9618,6 +9619,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
locked: false, locked: false,
frozen: false, frozen: false,
dry_run: Disabled, dry_run: Disabled,
force: false,
script: None, script: None,
python: None, python: None,
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
@ -9758,6 +9760,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
locked: false, locked: false,
frozen: false, frozen: false,
dry_run: Disabled, dry_run: Disabled,
force: false,
script: None, script: None,
python: None, python: None,
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
@ -9873,6 +9876,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
locked: false, locked: false,
frozen: false, frozen: false,
dry_run: Disabled, dry_run: Disabled,
force: false,
script: None, script: None,
python: None, python: None,
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
@ -9978,6 +9982,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
locked: false, locked: false,
frozen: false, frozen: false,
dry_run: Disabled, dry_run: Disabled,
force: false,
script: None, script: None,
python: None, python: None,
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {
@ -10084,6 +10089,7 @@ fn upgrade_project_cli_config_interaction() -> anyhow::Result<()> {
locked: false, locked: false,
frozen: false, frozen: false,
dry_run: Disabled, dry_run: Disabled,
force: false,
script: None, script: None,
python: None, python: None,
install_mirrors: PythonInstallMirrors { install_mirrors: PythonInstallMirrors {

View file

@ -1669,7 +1669,8 @@ uv lock [OPTIONS]
<p>May also be set with the <code>UV_EXTRA_INDEX_URL</code> environment variable.</p></dd><dt id="uv-lock--find-links"><a href="#uv-lock--find-links"><code>--find-links</code></a>, <code>-f</code> <i>find-links</i></dt><dd><p>Locations to search for candidate distributions, in addition to those found in the registry indexes.</p> <p>May also be set with the <code>UV_EXTRA_INDEX_URL</code> environment variable.</p></dd><dt id="uv-lock--find-links"><a href="#uv-lock--find-links"><code>--find-links</code></a>, <code>-f</code> <i>find-links</i></dt><dd><p>Locations to search for candidate distributions, in addition to those found in the registry indexes.</p>
<p>If a path, the target must be a directory that contains packages as wheel files (<code>.whl</code>) or source distributions (e.g., <code>.tar.gz</code> or <code>.zip</code>) at the top level.</p> <p>If a path, the target must be a directory that contains packages as wheel files (<code>.whl</code>) or source distributions (e.g., <code>.tar.gz</code> or <code>.zip</code>) at the top level.</p>
<p>If a URL, the page must contain a flat list of links to package files adhering to the formats described above.</p> <p>If a URL, the page must contain a flat list of links to package files adhering to the formats described above.</p>
<p>May also be set with the <code>UV_FIND_LINKS</code> environment variable.</p></dd><dt id="uv-lock--fork-strategy"><a href="#uv-lock--fork-strategy"><code>--fork-strategy</code></a> <i>fork-strategy</i></dt><dd><p>The strategy to use when selecting multiple versions of a given package across Python versions and platforms.</p> <p>May also be set with the <code>UV_FIND_LINKS</code> environment variable.</p></dd><dt id="uv-lock--force"><a href="#uv-lock--force"><code>--force</code></a></dt><dd><p>Force rewrite</p>
</dd><dt id="uv-lock--fork-strategy"><a href="#uv-lock--fork-strategy"><code>--fork-strategy</code></a> <i>fork-strategy</i></dt><dd><p>The strategy to use when selecting multiple versions of a given package across Python versions and platforms.</p>
<p>By default, uv will optimize for selecting the latest version of each package for each supported Python version (<code>requires-python</code>), while minimizing the number of selected versions across platforms.</p> <p>By default, uv will optimize for selecting the latest version of each package for each supported Python version (<code>requires-python</code>), while minimizing the number of selected versions across platforms.</p>
<p>Under <code>fewest</code>, uv will minimize the number of selected versions for each package, preferring older versions that are compatible with a wider range of supported Python versions or platforms.</p> <p>Under <code>fewest</code>, uv will minimize the number of selected versions for each package, preferring older versions that are compatible with a wider range of supported Python versions or platforms.</p>
<p>May also be set with the <code>UV_FORK_STRATEGY</code> environment variable.</p><p>Possible values:</p> <p>May also be set with the <code>UV_FORK_STRATEGY</code> environment variable.</p><p>Possible values:</p>