Skip to content

Commit 4f23491

Browse files
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 #6964. Closes #6255. Closes #6860.
1 parent 9a7262c commit 4f23491

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1022
-192
lines changed

Cargo.lock

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ requirements-txt = { path = "crates/requirements-txt" }
3030
uv-auth = { path = "crates/uv-auth" }
3131
uv-build = { path = "crates/uv-build" }
3232
uv-cache = { path = "crates/uv-cache" }
33+
uv-cache-info = { path = "crates/uv-cache-info" }
3334
uv-cli = { path = "crates/uv-cli" }
3435
uv-client = { path = "crates/uv-client" }
3536
uv-configuration = { path = "crates/uv-configuration" }

crates/distribution-types/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pep440_rs = { workspace = true }
1919
pep508_rs = { workspace = true, features = ["serde"] }
2020
platform-tags = { workspace = true }
2121
pypi-types = { workspace = true }
22+
uv-cache-info = { workspace = true }
2223
uv-fs = { workspace = true }
2324
uv-git = { workspace = true }
2425
uv-normalize = { workspace = true }

crates/distribution-types/src/cached.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use anyhow::{anyhow, Result};
55
use distribution_filename::WheelFilename;
66
use pep508_rs::VerbatimUrl;
77
use pypi_types::{HashDigest, ParsedDirectoryUrl};
8+
use uv_cache_info::CacheInfo;
89
use uv_normalize::PackageName;
910

