@@ -396,7 +396,11 @@ def default_lib_path(data_dir: str,
396
396
('hash' , str ),
397
397
('dependencies' , List [str ]), # names of imported modules
398
398
('data_mtime' , int ), # mtime of data_json
399
+ ('deps_mtime' , Optional [int ]), # mtime of deps_json
399
400
('data_json' , str ), # path of <id>.data.json
401
+ # path of <id>.deps.json, which we use to store fine-grained
402
+ # dependency information for fine-grained mode
403
+ ('deps_json' , Optional [str ]),
400
404
('suppressed' , List [str ]), # dependencies that weren't imported
401
405
('child_modules' , List [str ]), # all submodules of the given module
402
406
('options' , Optional [Dict [str , object ]]), # build options
@@ -413,7 +417,16 @@ def default_lib_path(data_dir: str,
413
417
# silent mode or simply not found.
414
418
415
419
416
- def cache_meta_from_dict (meta : Dict [str , Any ], data_json : str ) -> CacheMeta :
420
+ def cache_meta_from_dict (meta : Dict [str , Any ],
421
+ data_json : str , deps_json : Optional [str ]) -> CacheMeta :
422
+ """Build a CacheMeta object from a json metadata dictionary
423
+
424
+ Args:
425
+ meta: JSON metadata read from the metadata cache file
426
+ data_json: Path to the .data.json file containing the AST trees
427
+ deps_json: Optionally, path to the .deps.json file containign
428
+ fine-grained dependency information.
429
+ """
417
430
sentinel = None # type: Any # Values to be validated by the caller
418
431
return CacheMeta (
419
432
meta .get ('id' , sentinel ),
@@ -423,7 +436,9 @@ def cache_meta_from_dict(meta: Dict[str, Any], data_json: str) -> CacheMeta:
423
436
meta .get ('hash' , sentinel ),
424
437
meta .get ('dependencies' , []),
425
438
int (meta ['data_mtime' ]) if 'data_mtime' in meta else sentinel ,
439
+ int (meta ['deps_mtime' ]) if meta .get ('deps_mtime' ) is not None else None ,
426
440
data_json ,
441
+ deps_json ,
427
442
meta .get ('suppressed' , []),
428
443
meta .get ('child_modules' , []),
429
444
meta .get ('options' ),
@@ -962,7 +977,7 @@ def verify_module(fscache: FileSystemMetaCache, id: str, path: str) -> bool:
962
977
return True
963
978
964
979
965
- def get_cache_names (id : str , path : str , manager : BuildManager ) -> Tuple [str , str ]:
980
+ def get_cache_names (id : str , path : str , manager : BuildManager ) -> Tuple [str , str , Optional [ str ] ]:
966
981
"""Return the file names for the cache files.
967
982
968
983
Args:
@@ -972,16 +987,20 @@ def get_cache_names(id: str, path: str, manager: BuildManager) -> Tuple[str, str
972
987
pyversion: Python version (major, minor)
973
988
974
989
Returns:
975
- A tuple with the file names to be used for the meta JSON and the
976
- data JSON, respectively.
990
+ A tuple with the file names to be used for the meta JSON, the
991
+ data JSON, and the fine-grained deps JSON, respectively.
977
992
"""
978
993
cache_dir = manager .options .cache_dir
979
994
pyversion = manager .options .python_version
980
995
prefix = os .path .join (cache_dir , '%d.%d' % pyversion , * id .split ('.' ))
981
996
is_package = os .path .basename (path ).startswith ('__init__.py' )
982
997
if is_package :
983
998
prefix = os .path .join (prefix , '__init__' )
984
- return (prefix + '.meta.json' , prefix + '.data.json' )
999
+
1000
+ deps_json = None
1001
+ if manager .options .cache_fine_grained :
1002
+ deps_json = prefix + '.deps.json'
1003
+ return (prefix + '.meta.json' , prefix + '.data.json' , deps_json )
985
1004
986
1005
987
1006
def find_cache_meta (id : str , path : str , manager : BuildManager ) -> Optional [CacheMeta ]:
@@ -997,7 +1016,7 @@ def find_cache_meta(id: str, path: str, manager: BuildManager) -> Optional[Cache
997
1016
valid; otherwise None.
998
1017
"""
999
1018
# TODO: May need to take more build options into account
1000
- meta_json , data_json = get_cache_names (id , path , manager )
1019
+ meta_json , data_json , deps_json = get_cache_names (id , path , manager )
1001
1020
manager .trace ('Looking for {} at {}' .format (id , meta_json ))
1002
1021
try :
1003
1022
with open (meta_json , 'r' ) as f :
@@ -1011,11 +1030,12 @@ def find_cache_meta(id: str, path: str, manager: BuildManager) -> Optional[Cache
1011
1030
manager .log ('Could not load cache for {}: meta cache is not a dict: {}'
1012
1031
.format (id , repr (meta )))
1013
1032
return None
1014
- m = cache_meta_from_dict (meta , data_json )
1033
+ m = cache_meta_from_dict (meta , data_json , deps_json )
1015
1034
# Don't check for path match, that is dealt with in validate_meta().
1016
1035
if (m .id != id or
1017
1036
m .mtime is None or m .size is None or
1018
- m .dependencies is None or m .data_mtime is None ):
1037
+ m .dependencies is None or m .data_mtime is None or
1038
+ (manager .options .cache_fine_grained and m .deps_mtime is None )):
1019
1039
manager .log ('Metadata abandoned for {}: attributes are missing' .format (id ))
1020
1040
return None
1021
1041
@@ -1098,6 +1118,13 @@ def validate_meta(meta: Optional[CacheMeta], id: str, path: Optional[str],
1098
1118
if data_mtime != meta .data_mtime :
1099
1119
manager .log ('Metadata abandoned for {}: data cache is modified' .format (id ))
1100
1120
return None
1121
+ deps_mtime = None
1122
+ if manager .options .cache_fine_grained :
1123
+ assert meta .deps_json
1124
+ deps_mtime = getmtime (meta .deps_json )
1125
+ if deps_mtime != meta .deps_mtime :
1126
+ manager .log ('Metadata abandoned for {}: deps cache is modified' .format (id ))
1127
+ return None
1101
1128
1102
1129
path = os .path .abspath (path )
1103
1130
try :
@@ -1143,6 +1170,7 @@ def validate_meta(meta: Optional[CacheMeta], id: str, path: Optional[str],
1143
1170
'size' : size ,
1144
1171
'hash' : source_hash ,
1145
1172
'data_mtime' : data_mtime ,
1173
+ 'deps_mtime' : deps_mtime ,
1146
1174
'dependencies' : meta .dependencies ,
1147
1175
'suppressed' : meta .suppressed ,
1148
1176
'child_modules' : meta .child_modules ,
@@ -1158,7 +1186,7 @@ def validate_meta(meta: Optional[CacheMeta], id: str, path: Optional[str],
1158
1186
meta_str = json .dumps (meta_dict , indent = 2 , sort_keys = True )
1159
1187
else :
1160
1188
meta_str = json .dumps (meta_dict )
1161
- meta_json , _ = get_cache_names (id , path , manager )
1189
+ meta_json , _ , _2 = get_cache_names (id , path , manager )
1162
1190
manager .log ('Updating mtime for {}: file {}, meta {}, mtime {}'
1163
1191
.format (id , path , meta_json , meta .mtime ))
1164
1192
atomic_write (meta_json , meta_str , '\n ' ) # Ignore errors, it's just an optimization.
@@ -1176,6 +1204,13 @@ def compute_hash(text: str) -> str:
1176
1204
return hashlib .md5 (text .encode ('utf-8' )).hexdigest ()
1177
1205
1178
1206
1207
+ def json_dumps (obj : Any , debug_cache : bool ) -> str :
1208
+ if debug_cache :
1209
+ return json .dumps (obj , indent = 2 , sort_keys = True )
1210
+ else :
1211
+ return json .dumps (obj , sort_keys = True )
1212
+
1213
+
1179
1214
def write_cache (id : str , path : str , tree : MypyFile ,
1180
1215
serialized_fine_grained_deps : Dict [str , List [str ]],
1181
1216
dependencies : List [str ], suppressed : List [str ],
@@ -1209,21 +1244,17 @@ def write_cache(id: str, path: str, tree: MypyFile,
1209
1244
"""
1210
1245
# Obtain file paths
1211
1246
path = os .path .abspath (path )
1212
- meta_json , data_json = get_cache_names (id , path , manager )
1213
- manager .log ('Writing {} {} {} {}' .format (id , path , meta_json , data_json ))
1247
+ meta_json , data_json , deps_json = get_cache_names (id , path , manager )
1248
+ manager .log ('Writing {} {} {} {} {}' .format (
1249
+ id , path , meta_json , data_json , deps_json ))
1214
1250
1215
1251
# Make sure directory for cache files exists
1216
1252
parent = os .path .dirname (data_json )
1217
1253
assert os .path .dirname (meta_json ) == parent
1218
1254
1219
1255
# Serialize data and analyze interface
1220
- data = {'tree' : tree .serialize (),
1221
- 'fine_grained_deps' : serialized_fine_grained_deps ,
1222
- }
1223
- if manager .options .debug_cache :
1224
- data_str = json .dumps (data , indent = 2 , sort_keys = True )
1225
- else :
1226
- data_str = json .dumps (data , sort_keys = True )
1256
+ data = tree .serialize ()
1257
+ data_str = json_dumps (data , manager .options .debug_cache )
1227
1258
interface_hash = compute_hash (data_str )
1228
1259
1229
1260
# Obtain and set up metadata
@@ -1265,6 +1296,14 @@ def write_cache(id: str, path: str, tree: MypyFile,
1265
1296
return interface_hash , None
1266
1297
data_mtime = getmtime (data_json )
1267
1298
1299
+ deps_mtime = None
1300
+ if deps_json :
1301
+ deps_str = json_dumps (serialized_fine_grained_deps , manager .options .debug_cache )
1302
+ if not atomic_write (deps_json , deps_str , '\n ' ):
1303
+ manager .log ("Error writing deps JSON file {}" .format (deps_json ))
1304
+ return interface_hash , None
1305
+ deps_mtime = getmtime (deps_json )
1306
+
1268
1307
mtime = int (st .st_mtime )
1269
1308
size = st .st_size
1270
1309
options = manager .options .clone_for_module (id )
@@ -1275,6 +1314,7 @@ def write_cache(id: str, path: str, tree: MypyFile,
1275
1314
'size' : size ,
1276
1315
'hash' : source_hash ,
1277
1316
'data_mtime' : data_mtime ,
1317
+ 'deps_mtime' : deps_mtime ,
1278
1318
'dependencies' : dependencies ,
1279
1319
'suppressed' : suppressed ,
1280
1320
'child_modules' : child_modules ,
@@ -1287,17 +1327,14 @@ def write_cache(id: str, path: str, tree: MypyFile,
1287
1327
}
1288
1328
1289
1329
# Write meta cache file
1290
- if manager .options .debug_cache :
1291
- meta_str = json .dumps (meta , indent = 2 , sort_keys = True )
1292
- else :
1293
- meta_str = json .dumps (meta )
1330
+ meta_str = json_dumps (meta , manager .options .debug_cache )
1294
1331
if not atomic_write (meta_json , meta_str , '\n ' ):
1295
1332
# Most likely the error is the replace() call
1296
1333
# (see https://github.com/python/mypy/issues/3215).
1297
1334
# The next run will simply find the cache entry out of date.
1298
1335
manager .log ("Error writing meta JSON file {}" .format (meta_json ))
1299
1336
1300
- return interface_hash , cache_meta_from_dict (meta , data_json )
1337
+ return interface_hash , cache_meta_from_dict (meta , data_json , deps_json )
1301
1338
1302
1339
1303
1340
def delete_cache (id : str , path : str , manager : BuildManager ) -> None :
@@ -1308,12 +1345,13 @@ def delete_cache(id: str, path: str, manager: BuildManager) -> None:
1308
1345
see #4043 for an example.
1309
1346
"""
1310
1347
path = os .path .abspath (path )
1311
- meta_json , data_json = get_cache_names (id , path , manager )
1312
- manager .log ('Deleting {} {} {} {} ' .format (id , path , meta_json , data_json ))
1348
+ cache_paths = get_cache_names (id , path , manager )
1349
+ manager .log ('Deleting {} {} {}' .format (id , path , " " . join ( x for x in cache_paths if x ) ))
1313
1350
1314
- for filename in [ data_json , meta_json ] :
1351
+ for filename in cache_paths :
1315
1352
try :
1316
- os .remove (filename )
1353
+ if filename :
1354
+ os .remove (filename )
1317
1355
except OSError as e :
1318
1356
if e .errno != errno .ENOENT :
1319
1357
manager .log ("Error deleting cache file {}: {}" .format (filename , e .strerror ))
@@ -1657,15 +1695,22 @@ def wrap_context(self) -> Iterator[None]:
1657
1695
self .check_blockers ()
1658
1696
1659
1697
# Methods for processing cached modules.
1698
+ def load_fine_grained_deps (self ) -> None :
1699
+ assert self .meta is not None , "Internal error: this method must be called only" \
1700
+ " for cached modules"
1701
+ assert self .meta .deps_json
1702
+ with open (self .meta .deps_json ) as f :
1703
+ deps = json .load (f )
1704
+ # TODO: Assert deps file wasn't changed.
1705
+ self .fine_grained_deps = {k : set (v ) for k , v in deps .items ()}
1660
1706
1661
1707
def load_tree (self ) -> None :
1662
1708
assert self .meta is not None , "Internal error: this method must be called only" \
1663
1709
" for cached modules"
1664
1710
with open (self .meta .data_json ) as f :
1665
1711
data = json .load (f )
1666
1712
# TODO: Assert data file wasn't changed.
1667
- self .tree = MypyFile .deserialize (data ['tree' ])
1668
- self .fine_grained_deps = {k : set (v ) for k , v in data ['fine_grained_deps' ].items ()}
1713
+ self .tree = MypyFile .deserialize (data )
1669
1714
1670
1715
self .manager .modules [self .id ] = self .tree
1671
1716
self .manager .add_stats (fresh_trees = 1 )
@@ -2520,6 +2565,8 @@ def process_fine_grained_cache_graph(graph: Graph, manager: BuildManager) -> Non
2520
2565
# Note that ascc is a set, and scc is a list.
2521
2566
scc = order_ascc (graph , ascc )
2522
2567
process_fresh_scc (graph , scc , manager )
2568
+ for id in scc :
2569
+ graph [id ].load_fine_grained_deps ()
2523
2570
2524
2571
2525
2572
def order_ascc (graph : Graph , ascc : AbstractSet [str ], pri_max : int = PRI_ALL ) -> List [str ]:
0 commit comments