74
74
Graph = Dict [str , 'State' ]
75
75
76
76
77
- def getmtime (name : str ) -> int :
78
- return int (os .path .getmtime (name ))
79
-
80
-
81
77
# TODO: Get rid of BuildResult. We might as well return a BuildManager.
82
78
class BuildResult :
83
79
"""The result of a successful build.
@@ -230,7 +226,12 @@ def compute_lib_path(sources: List[BuildSource],
230
226
# to the lib_path
231
227
# TODO: Don't do this in some cases; for motivation see see
232
228
# https://github.com/python/mypy/issues/4195#issuecomment-341915031
233
- lib_path .appendleft (os .getcwd ())
229
+ if options .bazel :
230
+ dir = '.'
231
+ else :
232
+ dir = os .getcwd ()
233
+ if dir not in lib_path :
234
+ lib_path .appendleft (dir )
234
235
235
236
# Prepend a config-defined mypy path.
236
237
lib_path .extendleft (options .mypy_path )
@@ -687,6 +688,31 @@ def maybe_swap_for_shadow_path(self, path: str) -> str:
687
688
def get_stat (self , path : str ) -> os .stat_result :
688
689
return self .fscache .stat (self .maybe_swap_for_shadow_path (path ))
689
690
691
+ def getmtime (self , path : str ) -> int :
692
+ """Return a file's mtime; but 0 in bazel mode.
693
+
694
+ (Bazel's distributed cache doesn't like filesystem metadata to
695
+ end up in output files.)
696
+ """
697
+ if self .options .bazel :
698
+ return 0
699
+ else :
700
+ return int (os .path .getmtime (path ))
701
+
702
+ def normpath (self , path : str ) -> str :
703
+ """Convert path to absolute; but to relative in bazel mode.
704
+
705
+ (Bazel's distributed cache doesn't like filesystem metadata to
706
+ end up in output files.)
707
+ """
708
+ # TODO: Could we always use relpath? (A worry in non-bazel
709
+ # mode would be that a moved file may change its full module
710
+ # name without changing its size, mtime or hash.)
711
+ if self .options .bazel :
712
+ return os .path .relpath (path )
713
+ else :
714
+ return os .path .abspath (path )
715
+
690
716
def all_imported_modules_in_file (self ,
691
717
file : MypyFile ) -> List [Tuple [int , str , int ]]:
692
718
"""Find all reachable import statements in a file.
@@ -1094,14 +1120,17 @@ def get_cache_names(id: str, path: str, manager: BuildManager) -> Tuple[str, str
1094
1120
1095
1121
Args:
1096
1122
id: module ID
1097
- path: module path (used to recognize packages)
1123
+ path: module path
1098
1124
cache_dir: cache directory
1099
1125
pyversion: Python version (major, minor)
1100
1126
1101
1127
Returns:
1102
1128
A tuple with the file names to be used for the meta JSON, the
1103
1129
data JSON, and the fine-grained deps JSON, respectively.
1104
1130
"""
1131
+ pair = manager .options .cache_map .get (path )
1132
+ if pair is not None :
1133
+ return (pair [0 ], pair [1 ], None )
1105
1134
prefix = _cache_dir_prefix (manager , id )
1106
1135
is_package = os .path .basename (path ).startswith ('__init__.py' )
1107
1136
if is_package :
@@ -1232,22 +1261,23 @@ def validate_meta(meta: Optional[CacheMeta], id: str, path: Optional[str],
1232
1261
manager .log ('Metadata abandoned for {}: errors were previously ignored' .format (id ))
1233
1262
return None
1234
1263
1264
+ bazel = manager .options .bazel
1235
1265
assert path is not None , "Internal error: meta was provided without a path"
1236
1266
# Check data_json; assume if its mtime matches it's good.
1237
1267
# TODO: stat() errors
1238
- data_mtime = getmtime (meta .data_json )
1268
+ data_mtime = manager . getmtime (meta .data_json )
1239
1269
if data_mtime != meta .data_mtime :
1240
1270
manager .log ('Metadata abandoned for {}: data cache is modified' .format (id ))
1241
1271
return None
1242
1272
deps_mtime = None
1243
1273
if manager .options .cache_fine_grained :
1244
1274
assert meta .deps_json
1245
- deps_mtime = getmtime (meta .deps_json )
1275
+ deps_mtime = manager . getmtime (meta .deps_json )
1246
1276
if deps_mtime != meta .deps_mtime :
1247
1277
manager .log ('Metadata abandoned for {}: deps cache is modified' .format (id ))
1248
1278
return None
1249
1279
1250
- path = os . path . abspath (path )
1280
+ path = manager . normpath (path )
1251
1281
try :
1252
1282
st = manager .get_stat (path )
1253
1283
except OSError :
@@ -1272,12 +1302,14 @@ def validate_meta(meta: Optional[CacheMeta], id: str, path: Optional[str],
1272
1302
fine_grained_cache = manager .use_fine_grained_cache ()
1273
1303
1274
1304
size = st .st_size
1275
- if size != meta .size and not fine_grained_cache :
1305
+ # Bazel ensures the cache is valid.
1306
+ if size != meta .size and not bazel and not fine_grained_cache :
1276
1307
manager .log ('Metadata abandoned for {}: file {} has different size' .format (id , path ))
1277
1308
return None
1278
1309
1279
- mtime = int (st .st_mtime )
1280
- if mtime != meta .mtime or path != meta .path :
1310
+ # Bazel ensures the cache is valid.
1311
+ mtime = 0 if bazel else int (st .st_mtime )
1312
+ if not bazel and (mtime != meta .mtime or path != meta .path ):
1281
1313
try :
1282
1314
source_hash = manager .fscache .md5 (path )
1283
1315
except (OSError , UnicodeDecodeError , DecodeError ):
@@ -1317,7 +1349,7 @@ def validate_meta(meta: Optional[CacheMeta], id: str, path: Optional[str],
1317
1349
meta_str = json .dumps (meta_dict , indent = 2 , sort_keys = True )
1318
1350
else :
1319
1351
meta_str = json .dumps (meta_dict )
1320
- meta_json , _ , _2 = get_cache_names (id , path , manager )
1352
+ meta_json , _ , _ = get_cache_names (id , path , manager )
1321
1353
manager .log ('Updating mtime for {}: file {}, meta {}, mtime {}'
1322
1354
.format (id , path , meta_json , meta .mtime ))
1323
1355
atomic_write (meta_json , meta_str , '\n ' ) # Ignore errors, it's just an optimization.
@@ -1373,12 +1405,20 @@ def write_cache(id: str, path: str, tree: MypyFile,
1373
1405
corresponding to the metadata that was written (the latter may
1374
1406
be None if the cache could not be written).
1375
1407
"""
1376
- # Obtain file paths
1377
- path = os .path .abspath (path )
1408
+ # For Bazel we use relative paths and zero mtimes.
1409
+ bazel = manager .options .bazel
1410
+
1411
+ # Obtain file paths.
1412
+ path = manager .normpath (path )
1378
1413
meta_json , data_json , deps_json = get_cache_names (id , path , manager )
1379
1414
manager .log ('Writing {} {} {} {} {}' .format (
1380
1415
id , path , meta_json , data_json , deps_json ))
1381
1416
1417
+ # Update tree.path so that in bazel mode it's made relative (since
1418
+ # sometimes paths leak out).
1419
+ if bazel :
1420
+ tree .path = path
1421
+
1382
1422
# Make sure directory for cache files exists
1383
1423
parent = os .path .dirname (data_json )
1384
1424
assert os .path .dirname (meta_json ) == parent
@@ -1390,7 +1430,8 @@ def write_cache(id: str, path: str, tree: MypyFile,
1390
1430
1391
1431
# Obtain and set up metadata
1392
1432
try :
1393
- os .makedirs (parent , exist_ok = True )
1433
+ if parent :
1434
+ os .makedirs (parent , exist_ok = True )
1394
1435
st = manager .get_stat (path )
1395
1436
except OSError as err :
1396
1437
manager .log ("Cannot get stat for {}: {}" .format (path , err ))
@@ -1405,10 +1446,11 @@ def write_cache(id: str, path: str, tree: MypyFile,
1405
1446
return interface_hash , None
1406
1447
1407
1448
# Write data cache file, if applicable
1449
+ # Note that for Bazel we don't record the data file's mtime.
1408
1450
if old_interface_hash == interface_hash :
1409
1451
# If the interface is unchanged, the cached data is guaranteed
1410
1452
# to be equivalent, and we only need to update the metadata.
1411
- data_mtime = getmtime (data_json )
1453
+ data_mtime = manager . getmtime (data_json )
1412
1454
manager .trace ("Interface for {} is unchanged" .format (id ))
1413
1455
else :
1414
1456
manager .trace ("Interface for {} has changed" .format (id ))
@@ -1425,17 +1467,17 @@ def write_cache(id: str, path: str, tree: MypyFile,
1425
1467
# Both have the effect of slowing down the next run a
1426
1468
# little bit due to an out-of-date cache file.
1427
1469
return interface_hash , None
1428
- data_mtime = getmtime (data_json )
1470
+ data_mtime = manager . getmtime (data_json )
1429
1471
1430
1472
deps_mtime = None
1431
1473
if deps_json :
1432
1474
deps_str = json_dumps (serialized_fine_grained_deps , manager .options .debug_cache )
1433
1475
if not atomic_write (deps_json , deps_str , '\n ' ):
1434
1476
manager .log ("Error writing deps JSON file {}" .format (deps_json ))
1435
1477
return interface_hash , None
1436
- deps_mtime = getmtime (deps_json )
1478
+ deps_mtime = manager . getmtime (deps_json )
1437
1479
1438
- mtime = int (st .st_mtime )
1480
+ mtime = 0 if bazel else int (st .st_mtime )
1439
1481
size = st .st_size
1440
1482
options = manager .options .clone_for_module (id )
1441
1483
assert source_hash is not None
@@ -1475,7 +1517,7 @@ def delete_cache(id: str, path: str, manager: BuildManager) -> None:
1475
1517
This avoids inconsistent states with cache files from different mypy runs,
1476
1518
see #4043 for an example.
1477
1519
"""
1478
- path = os . path . abspath (path )
1520
+ path = manager . normpath (path )
1479
1521
cache_paths = get_cache_names (id , path , manager )
1480
1522
manager .log ('Deleting {} {} {}' .format (id , path , " " .join (x for x in cache_paths if x )))
1481
1523
0 commit comments