mirror of https://github.com/astral-sh/uv
Respect local freshness when auditing installed environment (#2169)
## Summary Ensures that local dependencies function similarly to editables, in that if they're `uv pip install`ed, we invalidate them. Closes https://github.com/astral-sh/uv/issues/1651.
This commit is contained in:
parent
8c51b59298
commit
fda691401a
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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}");
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,13 @@ fn command_without_exclude_newer(context: &TestContext) -> Command {
|
|||
.arg(context.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", context.venv.as_os_str())
|
||||
.current_dir(&context.temp_dir);
|
||||
|
||||
if cfg!(all(windows, debug_assertions)) {
|
||||
// TODO(konstin): Reduce stack usage in debug mode enough that the tests pass with the
|
||||
// default windows stack of 1MB
|
||||
command.env("UV_STACK_SIZE", (2 * 1024 * 1024).to_string());
|
||||
}
|
||||
|
||||
command
|
||||
}
|
||||
|
||||
|
|
@ -69,6 +76,13 @@ fn uninstall_command(context: &TestContext) -> Command {
|
|||
.arg(context.cache_dir.path())
|
||||
.env("VIRTUAL_ENV", context.venv.as_os_str())
|
||||
.current_dir(&context.temp_dir);
|
||||
|
||||
if cfg!(all(windows, debug_assertions)) {
|
||||
// TODO(konstin): Reduce stack usage in debug mode enough that the tests pass with the
|
||||
// default windows stack of 1MB
|
||||
command.env("UV_STACK_SIZE", (2 * 1024 * 1024).to_string());
|
||||
}
|
||||
|
||||
command
|
||||
}
|
||||
|
||||
|
|
@ -1920,7 +1934,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.
|
||||
|
|
@ -2010,7 +2024,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
|
||||
|
|
@ -2098,3 +2112,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(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue