Skip to content

Commit e05fe58

Browse files
authored
Enable generation and caching of fine-grained dependencies from normal runs (#4526)
1 parent 16db987 commit e05fe58

File tree

4 files changed

+39
-20
lines changed

4 files changed

+39
-20
lines changed

mypy/build.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
from mypy.version import __version__
5353
from mypy.plugin import Plugin, DefaultPlugin, ChainedPlugin
5454
from mypy.defaults import PYTHON3_VERSION_MIN
55+
from mypy.server.deps import get_dependencies
5556

5657

5758
PYTHON_EXTENSIONS = ['.pyi', '.py']
@@ -1183,6 +1184,7 @@ def compute_hash(text: str) -> str:
11831184

11841185

11851186
def write_cache(id: str, path: str, tree: MypyFile,
1187+
serialized_fine_grained_deps: Dict[str, List[str]],
11861188
dependencies: List[str], suppressed: List[str],
11871189
child_modules: List[str], dep_prios: List[int],
11881190
old_interface_hash: str, source_hash: str,
@@ -1221,7 +1223,9 @@ def write_cache(id: str, path: str, tree: MypyFile,
12211223
assert os.path.dirname(meta_json) == parent
12221224

12231225
# Serialize data and analyze interface
1224-
data = tree.serialize()
1226+
data = {'tree': tree.serialize(),
1227+
'fine_grained_deps': serialized_fine_grained_deps,
1228+
}
12251229
if manager.options.debug_cache:
12261230
data_str = json.dumps(data, indent=2, sort_keys=True)
12271231
else:
@@ -1523,6 +1527,8 @@ class State:
15231527
# Whether the module has an error or any of its dependencies have one.
15241528
transitive_error = False
15251529

1530+
fine_grained_deps = None # type: Dict[str, Set[str]]
1531+
15261532
# Type checker used for checking this file. Use type_checker() for
15271533
# access and to construct this on demand.
15281534
_type_checker = None # type: Optional[TypeChecker]
@@ -1551,6 +1557,7 @@ def __init__(self,
15511557
self.id = id or '__main__'
15521558
self.options = manager.options.clone_for_module(self.id)
15531559
self._type_checker = None
1560+
self.fine_grained_deps = {}
15541561
if not path and source is None:
15551562
assert id is not None
15561563
file_id = id
@@ -1734,7 +1741,9 @@ def load_tree(self) -> None:
17341741
with open(self.meta.data_json) as f:
17351742
data = json.load(f)
17361743
# TODO: Assert data file wasn't changed.
1737-
self.tree = MypyFile.deserialize(data)
1744+
self.tree = MypyFile.deserialize(data['tree'])
1745+
self.fine_grained_deps = {k: set(v) for k, v in data['fine_grained_deps'].items()}
1746+
17381747
self.manager.modules[self.id] = self.tree
17391748
self.manager.add_stats(fresh_trees=1)
17401749

@@ -1977,6 +1986,19 @@ def _patch_indirect_dependencies(self,
19771986
elif dep not in self.suppressed and dep in self.manager.missing_modules:
19781987
self.suppressed.append(dep)
19791988

1989+
def compute_fine_grained_deps(self) -> None:
1990+
assert self.tree is not None
1991+
if '/typeshed/' in self.xpath or self.xpath.startswith('typeshed/'):
1992+
# We don't track changes to typeshed -- the assumption is that they are only changed
1993+
# as part of mypy updates, which will invalidate everything anyway.
1994+
#
1995+
# TODO: Not a reliable test, as we could have a package named typeshed.
1996+
# TODO: Consider relaxing this -- maybe allow some typeshed changes to be tracked.
1997+
return
1998+
self.fine_grained_deps = get_dependencies(target=self.tree,
1999+
type_map=self.type_map(),
2000+
python_version=self.options.python_version)
2001+
19802002
def valid_references(self) -> Set[str]:
19812003
assert self.ancestors is not None
19822004
valid_refs = set(self.dependencies + self.suppressed + self.ancestors)
@@ -2003,6 +2025,7 @@ def write_cache(self) -> None:
20032025
dep_prios = self.dependency_priorities()
20042026
new_interface_hash, self.meta = write_cache(
20052027
self.id, self.path, self.tree,
2028+
{k: list(v) for k, v in self.fine_grained_deps.items()},
20062029
list(self.dependencies), list(self.suppressed), list(self.child_modules),
20072030
dep_prios, self.interface_hash, self.source_hash, self.ignore_all,
20082031
self.manager)
@@ -2534,6 +2557,8 @@ def process_stale_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No
25342557
graph[id].transitive_error = True
25352558
for id in stale:
25362559
graph[id].finish_passes()
2560+
if manager.options.cache_fine_grained:
2561+
graph[id].compute_fine_grained_deps()
25372562
graph[id].generate_unused_ignore_notes()
25382563
manager.flush_errors(manager.errors.file_messages(graph[id].xpath), False)
25392564
graph[id].write_cache()

mypy/main.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,8 @@ def add_invertible_flag(flag: str,
320320
parser.add_argument('--cache-dir', action='store', metavar='DIR',
321321
help="store module cache info in the given folder in incremental mode "
322322
"(defaults to '{}')".format(defaults.CACHE_DIR))
323+
parser.add_argument('--cache-fine-grained', action='store_true',
324+
help="include fine-grained dependency information in the cache")
323325
parser.add_argument('--skip-version-check', action='store_true',
324326
help="allow using cache written by older mypy version")
325327
add_invertible_flag('--strict-optional', default=False, strict_flag=True,

mypy/options.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ class Options:
4242
"disallow_untyped_decorators",
4343
}
4444

45-
OPTIONS_AFFECTING_CACHE = ((PER_MODULE_OPTIONS | {"quick_and_dirty", "platform"})
45+
OPTIONS_AFFECTING_CACHE = ((PER_MODULE_OPTIONS |
46+
{"quick_and_dirty", "platform", "cache_fine_grained"})
4647
- {"debug_cache"})
4748

4849
def __init__(self) -> None:
@@ -142,6 +143,7 @@ def __init__(self) -> None:
142143
self.quick_and_dirty = False
143144
self.skip_version_check = False
144145
self.fine_grained_incremental = False
146+
self.cache_fine_grained = False
145147

146148
# Paths of user plugins
147149
self.plugins = [] # type: List[str]

mypy/server/update.py

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ def update_single(self, module: str, path: str) -> Tuple[List[str],
280280
if not trigger.endswith('__>')]
281281
print('triggered:', sorted(filtered))
282282
self.triggered.extend(triggered | self.previous_targets_with_errors)
283-
update_dependencies({module: tree}, self.deps, graph, self.options)
283+
collect_dependencies({module: tree}, self.deps, graph)
284284
propagate_changes_using_dependencies(manager, graph, self.deps, triggered,
285285
{module},
286286
self.previous_targets_with_errors)
@@ -319,7 +319,7 @@ def get_all_dependencies(manager: BuildManager, graph: Dict[str, State],
319319
options: Options) -> Dict[str, Set[str]]:
320320
"""Return the fine-grained dependency map for an entire build."""
321321
deps = {} # type: Dict[str, Set[str]]
322-
update_dependencies(manager.modules, deps, graph, options)
322+
collect_dependencies(manager.modules, deps, graph)
323323
return deps
324324

325325

@@ -644,24 +644,14 @@ def find_import_line(node: MypyFile, target: str) -> Optional[int]:
644644
return None
645645

646646

647-
def update_dependencies(new_modules: Mapping[str, Optional[MypyFile]],
648-
deps: Dict[str, Set[str]],
649-
graph: Dict[str, State],
650-
options: Options) -> None:
647+
def collect_dependencies(new_modules: Mapping[str, Optional[MypyFile]],
648+
deps: Dict[str, Set[str]],
649+
graph: Dict[str, State]) -> None:
651650
for id, node in new_modules.items():
652651
if node is None:
653652
continue
654-
if '/typeshed/' in node.path or node.path.startswith('typeshed/'):
655-
# We don't track changes to typeshed -- the assumption is that they are only changed
656-
# as part of mypy updates, which will invalidate everything anyway.
657-
#
658-
# TODO: Not a reliable test, as we could have a package named typeshed.
659-
# TODO: Consider relaxing this -- maybe allow some typeshed changes to be tracked.
660-
continue
661-
module_deps = get_dependencies(target=node,
662-
type_map=graph[id].type_map(),
663-
python_version=options.python_version)
664-
for trigger, targets in module_deps.items():
653+
graph[id].compute_fine_grained_deps()
654+
for trigger, targets in graph[id].fine_grained_deps.items():
665655
deps.setdefault(trigger, set()).update(targets)
666656

667657

0 commit comments

Comments
 (0)