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")]
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.
///
/// 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 {
LockMode::Locked(target.interpreter())
} else {
LockMode::Write(target.interpreter())
LockMode::Write(target.interpreter(), false)
},
&settings.resolver,
client_builder,
@ -1102,7 +1102,7 @@ async fn lock_and_sync(
if locked {
LockMode::Locked(target.interpreter())
} else {
LockMode::Write(target.interpreter())
LockMode::Write(target.interpreter(), false)
},
&settings.resolver,
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.
LockMode::DryRun(interpreter.as_ref().unwrap())
} else {
LockMode::Write(interpreter.as_ref().unwrap())
LockMode::Write(interpreter.as_ref().unwrap(), false)
};
// Initialize any shared state.

View file

@ -85,6 +85,7 @@ pub(crate) async fn lock(
frozen: bool,
dry_run: DryRun,
refresh: Refresh,
force: bool,
python: Option<String>,
install_mirrors: PythonInstallMirrors,
settings: ResolverSettings,
@ -182,7 +183,7 @@ pub(crate) async fn lock(
} else if dry_run.enabled() {
LockMode::DryRun(&interpreter)
} else {
LockMode::Write(&interpreter)
LockMode::Write(&interpreter, force)
}
};
@ -253,7 +254,7 @@ pub(crate) async fn lock(
#[derive(Debug, Clone, Copy)]
pub(super) enum LockMode<'env> {
/// Write the lockfile to disk.
Write(&'env Interpreter),
Write(&'env Interpreter, bool),
/// Perform a resolution, but don't write the lockfile to disk.
DryRun(&'env Interpreter),
/// 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.printer,
self.preview,
false,
)
.await?;
@ -372,7 +374,7 @@ impl<'env> LockOperation<'env> {
Ok(result)
}
LockMode::Write(interpreter) | LockMode::DryRun(interpreter) => {
LockMode::Write(interpreter, force) => {
// Read the existing lockfile.
let existing = match target.read().await {
Ok(Some(existing)) => Some(existing),
@ -402,16 +404,50 @@ impl<'env> LockOperation<'env> {
self.workspace_cache,
self.printer,
self.preview,
force,
)
.await?;
// If the lockfile changed, write it to disk.
if !matches!(self.mode, LockMode::DryRun(_)) {
if let LockResult::Changed(_, lock) = &result {
target.commit(lock).await?;
}
if let LockResult::Changed(_, lock) = &result {
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)
}
}
@ -434,6 +470,7 @@ async fn do_lock(
workspace_cache: &WorkspaceCache,
printer: Printer,
preview: Preview,
force: bool,
) -> Result<LockResult, ProjectError> {
let start = std::time::Instant::now();
@ -767,6 +804,7 @@ async fn do_lock(
state.index(),
&database,
printer,
force,
)
.await
{
@ -986,11 +1024,16 @@ impl ValidatedLock {
index: &InMemoryIndex,
database: &DistributionDatabase<'_, Context>,
printer: Printer,
force: bool,
) -> Result<Self, ProjectError> {
// 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
// returns `Self::Versions`, followed by every check that returns `Self::Preferable`, and
// 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 {
let _ = writeln!(
printer.stderr(),

View file

@ -294,7 +294,7 @@ pub(crate) async fn remove(
let mode = if locked {
LockMode::Locked(target.interpreter())
} else {
LockMode::Write(target.interpreter())
LockMode::Write(target.interpreter(), false)
};
// 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 {
LockMode::Locked(environment.interpreter())
} else {
LockMode::Write(environment.interpreter())
LockMode::Write(environment.interpreter(), false)
};
// 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 {
LockMode::DryRun(venv.interpreter())
} else {
LockMode::Write(venv.interpreter())
LockMode::Write(venv.interpreter(), false)
};
let result = match project::lock::LockOperation::new(

View file

@ -309,7 +309,7 @@ pub(crate) async fn sync(
} else if dry_run.enabled() {
LockMode::DryRun(environment.interpreter())
} else {
LockMode::Write(environment.interpreter())
LockMode::Write(environment.interpreter(), false)
};
let lock_target = match &target {
@ -1272,7 +1272,7 @@ impl From<(&LockTarget<'_>, &LockMode<'_>, &Outcome)> for LockReport {
LockResult::Unchanged(..) => match mode {
// When `--frozen` is used, we don't check the lockfile
LockMode::Frozen => LockAction::Use,
LockMode::DryRun(_) | LockMode::Locked(_) | LockMode::Write(_) => {
LockMode::DryRun(_) | LockMode::Locked(_) | LockMode::Write(..) => {
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.
LockMode::DryRun(interpreter.as_ref().unwrap())
} else {
LockMode::Write(interpreter.as_ref().unwrap())
LockMode::Write(interpreter.as_ref().unwrap(), false)
};
// Initialize any shared state.

View file

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

View file

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

View file

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

View file

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