Skip to content

Commit fdc84ed

Browse files
committed
Compute fine-grained dependencies during normal operation and store them
1 parent eed5f01 commit fdc84ed

File tree

4 files changed

+43
-21
lines changed

4 files changed

+43
-21
lines changed

mypy/build.py

Lines changed: 31 additions & 3 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']
@@ -386,6 +387,7 @@ def default_lib_path(data_dir: str,
386387
('size', int),
387388
('hash', str),
388389
('dependencies', List[str]), # names of imported modules
390+
('fine_grained_deps', Dict[str, List[str]]),
389391
('data_mtime', int), # mtime of data_json
390392
('data_json', str), # path of <id>.data.json
391393
('suppressed', List[str]), # dependencies that weren't imported
@@ -411,6 +413,7 @@ def cache_meta_from_dict(meta: Dict[str, Any], data_json: str) -> CacheMeta:
411413
meta.get('size', sentinel),
412414
meta.get('hash', sentinel),
413415
meta.get('dependencies', []),
416+
meta.get('fine_grained_deps', {}),
414417
int(meta['data_mtime']) if 'data_mtime' in meta else sentinel,
415418
data_json,
416419
meta.get('suppressed', []),
@@ -1151,6 +1154,7 @@ def validate_meta(meta: Optional[CacheMeta], id: str, path: Optional[str],
11511154
'hash': source_hash,
11521155
'data_mtime': data_mtime,
11531156
'dependencies': meta.dependencies,
1157+
'fine_grained_deps': meta.fine_grained_deps,
11541158
'suppressed': meta.suppressed,
11551159
'child_modules': meta.child_modules,
11561160
'options': (manager.options.clone_for_module(id)
@@ -1183,7 +1187,9 @@ def compute_hash(text: str) -> str:
11831187

11841188

11851189
def write_cache(id: str, path: str, tree: MypyFile,
1186-
dependencies: List[str], suppressed: List[str],
1190+
dependencies: List[str],
1191+
fine_grained_deps: Dict[str, List[str]],
1192+
suppressed: List[str],
11871193
child_modules: List[str], dep_prios: List[int],
11881194
old_interface_hash: str, source_hash: str,
11891195
ignore_all: bool, manager: BuildManager) -> Tuple[str, Optional[CacheMeta]]:
@@ -1278,6 +1284,7 @@ def write_cache(id: str, path: str, tree: MypyFile,
12781284
'hash': source_hash,
12791285
'data_mtime': data_mtime,
12801286
'dependencies': dependencies,
1287+
'fine_grained_deps': fine_grained_deps,
12811288
'suppressed': suppressed,
12821289
'child_modules': child_modules,
12831290
'options': options.select_options_affecting_cache(),
@@ -1486,7 +1493,7 @@ class State:
14861493
data = None # type: Optional[str]
14871494
tree = None # type: Optional[MypyFile]
14881495
is_from_saved_cache = False # True if the tree came from the in-memory cache
1489-
dependencies = None # type: List[str] # Modules directly imported by the module
1496+
dependencies = None # type: List[str]
14901497
suppressed = None # type: List[str] # Suppressed/missing dependencies
14911498
priorities = None # type: Dict[str, int]
14921499

@@ -1523,6 +1530,8 @@ class State:
15231530
# Whether the module has an error or any of its dependencies have one.
15241531
transitive_error = False
15251532

1533+
fine_grained_deps = None # type: Dict[str, Set[str]]
1534+
15261535
# Type checker used for checking this file. Use type_checker() for
15271536
# access and to construct this on demand.
15281537
_type_checker = None # type: Optional[TypeChecker]
@@ -1622,6 +1631,7 @@ def __init__(self,
16221631
# Make copies, since we may modify these and want to
16231632
# compare them to the originals later.
16241633
self.dependencies = list(self.meta.dependencies)
1634+
self.fine_grained_deps = {k: set(v) for k, v in self.meta.fine_grained_deps.items()}
16251635
self.suppressed = list(self.meta.suppressed)
16261636
assert len(self.meta.dependencies) == len(self.meta.dep_prios)
16271637
self.priorities = {id: pri
@@ -1630,6 +1640,7 @@ def __init__(self,
16301640
self.dep_line_map = {}
16311641
else:
16321642
# Parse the file (and then some) to get the dependencies.
1643+
self.fine_grained_deps = {}
16331644
self.parse_file()
16341645
self.suppressed = []
16351646
self.child_modules = set()
@@ -1977,6 +1988,19 @@ def _patch_indirect_dependencies(self,
19771988
elif dep not in self.suppressed and dep in self.manager.missing_modules:
19781989
self.suppressed.append(dep)
19791990

1991+
def find_fine_grained_deps(self) -> None:
1992+
assert self.tree is not None
1993+
if '/typeshed/' in self.xpath or self.xpath.startswith('typeshed/'):
1994+
# We don't track changes to typeshed -- the assumption is that they are only changed
1995+
# as part of mypy updates, which will invalidate everything anyway.
1996+
#
1997+
# TODO: Not a reliable test, as we could have a package named typeshed.
1998+
# TODO: Consider relaxing this -- maybe allow some typeshed changes to be tracked.
1999+
return
2000+
self.fine_grained_deps = get_dependencies(target=self.tree,
2001+
type_map=self.type_map(),
2002+
python_version=self.options.python_version)
2003+
19802004
def valid_references(self) -> Set[str]:
19812005
assert self.ancestors is not None
19822006
valid_refs = set(self.dependencies + self.suppressed + self.ancestors)
@@ -2003,7 +2027,9 @@ def write_cache(self) -> None:
20032027
dep_prios = self.dependency_priorities()
20042028
new_interface_hash, self.meta = write_cache(
20052029
self.id, self.path, self.tree,
2006-
list(self.dependencies), list(self.suppressed), list(self.child_modules),
2030+
list(self.dependencies),
2031+
{k: list(v) for k, v in self.fine_grained_deps.items()},
2032+
list(self.suppressed), list(self.child_modules),
20072033
dep_prios, self.interface_hash, self.source_hash, self.ignore_all,
20082034
self.manager)
20092035
if new_interface_hash == self.interface_hash:
@@ -2534,6 +2560,8 @@ def process_stale_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No
25342560
graph[id].transitive_error = True
25352561
for id in stale:
25362562
graph[id].finish_passes()
2563+
if manager.options.cache_fine_grained:
2564+
graph[id].find_fine_grained_deps()
25372565
graph[id].generate_unused_ignore_notes()
25382566
manager.flush_errors(manager.errors.file_messages(graph[id].xpath), False)
25392567
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="included 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
@@ -278,7 +278,7 @@ def update_single(self, module: str, path: str) -> Tuple[List[str],
278278
if not trigger.endswith('__>')]
279279
print('triggered:', sorted(filtered))
280280
self.triggered.extend(triggered | self.previous_targets_with_errors)
281-
update_dependencies({module: tree}, self.deps, graph, self.options)
281+
collect_dependencies({module: tree}, self.deps, graph)
282282
propagate_changes_using_dependencies(manager, graph, self.deps, triggered,
283283
{module},
284284
self.previous_targets_with_errors,
@@ -318,7 +318,7 @@ def get_all_dependencies(manager: BuildManager, graph: Dict[str, State],
318318
options: Options) -> Dict[str, Set[str]]:
319319
"""Return the fine-grained dependency map for an entire build."""
320320
deps = {} # type: Dict[str, Set[str]]
321-
update_dependencies(manager.modules, deps, graph, options)
321+
collect_dependencies(manager.modules, deps, graph)
322322
return deps
323323

324324

@@ -640,24 +640,14 @@ def find_import_line(node: MypyFile, target: str) -> Optional[int]:
640640
return None
641641

642642

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

663653

0 commit comments

Comments
 (0)