1011
use crate::{
@@ -26,6 +27,7 @@ pub struct CachedRegistryDist {
2627
pub filename: WheelFilename,
2728
pub path: PathBuf,
2829
pub hashes: Vec<HashDigest>,
30+
pub cache_info: CacheInfo,
2931
}
3032

3133
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
@@ -36,6 +38,7 @@ pub struct CachedDirectUrlDist {
3638
pub editable: bool,
3739
pub r#virtual: bool,
3840
pub hashes: Vec<HashDigest>,
41+
pub cache_info: CacheInfo,
3942
}
4043

4144
impl CachedDist {
@@ -44,18 +47,21 @@ impl CachedDist {
4447
remote: Dist,
4548
filename: WheelFilename,
4649
hashes: Vec<HashDigest>,
50+
cache_info: CacheInfo,
4751
path: PathBuf,
4852
) -> Self {
4953
match remote {
5054
Dist::Built(BuiltDist::Registry(_dist)) => Self::Registry(CachedRegistryDist {
5155
filename,
5256
path,
5357
hashes,
58+
cache_info,
5459
}),
5560
Dist::Built(BuiltDist::DirectUrl(dist)) => Self::Url(CachedDirectUrlDist {
5661
filename,
5762
url: dist.url,
5863
hashes,
64+
cache_info,
5965
path,
6066
editable: false,
6167
r#virtual: false,
@@ -64,6 +70,7 @@ impl CachedDist {
6470
filename,
6571
url: dist.url,
6672
hashes,
73+
cache_info,
6774
path,
6875
editable: false,
6976
r#virtual: false,
@@ -72,11 +79,13 @@ impl CachedDist {
7279
filename,
7380
path,
7481
hashes,
82+
cache_info,
7583
}),
7684
Dist::Source(SourceDist::DirectUrl(dist)) => Self::Url(CachedDirectUrlDist {
7785
filename,
7886
url: dist.url,
7987
hashes,
88+
cache_info,
8089
path,
8190
editable: false,
8291
r#virtual: false,
@@ -85,6 +94,7 @@ impl CachedDist {
8594
filename,
8695
url: dist.url,
8796
hashes,
97+
cache_info,
8898
path,
8999
editable: false,
90100
r#virtual: false,
@@ -93,6 +103,7 @@ impl CachedDist {
93103
filename,
94104
url: dist.url,
95105
hashes,
106+
cache_info,
96107
path,
97108
editable: false,
98109
r#virtual: false,
@@ -101,6 +112,7 @@ impl CachedDist {
101112
filename,
102113
url: dist.url,
103114
hashes,
115+
cache_info,
104116
path,
105117
editable: dist.editable,
106118
r#virtual: dist.r#virtual,
@@ -116,6 +128,14 @@ impl CachedDist {
116128
}
117129
}
118130

131+
/// Return the [`CacheInfo`] of the distribution.
132+
pub fn cache_info(&self) -> &CacheInfo {
133+
match self {
134+
Self::Registry(dist) => &dist.cache_info,
135+
Self::Url(dist) => &dist.cache_info,
136+
}
137+
}
138+
119139
/// Return the [`ParsedUrl`] of the distribution, if it exists.
120140
pub fn parsed_url(&self) -> Result<Option<ParsedUrl>> {
121141
match self {
@@ -161,12 +181,14 @@ impl CachedDirectUrlDist {
161181
filename: WheelFilename,
162182
url: VerbatimUrl,
163183
hashes: Vec<HashDigest>,
184+
cache_info: CacheInfo,
164185
path: PathBuf,
165186
) -> Self {
166187
Self {
167188
filename,
168189
url,
169190
hashes,
191+
cache_info,
170192
path,
171193
editable: false,
172194
r#virtual: false,

crates/distribution-types/src/installed.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use url::Url;
1010
use distribution_filename::EggInfoFilename;
1111
use pep440_rs::Version;
1212
use pypi_types::DirectUrl;
13+
use uv_cache_info::CacheInfo;
1314
use uv_fs::Simplified;
1415
use uv_normalize::PackageName;
1516

@@ -35,6 +36,7 @@ pub struct InstalledRegistryDist {
3536
pub name: PackageName,
3637
pub version: Version,
3738
pub path: PathBuf,
39+
pub cache_info: Option<CacheInfo>,
3840
}
3941

4042
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
@@ -45,6 +47,7 @@ pub struct InstalledDirectUrlDist {
4547
pub url: Url,
4648
pub editable: bool,
4749
pub path: PathBuf,
50+
pub cache_info: Option<CacheInfo>,
4851
}
4952

5053
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
@@ -90,6 +93,7 @@ impl InstalledDist {
9093

9194
let name = PackageName::from_str(name)?;
9295
let version = Version::from_str(version).map_err(|err| anyhow!(err))?;
96+
let cache_info = Self::cache_info(path)?;
9397

9498
return if let Some(direct_url) = Self::direct_url(path)? {
9599
match Url::try_from(&direct_url) {
@@ -100,13 +104,15 @@ impl InstalledDist {
100104
direct_url: Box::new(direct_url),
101105
url,
102106
path: path.to_path_buf(),
107+
cache_info,
103108
}))),
104109
Err(err) => {
105110
warn!("Failed to parse direct URL: {err}");
106111
Ok(Some(Self::Registry(InstalledRegistryDist {
107112
name,
108113
version,
109114
path: path.to_path_buf(),
115+
cache_info,
110116
})))
111117
}
112118
}
@@ -115,6 +121,7 @@ impl InstalledDist {
115121
name,
116122
version,
117123
path: path.to_path_buf(),
124+
cache_info,
118125
})))
119126
};
120127
}
@@ -256,13 +263,27 @@ impl InstalledDist {
256263
/// Read the `direct_url.json` file from a `.dist-info` directory.
257264
pub fn direct_url(path: &Path) -> Result<Option<DirectUrl>> {
258265
let path = path.join("direct_url.json");
259-
let Ok(file) = fs_err::File::open(path) else {
260-
return Ok(None);
266+
let file = match fs_err::File::open(&path) {
267+
Ok(file) => file,
268+
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
269+
Err(err) => return Err(err.into()),
261270
};
262271
let direct_url = serde_json::from_reader::<fs_err::File, DirectUrl>(file)?;
263272
Ok(Some(direct_url))
264273
}
265274

275+
/// Read the `uv_cache.json` file from a `.dist-info` directory.
276+
pub fn cache_info(path: &Path) -> Result<Option<CacheInfo>> {
277+
let path = path.join("uv_cache.json");
278+
let file = match fs_err::File::open(&path) {
279+
Ok(file) => file,
280+
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(None),
281+
Err(err) => return Err(err.into()),
282+
};
283+
let cache_info = serde_json::from_reader::<fs_err::File, CacheInfo>(file)?;
284+
Ok(Some(cache_info))
285+
}
286+
266287
/// Read the `METADATA` file from a `.dist-info` directory.
267288
pub fn metadata(&self) -> Result<pypi_types::Metadata23> {
268289
match self {

crates/install-wheel-rs/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
name = "install-wheel-rs"
33
version = "0.0.1"
44
publish = false
5-
description = "Takes a wheel and installs it, either in a venv or for monotrail"
5+
description = "Takes a wheel and installs it."
66
keywords = ["wheel", "python"]
77

88
edition = { workspace = true }
@@ -24,6 +24,7 @@ distribution-filename = { workspace = true }
2424
pep440_rs = { workspace = true }
2525
platform-tags = { workspace = true }
2626
pypi-types = { workspace = true }
27+
uv-cache-info = { workspace = true }
2728
uv-fs = { workspace = true }
2829
uv-normalize = { workspace = true }
2930
uv-warnings = { workspace = true }

crates/install-wheel-rs/Readme.md

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)