Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion crates/uv/src/commands/project/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use owo_colors::OwoColorize;
use rustc_hash::{FxBuildHasher, FxHashMap};
use tracing::debug;

use uv_cache::Cache;
use uv_cache::{Cache, Refresh};
use uv_client::{BaseClientBuilder, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
Concurrency, Constraints, DependencyGroupsWithDefaults, DryRun, ExtrasSpecification, Reinstall,
Expand Down Expand Up @@ -84,6 +84,7 @@ pub(crate) async fn lock(
locked: bool,
frozen: bool,
dry_run: DryRun,
refresh: Refresh,
python: Option<String>,
install_mirrors: PythonInstallMirrors,
settings: ResolverSettings,
Expand Down Expand Up @@ -201,6 +202,7 @@ pub(crate) async fn lock(
printer,
preview,
)
.with_refresh(&refresh)
.execute(target)
.await
{
Expand Down Expand Up @@ -260,6 +262,7 @@ pub(super) enum LockMode<'env> {
pub(super) struct LockOperation<'env> {
mode: LockMode<'env>,
constraints: Vec<NameRequirementSpecification>,
refresh: Option<&'env Refresh>,
settings: &'env ResolverSettings,
client_builder: &'env BaseClientBuilder<'env>,
state: &'env UniversalState,
Expand Down Expand Up @@ -288,6 +291,7 @@ impl<'env> LockOperation<'env> {
Self {
mode,
constraints: vec![],
refresh: None,
settings,
client_builder,
state,
Expand All @@ -310,6 +314,13 @@ impl<'env> LockOperation<'env> {
self
}

/// Set the refresh strategy for the [`LockOperation`].
#[must_use]
pub(super) fn with_refresh(mut self, refresh: &'env Refresh) -> Self {
self.refresh = Some(refresh);
self
}

/// Perform a [`LockOperation`].
pub(super) async fn execute(self, target: LockTarget<'_>) -> Result<LockResult, ProjectError> {
match self.mode {
Expand All @@ -334,6 +345,7 @@ impl<'env> LockOperation<'env> {
interpreter,
Some(existing),
self.constraints,
self.refresh,
self.settings,
self.client_builder,
self.state,
Expand Down Expand Up @@ -376,6 +388,7 @@ impl<'env> LockOperation<'env> {
interpreter,
existing,
self.constraints,
self.refresh,
self.settings,
self.client_builder,
self.state,
Expand Down Expand Up @@ -407,6 +420,7 @@ async fn do_lock(
interpreter: &Interpreter,
existing_lock: Option<Lock>,
external: Vec<NameRequirementSpecification>,
refresh: Option<&Refresh>,
settings: &ResolverSettings,
client_builder: &BaseClientBuilder<'_>,
state: &UniversalState,
Expand Down Expand Up @@ -744,6 +758,7 @@ async fn do_lock(
&requires_python,
index_locations,
upgrade,
refresh,
&options,
&hasher,
state.index(),
Expand Down Expand Up @@ -958,6 +973,7 @@ impl ValidatedLock {
requires_python: &RequiresPython,
index_locations: &IndexLocations,
upgrade: &Upgrade,
refresh: Option<&Refresh>,
options: &Options,
hasher: &HashStrategy,
index: &InMemoryIndex,
Expand Down Expand Up @@ -1143,6 +1159,12 @@ impl ValidatedLock {
return Ok(Self::Versions(lock));
}

// If the user specified `--refresh`, then we have to re-resolve.
if matches!(refresh, Some(Refresh::All(..) | Refresh::Packages(..))) {
debug!("Resolving despite existing lockfile due to `--refresh`");
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.
Expand Down
2 changes: 2 additions & 0 deletions crates/uv/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1902,6 +1902,7 @@ async fn run_project(
// Initialize the cache.
let cache = cache.init()?.with_refresh(
args.refresh
.clone()
.combine(Refresh::from(args.settings.upgrade.clone())),
);

Expand All @@ -1923,6 +1924,7 @@ async fn run_project(
args.locked,
args.frozen,
args.dry_run,
args.refresh,
args.python,
args.install_mirrors,
args.settings,
Expand Down
215 changes: 215 additions & 0 deletions crates/uv/tests/it/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31658,6 +31658,221 @@ fn lock_required_intersection() -> Result<()> {
Ok(())
}

#[test]
fn lock_refresh() -> Result<()> {
let context = TestContext::new("3.12");

let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"
[project]
name = "project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["anyio==3.7.0"]
"#,
)?;

uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 4 packages in [TIME]
"###);

// Write a `uv.lock` that accidentally omits the `anyio` wheel.
context.temp_dir.child("uv.lock").write_str(
r#"
version = 1
revision = 3
requires-python = ">=3.12"

[options]
exclude-newer = "2024-03-25T00:00:00Z"

[[package]]
name = "anyio"
version = "3.7.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c6/b3/fefbf7e78ab3b805dec67d698dc18dd505af7a18a8dd08868c9b4fa736b5/anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce", size = 142737, upload-time = "2023-05-27T11:12:46.688Z" }

[[package]]
name = "idna"
version = "3.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" },
]

[[package]]
name = "project"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "anyio" },
]

[package.metadata]
requires-dist = [{ name = "anyio", specifier = "==3.7.0" }]

[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
"#,
)?;

// Run `uv lock`.
uv_snapshot!(context.filters(), context.lock(), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 4 packages in [TIME]
"###);

let lock = context.read("uv.lock");

// The wheel should still be missing.
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r#"
version = 1
revision = 3
requires-python = ">=3.12"

[options]
exclude-newer = "2024-03-25T00:00:00Z"

[[package]]
name = "anyio"
version = "3.7.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c6/b3/fefbf7e78ab3b805dec67d698dc18dd505af7a18a8dd08868c9b4fa736b5/anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce", size = 142737, upload-time = "2023-05-27T11:12:46.688Z" }

[[package]]
name = "idna"
version = "3.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" },
]

[[package]]
name = "project"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "anyio" },
]

[package.metadata]
requires-dist = [{ name = "anyio", specifier = "==3.7.0" }]

[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
"#
);
});

// Re-run with `--refresh`.
uv_snapshot!(context.filters(), context.lock().arg("--refresh"), @r"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
Resolved 4 packages in [TIME]
");

let lock = context.read("uv.lock");

// The wheel should be present.
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r#"
version = 1
revision = 3
requires-python = ">=3.12"

[options]
exclude-newer = "2024-03-25T00:00:00Z"

[[package]]
name = "anyio"
version = "3.7.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c6/b3/fefbf7e78ab3b805dec67d698dc18dd505af7a18a8dd08868c9b4fa736b5/anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce", size = 142737, upload-time = "2023-05-27T11:12:46.688Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/68/fe/7ce1926952c8a403b35029e194555558514b365ad77d75125f521a2bec62/anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0", size = 80873, upload-time = "2023-05-27T11:12:44.474Z" },
]

[[package]]
name = "idna"
version = "3.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", size = 175426, upload-time = "2023-11-25T15:40:54.902Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/e7/a82b05cf63a603df6e68d59ae6a68bf5064484a0718ea5033660af4b54a9/idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f", size = 61567, upload-time = "2023-11-25T15:40:52.604Z" },
]

[[package]]
name = "project"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "anyio" },
]

[package.metadata]
requires-dist = [{ name = "anyio", specifier = "==3.7.0" }]

[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
"#
);
});

Ok(())
}

/// Ensure conflicts on virtual packages (such as markers) give good error messages.
#[test]
fn collapsed_error_with_marker_packages() -> Result<()> {
Expand Down
Loading