Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invalidate uv.lock if registry sources are removed #6026

Merged
merged 1 commit into from
Aug 13, 2024
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
7 changes: 7 additions & 0 deletions crates/distribution-types/src/file.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::fmt::{self, Display, Formatter};
use std::path::PathBuf;
use std::str::FromStr;
Expand Down Expand Up @@ -197,6 +198,12 @@ impl From<&Url> for UrlString {
}
}

impl From<Cow<'_, Url>> for UrlString {
fn from(value: Cow<'_, Url>) -> Self {
UrlString(value.to_string())
}
}

impl From<VerbatimUrl> for UrlString {
fn from(value: VerbatimUrl) -> Self {
UrlString(value.raw().to_string())
Expand Down
9 changes: 9 additions & 0 deletions crates/distribution-types/src/index_url.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,15 @@ impl IndexLocations {
no_index: self.no_index || no_index,
}
}

/// Returns `true` if no index configuration is set, i.e., the [`IndexLocations`] matches the
/// default configuration.
pub fn is_none(&self) -> bool {
self.index.is_none()
&& self.extra_index.is_empty()
&& self.flat_index.is_empty()
&& !self.no_index
}
}

impl<'a> IndexLocations {
Expand Down
9 changes: 9 additions & 0 deletions crates/uv-resolver/src/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1210,10 +1210,19 @@ impl Package {
&self.id.version
}

/// Return the fork markers for this package, if any.
pub fn fork_markers(&self) -> Option<&BTreeSet<MarkerTree>> {
self.fork_markers.as_ref()
}

/// Return the index URL for this package, if it is a registry source.
pub fn index(&self) -> Option<&UrlString> {
match &self.id.source {
Source::Registry(url) => Some(url),
_ => None,
}
}

/// Returns a [`VersionId`] for this package that can be used for resolution.
fn version_id(&self, workspace_root: &Path) -> Result<VersionId, LockError> {
match &self.id.source {
Expand Down
52 changes: 50 additions & 2 deletions crates/uv/src/commands/project/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ use owo_colors::OwoColorize;
use rustc_hash::{FxBuildHasher, FxHashMap};
use tracing::debug;

use distribution_types::{Diagnostic, UnresolvedRequirementSpecification};
use distribution_types::{
Diagnostic, FlatIndexLocation, IndexUrl, UnresolvedRequirementSpecification, UrlString,
};
use pep440_rs::Version;
use uv_auth::store_credentials_from_url;
use uv_cache::Cache;
Expand Down Expand Up @@ -427,7 +429,53 @@ async fn do_lock(
existing_lock.and_then(|lock| lock.fork_markers().clone())
});

let resolution = match existing_lock.filter(|_| upgrade.is_none()) {
// If any upgrades are specified, don't use the existing lockfile.
let existing_lock = existing_lock.filter(|_| 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),
)
.map(UrlString::from)
.collect::<BTreeSet<_>>();

// 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 resolution = match existing_lock {
None => None,

// Try to resolve using metadata in the lockfile.
Expand Down
118 changes: 118 additions & 0 deletions crates/uv/tests/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7652,3 +7652,121 @@ fn unconditional_overlapping_marker_disjoint_version_constraints() -> Result<()>

Ok(())
}

/// Change indexes between locking operations.
#[test]
fn lock_change_index() -> 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 = ["iniconfig"]
"#,
)?;

uv_snapshot!(context.filters(), context.lock().arg("--index-url").arg("https://public:heron@pypi-proxy.fly.dev/basic-auth/simple"), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
warning: `uv lock` is experimental and may change without warning
Resolved 2 packages in [TIME]
"###);

let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();

insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"

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

[[package]]
name = "iniconfig"
version = "2.0.0"
source = { registry = "https://pypi-proxy.fly.dev/basic-auth/simple" }
sdist = { url = "https://pypi-proxy.fly.dev/basic-auth/files/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
wheels = [
{ url = "https://pypi-proxy.fly.dev/basic-auth/files/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
]

[[package]]
name = "project"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "iniconfig" },
]
"###
);
});

// Re-run against PyPI.
uv_snapshot!(context.filters(), context.lock().arg("--index-url").arg("https://pypi.org/simple"), @r###"
success: true
exit_code: 0
----- stdout -----

----- stderr -----
warning: `uv lock` is experimental and may change without warning
Ignoring existing lockfile due to removal of referenced registry: https://pypi-proxy.fly.dev/basic-auth/simple
Resolved 2 packages in [TIME]
"###);

let lock = fs_err::read_to_string(context.temp_dir.join("uv.lock")).unwrap();

insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(
lock, @r###"
version = 1
requires-python = ">=3.12"

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

[[package]]
name = "iniconfig"
version = "2.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 },
]

[[package]]
name = "project"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "iniconfig" },
]
"###
);
});

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

----- stderr -----
warning: `uv lock` is experimental and may change without warning
Resolved 2 packages in [TIME]
"###);

Ok(())
}
Loading
Loading