6969 Tag ,
7070 WriteBuffer ,
7171 read_bytes ,
72+ read_errors ,
7273 read_int ,
7374 read_int_list ,
7475 read_int_opt ,
7576 read_str ,
7677 read_str_list ,
7778 read_str_opt ,
7879 write_bytes ,
80+ write_errors ,
7981 write_int ,
8082 write_int_list ,
8183 write_int_opt ,
@@ -1626,6 +1628,13 @@ def create_metastore(options: Options, parallel_worker: bool = False) -> Metadat
16261628 return mds
16271629
16281630
1631+ def get_errors_name (meta_name : str ) -> str :
1632+ # Convert e.g. foo.bar.meta.ff to foo.bar.err.ff
1633+ parts = meta_name .rsplit ("." , maxsplit = 2 )
1634+ parts [1 ] = "err"
1635+ return "." .join (parts )
1636+
1637+
16291638def get_cache_names (id : str , path : str , options : Options ) -> tuple [str , str , str | None ]:
16301639 """Return the file names for the cache files.
16311640
@@ -1688,7 +1697,7 @@ def options_snapshot(id: str, manager: BuildManager) -> dict[str, object]:
16881697
16891698def find_cache_meta (
16901699 id : str , path : str , manager : BuildManager , skip_validation : bool = False
1691- ) -> CacheMeta | None :
1700+ ) -> tuple [ CacheMeta | None , list [ ErrorTuple ]] :
16921701 """Find cache data for a module.
16931702
16941703 Args:
@@ -1705,29 +1714,21 @@ def find_cache_meta(
17051714 meta_file , data_file , _ = get_cache_names (id , path , manager .options )
17061715 if manager .tracing_enabled :
17071716 manager .trace (f"Looking for { id } at { meta_file } " )
1708- meta : bytes | dict [str , Any ] | None
17091717 if manager .stats_enabled :
17101718 t0 = time .time ()
17111719 if manager .options .fixed_format_cache :
17121720 meta = _load_ff_file (
17131721 meta_file , manager , log_error_fmt = "Could not load cache for {}: " , id = id
17141722 )
1715- if meta is None :
1716- return None
17171723 else :
17181724 meta = _load_json_file (
17191725 meta_file ,
17201726 manager ,
17211727 log_success = f"Meta { id } " ,
17221728 log_error = f"Could not load cache for { id } : " ,
17231729 )
1724- if meta is None :
1725- return None
1726- if not isinstance (meta , dict ):
1727- manager .log ( # type: ignore[unreachable]
1728- f"Could not load cache for { id } : meta cache is not a dict: { repr (meta )} "
1729- )
1730- return None
1730+ if meta is None :
1731+ return None , []
17311732 if manager .stats_enabled :
17321733 t1 = time .time ()
17331734 if isinstance (meta , bytes ):
@@ -1736,31 +1737,31 @@ def find_cache_meta(
17361737 # TODO: switch to something like librt.internal.read_byte() if this is slow.
17371738 if meta [0 ] != cache_version () or meta [1 ] != CACHE_VERSION :
17381739 manager .log (f"Metadata abandoned for { id } : incompatible cache format" )
1739- return None
1740+ return None , []
17401741 data_io = ReadBuffer (meta [2 :])
17411742 m = CacheMeta .read (data_io , data_file )
17421743 else :
17431744 m = CacheMeta .deserialize (meta , data_file )
17441745 if m is None :
17451746 manager .log (f"Metadata abandoned for { id } : cannot deserialize data" )
1746- return None
1747+ return None , []
17471748 if manager .stats_enabled :
17481749 t2 = time .time ()
17491750 manager .add_stats (
17501751 load_meta_time = t2 - t0 , load_meta_load_time = t1 - t0 , load_meta_from_dict_time = t2 - t1
17511752 )
17521753 if skip_validation :
1753- return m
1754+ return m , []
17541755
17551756 # Ignore cache if generated by an older mypy version.
17561757 if m .version_id != manager .version_id and not manager .options .skip_version_check :
17571758 manager .log (f"Metadata abandoned for { id } : different mypy version" )
1758- return None
1759+ return None , []
17591760
17601761 total_deps = len (m .dependencies ) + len (m .suppressed )
17611762 if len (m .dep_prios ) != total_deps or len (m .dep_lines ) != total_deps :
17621763 manager .log (f"Metadata abandoned for { id } : broken dependencies" )
1763- return None
1764+ return None , []
17641765
17651766 # Ignore cache if (relevant) options aren't the same.
17661767 # Note that it's fine to mutilate cached_options since it's only used here.
@@ -1782,12 +1783,12 @@ def find_cache_meta(
17821783 key , cached_options .get (key ), current_options .get (key )
17831784 )
17841785 )
1785- return None
1786+ return None , []
17861787 if manager .old_plugins_snapshot and manager .plugins_snapshot :
17871788 # Check if plugins are still the same.
17881789 if manager .plugins_snapshot != manager .old_plugins_snapshot :
17891790 manager .log (f"Metadata abandoned for { id } : plugins differ" )
1790- return None
1791+ return None , []
17911792 plugin_data = manager .plugin .report_config_data (ReportConfigContext (id , path , is_check = True ))
17921793 if not manager .options .fixed_format_cache :
17931794 # So that plugins can return data with tuples in it without
@@ -1796,10 +1797,31 @@ def find_cache_meta(
17961797 plugin_data = json_loads (json_dumps (plugin_data ))
17971798 if m .plugin_data != plugin_data :
17981799 manager .log (f"Metadata abandoned for { id } : plugin configuration differs" )
1799- return None
1800+ return None , []
18001801
1802+ # Load cached errors for this file, even if empty. This is needed to avoid
1803+ # invalid cache state after a crash/blocker/Ctrl+C etc.
1804+ errors_file = get_errors_name (meta_file )
1805+ if manager .options .fixed_format_cache :
1806+ errors = _load_ff_file (
1807+ errors_file , manager , log_error_fmt = "Could not load errors for {}: " , id = id
1808+ )
1809+ else :
1810+ errors = _load_json_file (
1811+ errors_file ,
1812+ manager ,
1813+ log_success = f"Errors { id } " ,
1814+ log_error = f"Could not load errors for { id } : " ,
1815+ )
1816+ if errors is None :
1817+ return None , []
1818+ if isinstance (errors , bytes ):
1819+ data_io = ReadBuffer (errors )
1820+ e = read_errors (data_io )
1821+ else :
1822+ e = [tuple (err ) for err in errors ["error_lines" ]]
18011823 manager .add_stats (fresh_metas = 1 )
1802- return m
1824+ return m , e
18031825
18041826
18051827def validate_meta (
@@ -2078,9 +2100,8 @@ def write_cache(
20782100 version_id = manager .version_id ,
20792101 ignore_all = ignore_all ,
20802102 plugin_data = plugin_data ,
2081- # These two will be filled by the caller.
2103+ # This one will be filled by the caller.
20822104 dep_hashes = [],
2083- error_lines = [],
20842105 )
20852106 return interface_hash , (meta , meta_file )
20862107
@@ -2104,6 +2125,23 @@ def write_cache_meta(meta: CacheMeta, manager: BuildManager, meta_file: str) ->
21042125 manager .log (f"Error writing cache meta file { meta_file } " )
21052126
21062127
2128+ def write_errors_file (
2129+ meta_file : str , error_lines : list [ErrorTuple ], manager : BuildManager
2130+ ) -> None :
2131+ # Write errors cache file
2132+ errors_file = get_errors_name (meta_file )
2133+ metastore = manager .metastore
2134+ if manager .options .fixed_format_cache :
2135+ data_io = WriteBuffer ()
2136+ write_errors (data_io , error_lines )
2137+ meta_bytes = data_io .getvalue ()
2138+ else :
2139+ # Some generic JSON helpers require top-level to be a dict.
2140+ meta_bytes = json_dumps ({"error_lines" : error_lines }, manager .options .debug_cache )
2141+ if not metastore .write (errors_file , meta_bytes ):
2142+ manager .log (f"Error writing errors file { errors_file } " )
2143+
2144+
21072145"""Dependency manager.
21082146
21092147Design
@@ -2393,7 +2431,7 @@ def new_state(
23932431 interface_hash = b""
23942432 meta_source_hash = None
23952433 if path and source is None and manager .cache_enabled :
2396- meta = find_cache_meta (id , path , manager )
2434+ meta , error_lines = find_cache_meta (id , path , manager )
23972435 # TODO: Get mtime if not cached.
23982436 if meta is not None :
23992437 interface_hash = meta .interface_hash
@@ -2420,7 +2458,7 @@ def new_state(
24202458 assert len (meta .dep_hashes ) == len (meta .dependencies )
24212459 dep_hashes = {k : v for (k , v ) in zip (meta .dependencies , meta .dep_hashes )}
24222460 # Only copy `error_lines` if the module is not silently imported.
2423- error_lines = [] if ignore_all else meta . error_lines
2461+ error_lines = [] if ignore_all else error_lines
24242462 imports_ignored = meta .imports_ignored
24252463 else :
24262464 dependencies = []
@@ -2656,7 +2694,7 @@ def reload_meta(self) -> None:
26562694 the interface hash.
26572695 """
26582696 assert self .path is not None
2659- self .meta = find_cache_meta (self .id , self .path , self .manager , skip_validation = True )
2697+ self .meta , _ = find_cache_meta (self .id , self .path , self .manager , skip_validation = True )
26602698 assert self .meta is not None
26612699 self .interface_hash = self .meta .interface_hash
26622700
@@ -4355,8 +4393,8 @@ def process_stale_scc(
43554393 continue
43564394 meta , meta_file = meta_tuple
43574395 meta .dep_hashes = [graph [dep ].interface_hash for dep in graph [id ].dependencies ]
4358- meta .error_lines = errors_by_id .get (id , [])
43594396 write_cache_meta (meta , manager , meta_file )
4397+ write_errors_file (meta_file , errors_by_id .get (id , []), manager )
43604398 manager .done_sccs .add (ascc .id )
43614399 manager .add_stats (
43624400 load_missing_time = t1 - t0 ,
0 commit comments