Ensure mtime of site packages is updated during wheel installation (#2545)

Closes https://github.com/astral-sh/uv/issues/2530
This commit is contained in:
Zanie Blue 2024-03-19 19:54:05 -05:00 committed by GitHub
parent 136e744a9f
commit baa30697a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 72 additions and 2 deletions

View file

@ -3,6 +3,7 @@
use std::path::Path;
use std::str::FromStr;
use std::time::SystemTime;
use fs_err as fs;
use fs_err::{DirEntry, File};
@ -263,6 +264,31 @@ fn clone_wheel_files(
count += 1;
}
// The directory mtime is not updated when cloning and the mtime is used by CPython's
// import mechanisms to determine if it should look for new packages in a directory.
// Here, we force the mtime to be updated to ensure that packages are importable without
// manual cache invalidation.
//
// <https://github.com/python/cpython/blob/8336cb2b6f428246803b02a4e97fce49d0bb1e09/Lib/importlib/_bootstrap_external.py#L1601>
let now = SystemTime::now();
// `File.set_modified` is not available in `fs_err` yet
#[allow(clippy::disallowed_types)]
match std::fs::File::open(site_packages.as_ref()) {
Ok(dir) => {
if let Err(err) = dir.set_modified(now) {
debug!(
"Failed to update mtime for {}: {err}",
site_packages.as_ref().display()
);
}
}
Err(err) => debug!(
"Failed to open {} to update mtime: {err}",
site_packages.as_ref().display()
),
}
Ok(count)
}

View file

@ -1,13 +1,12 @@
#![cfg(all(feature = "python", feature = "pypi"))]
use std::process::Command;
use anyhow::Result;
use assert_cmd::prelude::*;
use assert_fs::prelude::*;
use base64::{prelude::BASE64_STANDARD as base64, Engine};
use indoc::indoc;
use itertools::Itertools;
use std::process::Command;
use url::Url;
use common::{uv_snapshot, TestContext, EXCLUDE_NEWER, INSTA_FILTERS};
@ -2843,3 +2842,48 @@ fn install_index_with_relative_links_authenticated() {
context.assert_command("import anyio").success();
}
/// The modified time of `site-packages` should change on package installation.
#[cfg(unix)]
#[test]
fn install_site_packages_mtime_updated() -> Result<()> {
use std::os::unix::fs::MetadataExt;
let context = TestContext::new("3.12");
let site_packages = context.site_packages();
// `mtime` is only second-resolution so we include the nanoseconds as well
let metadata = site_packages.metadata()?;
let pre_mtime = metadata.mtime();
let pre_mtime_ns = metadata.mtime_nsec();
// Install a package.
uv_snapshot!(command(&context)
.arg("anyio")
.arg("--strict"), @r###"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 3 packages in [TIME]
Downloaded 3 packages in [TIME]
Installed 3 packages in [TIME]
+ anyio==4.0.0
+ idna==3.4
+ sniffio==1.3.0
"###
);
let metadata = site_packages.metadata()?;
let post_mtime = metadata.mtime();
let post_mtime_ns = metadata.mtime_nsec();
assert!(
(post_mtime, post_mtime_ns) > (pre_mtime, pre_mtime_ns),
"Expected newer mtime than {pre_mtime}.{pre_mtime_ns} but got {post_mtime}.{post_mtime_ns}"
);
Ok(())
}