Skip to content

Commit

Permalink
Respect local freshness when auditing installed environment
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Mar 4, 2024
1 parent 6f94dc3 commit 1320e8a
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 7 deletions.
8 changes: 6 additions & 2 deletions crates/uv-installer/src/plan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,12 @@ impl<'a> Planner<'a> {
[distribution] => {
// Filter out already-installed packages.
match requirement.version_or_url.as_ref() {
// Accept any version of the package.
None => continue,

// If the requirement comes from a registry, check by name.
None | Some(VersionOrUrl::VersionSpecifier(_)) => {
if requirement.is_satisfied_by(distribution.version()) {
Some(VersionOrUrl::VersionSpecifier(version_specifier)) => {
if version_specifier.contains(distribution.version()) {
debug!("Requirement already satisfied: {distribution}");
continue;
}
Expand All @@ -196,6 +199,7 @@ impl<'a> Planner<'a> {
debug!("Requirement already satisfied (and up-to-date): {installed}");
continue;
}
debug!("Requirement already satisfied (but not up-to-date): {installed}");
} else {
// Otherwise, assume the requirement is up-to-date.
debug!("Requirement already satisfied (assumed up-to-date): {installed}");
Expand Down
52 changes: 49 additions & 3 deletions crates/uv-installer/src/site_packages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,30 @@ impl<'a> SitePackages<'a> {
[distribution] => {
// Validate that the installed version matches the requirement.
match &requirement.version_or_url {
None | Some(pep508_rs::VersionOrUrl::Url(_)) => {}
// Accept any installed version.
None => {}

// If the requirement comes from a URL, verify by URL.
Some(pep508_rs::VersionOrUrl::Url(url)) => {
let InstalledDist::Url(installed) = &distribution else {
return Ok(false);
};

if &installed.url != url.raw() {
return Ok(false);
}

// If the requirement came from a local path, check freshness.
if let Ok(archive) = url.to_file_path() {
if !ArchiveTimestamp::up_to_date_with(
&archive,
ArchiveTarget::Install(distribution),
)? {
return Ok(false);
}
}
}

Some(pep508_rs::VersionOrUrl::VersionSpecifier(version_specifier)) => {
// The installed version doesn't satisfy the requirement.
if !version_specifier.contains(distribution.version()) {
Expand All @@ -343,9 +366,32 @@ impl<'a> SitePackages<'a> {
}

match &constraint.version_or_url {
None | Some(pep508_rs::VersionOrUrl::Url(_)) => {}
// Accept any installed version.
None => {}

// If the requirement comes from a URL, verify by URL.
Some(pep508_rs::VersionOrUrl::Url(url)) => {
let InstalledDist::Url(installed) = &distribution else {
return Ok(false);
};

if &installed.url != url.raw() {
return Ok(false);
}

// If the requirement came from a local path, check freshness.
if let Ok(archive) = url.to_file_path() {
if !ArchiveTimestamp::up_to_date_with(
&archive,
ArchiveTarget::Install(distribution),
)? {
return Ok(false);
}
}
}

Some(pep508_rs::VersionOrUrl::VersionSpecifier(version_specifier)) => {
// The installed version doesn't satisfy the constraint.
// The installed version doesn't satisfy the requirement.
if !version_specifier.contains(distribution.version()) {
return Ok(false);
}
Expand Down
92 changes: 90 additions & 2 deletions crates/uv/tests/pip_install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1920,7 +1920,7 @@ fn install_symlink() {
}

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

// Create an editable package.
Expand Down Expand Up @@ -2010,7 +2010,7 @@ requires-python = ">=3.8"
}

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

// Create an editable package with dynamic metadata
Expand Down Expand Up @@ -2098,3 +2098,91 @@ dependencies = {file = ["requirements.txt"]}

Ok(())
}

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

// Create a local package.
let editable_dir = assert_fs::TempDir::new()?;
let pyproject_toml = editable_dir.child("pyproject.toml");
pyproject_toml.write_str(
r#"[project]
name = "example"
version = "0.0.0"
dependencies = [
"anyio==4.0.0"
]
requires-python = ">=3.8"
"#,
)?;

let filters = [(r"\(from file://.*\)", "(from [WORKSPACE_DIR])")]
.into_iter()
.chain(INSTA_FILTERS.to_vec())
.collect::<Vec<_>>();

uv_snapshot!(filters, command(&context)
.arg("example @ .")
.current_dir(editable_dir.path()), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 4 packages in [TIME]
Downloaded 4 packages in [TIME]
Installed 4 packages in [TIME]
+ anyio==4.0.0
+ example==0.0.0 (from [WORKSPACE_DIR])
+ idna==3.4
+ sniffio==1.3.0
"###
);

// Re-installing should be a no-op.
uv_snapshot!(filters, command(&context)
.arg("example @ .")
.current_dir(editable_dir.path()), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Audited 1 package in [TIME]
"###
);

// Modify the editable package.
pyproject_toml.write_str(
r#"[project]
name = "example"
version = "0.0.0"
dependencies = [
"anyio==3.7.1"
]
requires-python = ">=3.8"
"#,
)?;

// Re-installing should update the package.
uv_snapshot!(filters, command(&context)
.arg("example @ .")
.current_dir(editable_dir.path()), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 4 packages in [TIME]
Downloaded 2 packages in [TIME]
Installed 2 packages in [TIME]
- anyio==4.0.0
+ anyio==3.7.1
- example==0.0.0 (from [WORKSPACE_DIR])
+ example==0.0.0 (from [WORKSPACE_DIR])
"###
);

Ok(())
}

0 comments on commit 1320e8a

Please sign in to comment.