Repository navigation
Conversation
scripttest's change detection compares directories by mtime only, and FoundFile comparisons include mtime alongside content. With pip now preserving file and directory mtimes from wheel metadata, a reinstall of the same wheel is bit-identical, and even upgrades can leave a package directory untouched when the two wheels' files are identical (test wheels are often built within the same second, and the simple test sdists share committed source mtimes). Assert reinstalls and upgrades through pip's output and the newly created dist-info directory instead.
|
Following up on this one — CI is green across the matrix and the branch is mergeable. The #13681 thread ended with "happy to review" once the comments were addressed, so I wanted to make sure this didn't fall off the radar. Happy to make changes if anything looks off. |
agammann
left a comment
There was a problem hiding this comment.
dist_dirs only contains the immediate parent of each extracted file. For a wheel with pkg/sub/mod.py but no file directly in pkg/, this resets pkg/sub but never includes pkg, so the package directory keeps the install-time mtime and changes on each reinstall. Could you include ancestor directories up to the scheme root and add this layout to the reinstall test?
|
Good catch — dist_dirs only had the immediate parent of each extracted file, so a package with nothing directly inside it kept an install-time mtime. I now collect each file's ancestor directories up to (but not including) the scheme root, so |
|
Thanks for adding the ancestor coverage. One case to consider in the new directory pass: directories below a scheme root can also be shared by multiple distributions, for example an implicit namespace package |
Building on SnoopJ's #13681 (closed with "feel free to open a new PR with comments addressed, happy to review"), this preserves the mtimes of files installed from wheels and — the part #13681 left open — gives the distribution's directories deterministic mtimes so reinstalling an unchanged wheel leaves every timestamp identical. Resolves #13207.
File mtimes
As in #13681: at extraction time, each file's atime and mtime are set from its
ZipInfo.date_time(setting both was confirmed fine in the earlier review). The conversion attaches the local timezone before computing the POSIX timestamp, since the zip format stores local time without timezone information — the macOS CI failure in #13681 (a 4-hour offset) came from ignoring that.Directory mtimes (new — the main review feedback)
Wheels contain no directory entries, so every directory is created by pip, and writing into a directory updates its mtime. Without handling, directory mtimes merely reflect whenever pip last ran — churning on every reinstall even when contents are unchanged, and varying with extraction order.
After all of the distribution's files have been written (including generated ones: RECORD, INSTALLER,
__pycache__, rewritten scripts — these all bump parent mtimes, which is exactly the "accidentally updated" problem raised in review), the directories that received wheel files are walked deepest-first and each is assigned the maximum mtime among the wheel-provided items it contains. Generated files are deliberately not counted, so a directory's mtime is a pure function of the wheel's content: reinstalling the same wheel reproduces identical directory mtimes.Two deliberate choices, flagging for review:
Performance
Median of 5 runs installing a synthetic 3000-file wheel, Windows/Python 3.13:
--no-compile(extraction only)The cost is one
os.utimeper extracted file plus the directory pass; it is inherent to setting the timestamps at all. No extrastatpasses are done — mtimes are collected during extraction.Tests
test_wheel_install_mtime(from Preserve mtime of files installed from wheels #13681, restored): installed file mtime equals the wheel's stored time; naive-vs-naive comparison, timezone-robust.test_wheel_install_directory_mtimes_stable_across_reinstalls(new): installs the same wheel twice; asserts both installs produce identical directory mtimes, equal to the max mtime of the wheel-provided files inside, and that the site-packages root is not modified.tests/functional/test_install_wheel.py(42 passed),tests/functional/test_uninstall.py(27 passed, 8 pre-existing skips), and the wheel-related unit test modules (96 passed) on Windows/Python 3.13.pre-commitclean on touched files.News entry:
news/13207.feature.rst.Credit: the file-mtime approach and first test are SnoopJ's work from #13681, carried over with the timezone fix they identified.
PR Checklist: