Skip to content

Commit 92101f3

Browse files
authored
Force-discard cache if cache format changed (#20152)
If either low-level (i.e. `librt`) or high-level cache format changes, discard the cache. Note I intentionally don't use `librt` to read/write the first two bytes of cache meta, se we are 100% sure we can always read them.
1 parent 98d7930 commit 92101f3

File tree

5 files changed

+24
-8
lines changed

5 files changed

+24
-8
lines changed

mypy-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ typing_extensions>=4.6.0
44
mypy_extensions>=1.0.0
55
pathspec>=0.9.0
66
tomli>=1.1.0; python_version<'3.11'
7-
librt>=0.3.0
7+
librt>=0.4.0

mypy/build.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@
2828
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Final, NoReturn, TextIO, TypedDict
2929
from typing_extensions import TypeAlias as _TypeAlias
3030

31+
from librt.internal import cache_version
32+
3133
import mypy.semanal_main
32-
from mypy.cache import Buffer, CacheMeta
34+
from mypy.cache import CACHE_VERSION, Buffer, CacheMeta
3335
from mypy.checker import TypeChecker
3436
from mypy.error_formatter import OUTPUT_CHOICES, ErrorFormatter
3537
from mypy.errors import CompileError, ErrorInfo, Errors, report_internal_error
@@ -1334,12 +1336,18 @@ def find_cache_meta(id: str, path: str, manager: BuildManager) -> CacheMeta | No
13341336
return None
13351337
t1 = time.time()
13361338
if isinstance(meta, bytes):
1337-
data_io = Buffer(meta)
1339+
# If either low-level buffer format or high-level cache layout changed, we
1340+
# cannot use the cache files, even with --skip-version-check.
1341+
# TODO: switch to something like librt.internal.read_byte() if this is slow.
1342+
if meta[0] != cache_version() or meta[1] != CACHE_VERSION:
1343+
manager.log(f"Metadata abandoned for {id}: incompatible cache format")
1344+
return None
1345+
data_io = Buffer(meta[2:])
13381346
m = CacheMeta.read(data_io, data_file)
13391347
else:
13401348
m = CacheMeta.deserialize(meta, data_file)
13411349
if m is None:
1342-
manager.log(f"Metadata abandoned for {id}: attributes are missing")
1350+
manager.log(f"Metadata abandoned for {id}: cannot deserialize data")
13431351
return None
13441352
t2 = time.time()
13451353
manager.add_stats(
@@ -1671,7 +1679,9 @@ def write_cache_meta(meta: CacheMeta, manager: BuildManager, meta_file: str) ->
16711679
if manager.options.fixed_format_cache:
16721680
data_io = Buffer()
16731681
meta.write(data_io)
1674-
meta_bytes = data_io.getvalue()
1682+
# Prefix with both low- and high-level cache format versions for future validation.
1683+
# TODO: switch to something like librt.internal.write_byte() if this is slow.
1684+
meta_bytes = bytes([cache_version(), CACHE_VERSION]) + data_io.getvalue()
16751685
else:
16761686
meta_dict = meta.serialize()
16771687
meta_bytes = json_dumps(meta_dict, manager.options.debug_cache)

mypy/cache.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
serialization. The write method should write both class tag and end tag. The read method
4141
conventionally *does not* read the start tag (to simplify logic for unions). Known exceptions
4242
are MypyFile.read() and SymbolTableNode.read(), since those two never appear in a union.
43+
44+
If any of these details change, or if the structure of CacheMeta changes please
45+
bump CACHE_VERSION below.
4346
"""
4447

4548
from __future__ import annotations
@@ -65,6 +68,9 @@
6568
)
6669
from mypy_extensions import u8
6770

71+
# High-level cache layout format
72+
CACHE_VERSION: Final = 0
73+
6874

6975
class CacheMeta:
7076
"""Class representing cache metadata for a module."""

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ requires = [
99
"mypy_extensions>=1.0.0",
1010
"pathspec>=0.9.0",
1111
"tomli>=1.1.0; python_version<'3.11'",
12-
"librt>=0.3.0",
12+
"librt>=0.4.0",
1313
# the following is from build-requirements.txt
1414
"types-psutil",
1515
"types-setuptools",
@@ -54,7 +54,7 @@ dependencies = [
5454
"mypy_extensions>=1.0.0",
5555
"pathspec>=0.9.0",
5656
"tomli>=1.1.0; python_version<'3.11'",
57-
"librt>=0.3.0",
57+
"librt>=0.4.0",
5858
]
5959
dynamic = ["version"]
6060

test-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ identify==2.6.15
2222
# via pre-commit
2323
iniconfig==2.1.0
2424
# via pytest
25-
librt==0.3.0
25+
librt==0.4.0
2626
# via -r mypy-requirements.txt
2727
lxml==6.0.2 ; python_version < "3.15"
2828
# via -r test-requirements.in

0 commit comments

Comments
 (0)