48
48
PYTHON_EXTENSIONS = ['.pyi' , '.py' ]
49
49
50
50
51
+ Graph = Dict [str , 'State' ]
52
+
53
+
51
54
class BuildResult :
52
55
"""The result of a successful build.
53
56
@@ -703,12 +706,14 @@ def find_cache_meta(id: str, path: str, manager: BuildManager) -> Optional[Cache
703
706
id , path , manager .options .cache_dir , manager .options .python_version )
704
707
manager .trace ('Looking for {} {}' .format (id , data_json ))
705
708
if not os .path .exists (meta_json ):
709
+ manager .trace ('Could not load cache for {}: could not find {}' .format (id , meta_json ))
706
710
return None
707
711
with open (meta_json , 'r' ) as f :
708
712
meta_str = f .read ()
709
713
manager .trace ('Meta {} {}' .format (id , meta_str .rstrip ()))
710
714
meta = json .loads (meta_str ) # TODO: Errors
711
715
if not isinstance (meta , dict ):
716
+ manager .trace ('Could not load cache for {}: meta cache is not a dict' .format (id ))
712
717
return None
713
718
path = os .path .abspath (path )
714
719
m = CacheMeta (
@@ -728,32 +733,36 @@ def find_cache_meta(id: str, path: str, manager: BuildManager) -> Optional[Cache
728
733
if (m .id != id or m .path != path or
729
734
m .mtime is None or m .size is None or
730
735
m .dependencies is None or m .data_mtime is None ):
736
+ manager .trace ('Metadata abandoned for {}: attributes are missing' .format (id ))
731
737
return None
732
738
733
739
# Ignore cache if generated by an older mypy version.
734
740
if (m .version_id != manager .version_id
735
741
or m .options is None
736
742
or len (m .dependencies ) != len (m .dep_prios )):
743
+ manager .trace ('Metadata abandoned for {}: new attributes are missing' .format (id ))
737
744
return None
738
745
739
746
# Ignore cache if (relevant) options aren't the same.
740
747
cached_options = m .options
741
748
current_options = select_options_affecting_cache (manager .options )
742
749
if cached_options != current_options :
750
+ manager .trace ('Metadata abandoned for {}: options differ' .format (id ))
743
751
return None
744
752
745
753
# TODO: Share stat() outcome with find_module()
746
754
st = os .stat (path ) # TODO: Errors
747
755
if st .st_mtime != m .mtime or st .st_size != m .size :
748
- manager .log ('Metadata abandoned because of modified file {}' .format (path ))
756
+ manager .log ('Metadata abandoned for {}: file {} is modified ' .format (id , path ))
749
757
return None
750
758
751
759
# It's a match on (id, path, mtime, size).
752
760
# Check data_json; assume if its mtime matches it's good.
753
761
# TODO: stat() errors
754
762
if os .path .getmtime (data_json ) != m .data_mtime :
763
+ manager .log ('Metadata abandoned for {}: data cache is modified' .format (id ))
755
764
return None
756
- manager .log ('Found {} {}' .format (id , meta_json ))
765
+ manager .log ('Found {} {} (metadata is fresh) ' .format (id , meta_json ))
757
766
return m
758
767
759
768
@@ -1200,6 +1209,40 @@ def fix_cross_refs(self) -> None:
1200
1209
def calculate_mros (self ) -> None :
1201
1210
fixup_module_pass_two (self .tree , self .manager .modules )
1202
1211
1212
+ def fix_suppressed_dependencies (self , graph : Graph ) -> None :
1213
+ """Corrects whether dependencies are considered stale or not when using silent_imports.
1214
+
1215
+ This method is a hack to correct imports in silent_imports + incremental mode.
1216
+ In particular, the problem is that when running mypy with a cold cache, the
1217
+ `parse_file(...)` function is called *at the start* of the `load_graph(...)` function.
1218
+ Note that load_graph will mark some dependencies as suppressed if they weren't specified
1219
+ on the command line in silent_imports mode.
1220
+
1221
+ However, if the interface for a module is changed, parse_file will be called within
1222
+ `process_stale_scc` -- *after* load_graph is finished, wiping out the changes load_graph
1223
+ previously made.
1224
+
1225
+ This method is meant to be run after parse_file finishes in process_stale_scc and will
1226
+ recompute what modules should be considered suppressed in silent_import mode.
1227
+ """
1228
+ # TODO: See if it's possible to move this check directly into parse_file in some way.
1229
+ # TODO: Find a way to write a test case for this fix.
1230
+ silent_mode = self .manager .options .silent_imports or self .manager .options .almost_silent
1231
+ if not silent_mode :
1232
+ return
1233
+
1234
+ new_suppressed = []
1235
+ new_dependencies = []
1236
+ entry_points = self .manager .source_set .source_modules
1237
+ for dep in self .dependencies + self .suppressed :
1238
+ ignored = dep in self .suppressed and dep not in entry_points
1239
+ if ignored or dep not in graph :
1240
+ new_suppressed .append (dep )
1241
+ else :
1242
+ new_dependencies .append (dep )
1243
+ self .dependencies = new_dependencies
1244
+ self .suppressed = new_suppressed
1245
+
1203
1246
# Methods for processing modules from source code.
1204
1247
1205
1248
def parse_file (self ) -> None :
@@ -1325,9 +1368,6 @@ def write_cache(self) -> None:
1325
1368
self .manager )
1326
1369
1327
1370
1328
- Graph = Dict [str , State ]
1329
-
1330
-
1331
1371
def dispatch (sources : List [BuildSource ], manager : BuildManager ) -> None :
1332
1372
manager .log ("Mypy version %s" % __version__ )
1333
1373
graph = load_graph (sources , manager )
@@ -1556,6 +1596,7 @@ def process_stale_scc(graph: Graph, scc: List[str]) -> None:
1556
1596
# We may already have parsed the module, or not.
1557
1597
# If the former, parse_file() is a no-op.
1558
1598
graph [id ].parse_file ()
1599
+ graph [id ].fix_suppressed_dependencies (graph )
1559
1600
for id in scc :
1560
1601
graph [id ].patch_parent ()
1561
1602
for id in scc :
0 commit comments