uv/crates/uv-cache-info/src/timestamp.rs
Charlie Marsh 4f2349119c
Add support for dynamic cache keys (#7136)
## Summary

This PR adds a more flexible cache invalidation abstraction for uv, and
uses that new abstraction to improve support for dynamic metadata.

Specifically, instead of relying solely on a timestamp, we now pass
around a `CacheInfo` struct which (as of now) contains
`Option<Timestamp>` and `Option<Commit>`. The `CacheInfo` is saved in
`dist-info` as `uv_cache.json`, so we can test already-installed
distributions for cache validity (along with testing _cached_
distributions for cache validity).

Beyond the defaults (`pyproject.toml`, `setup.py`, and `setup.cfg`
changes), users can also specify additional cache keys, and it's easy
for us to extend support in the future. Right now, cache keys can either
be instructions to include the current commit (for `setuptools_scm` and
similar) or file paths (for `hatch-requirements-txt` and similar):

```toml
[tool.uv]
cache-keys = [{ file = "requirements.txt" }, { git = true }]
```

This change should be fully backwards compatible.

Closes https://github.com/astral-sh/uv/issues/6964.

Closes https://github.com/astral-sh/uv/issues/6255.

Closes https://github.com/astral-sh/uv/issues/6860.
2024-09-09 20:19:15 +00:00

46 lines
1.6 KiB
Rust

use serde::{Deserialize, Serialize};
use std::path::Path;
/// A timestamp used to measure changes to a file.
///
/// On Unix, this uses `ctime` as a conservative approach. `ctime` should detect all
/// modifications, including some that we don't care about, like hardlink modifications.
/// On other platforms, it uses `mtime`.
///
/// See: <https://github.com/restic/restic/issues/2179>
/// See: <https://apenwarr.ca/log/20181113>
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
pub struct Timestamp(std::time::SystemTime);
impl Timestamp {
/// Return the [`Timestamp`] for the given path.
pub fn from_path(path: impl AsRef<Path>) -> std::io::Result<Self> {
let metadata = fs_err::metadata(path.as_ref())?;
Ok(Self::from_metadata(&metadata))
}
/// Return the [`Timestamp`] for the given metadata.
pub fn from_metadata(metadata: &std::fs::Metadata) -> Self {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
let ctime = u64::try_from(metadata.ctime()).expect("ctime to be representable as u64");
let ctime_nsec = u32::try_from(metadata.ctime_nsec())
.expect("ctime_nsec to be representable as u32");
let duration = std::time::Duration::new(ctime, ctime_nsec);
Self(std::time::UNIX_EPOCH + duration)
}
#[cfg(not(unix))]
{
let modified = metadata.modified().expect("modified time to be available");
Self(modified)
}
}
/// Return the current [`Timestamp`].
pub fn now() -> Self {
Self(std::time::SystemTime::now())
}
}