@@ -30,6 +30,9 @@ pub struct CacheInfo {
30
30
commit : Option < Commit > ,
31
31
/// The Git tags present at the time of the build.
32
32
tags : Option < Tags > ,
33
+ /// The timestamp or inode of any directories that should be considered in the cache key.
34
+ #[ serde( default , skip_serializing_if = "BTreeMap::is_empty" ) ]
35
+ directories : BTreeMap < String , DirectoryTimestamp > ,
33
36
/// Environment variables to include in the cache key.
34
37
#[ serde( default , skip_serializing_if = "BTreeMap::is_empty" ) ]
35
38
env : BTreeMap < String , Option < String > > ,
@@ -59,6 +62,7 @@ impl CacheInfo {
59
62
let mut commit = None ;
60
63
let mut tags = None ;
61
64
let mut timestamp = None ;
65
+ let mut directories = BTreeMap :: new ( ) ;
62
66
let mut env = BTreeMap :: new ( ) ;
63
67
64
68
// Read the cache keys.
@@ -82,6 +86,9 @@ impl CacheInfo {
82
86
CacheKey :: Path ( "pyproject.toml" . to_string( ) ) ,
83
87
CacheKey :: Path ( "setup.py" . to_string( ) ) ,
84
88
CacheKey :: Path ( "setup.cfg" . to_string( ) ) ,
89
+ CacheKey :: Directory {
90
+ dir: "src" . to_string( ) ,
91
+ } ,
85
92
]
86
93
} ) ;
87
94
@@ -117,6 +124,47 @@ impl CacheInfo {
117
124
}
118
125
timestamp = max ( timestamp, Some ( Timestamp :: from_metadata ( & metadata) ) ) ;
119
126
}
127
+ CacheKey :: Directory { dir } => {
128
+ // Treat the path as a directory.
129
+ let path = directory. join ( & dir) ;
130
+ let metadata = match path. metadata ( ) {
131
+ Ok ( metadata) => metadata,
132
+ Err ( err) if err. kind ( ) == std:: io:: ErrorKind :: NotFound => {
133
+ continue ;
134
+ }
135
+ Err ( err) => {
136
+ warn ! ( "Failed to read metadata for directory: {err}" ) ;
137
+ continue ;
138
+ }
139
+ } ;
140
+ if !metadata. is_dir ( ) {
141
+ warn ! (
142
+ "Expected directory for cache key, but found file: `{}`" ,
143
+ path. display( )
144
+ ) ;
145
+ continue ;
146
+ }
147
+
148
+ if let Ok ( created) = metadata. created ( ) {
149
+ // Prefer the creation time.
150
+ directories
151
+ . insert ( dir, DirectoryTimestamp :: Timestamp ( Timestamp :: from ( created) ) ) ;
152
+ } else {
153
+ // Fall back to the inode.
154
+ #[ cfg( unix) ]
155
+ {
156
+ use std:: os:: unix:: fs:: MetadataExt ;
157
+ directories. insert ( dir, DirectoryTimestamp :: Inode ( metadata. ino ( ) ) ) ;
158
+ }
159
+ #[ cfg( not( unix) ) ]
160
+ {
161
+ warn ! (
162
+ "Failed to read creation time for directory: `{}`" ,
163
+ path. display( )
164
+ ) ;
165
+ }
166
+ }
167
+ }
120
168
CacheKey :: Git {
121
169
git : GitPattern :: Bool ( true ) ,
122
170
} => match Commit :: from_repository ( directory) {
@@ -190,6 +238,7 @@ impl CacheInfo {
190
238
timestamp,
191
239
commit,
192
240
tags,
241
+ directories,
193
242
env,
194
243
} )
195
244
}
@@ -241,6 +290,8 @@ pub enum CacheKey {
241
290
Path ( String ) ,
242
291
/// Ex) `{ file = "Cargo.lock" }` or `{ file = "**/*.toml" }`
243
292
File { file : String } ,
293
+ /// Ex) `{ dir = "src" }`
294
+ Directory { dir : String } ,
244
295
/// Ex) `{ git = true }` or `{ git = { commit = true, tags = false } }`
245
296
Git { git : GitPattern } ,
246
297
/// Ex) `{ env = "UV_CACHE_INFO" }`
@@ -267,3 +318,10 @@ pub enum FilePattern {
267
318
Glob ( String ) ,
268
319
Path ( PathBuf ) ,
269
320
}
321
+
322
+ /// A timestamp used to measure changes to a directory.
323
+ #[ derive( Debug , Clone , Hash , PartialEq , Eq , serde:: Deserialize , serde:: Serialize ) ]
324
+ enum DirectoryTimestamp {
325
+ Timestamp ( Timestamp ) ,
326
+ Inode ( u64 ) ,
327
+ }
0 commit comments