forked from facebook/pyre-check
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconfiguration_monitor.py
134 lines (120 loc) · 5.31 KB
/
configuration_monitor.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# Copyright (c) 2016-present, Facebook, Inc.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
import os
from pathlib import Path
from typing import Any, Dict, List, Optional
from .analysis_directory import AnalysisDirectory
from .commands import stop
from .commands.command import CommandArguments
from .configuration import Configuration
from .find_directories import CONFIGURATION_FILE, LOCAL_CONFIGURATION_FILE
# We use the `LOG` from watchman due to its better formatting in log files
from .watchman import LOG, Subscriber, Subscription
class ConfigurationMonitor(Subscriber):
"""
The ConfigurationMonitor watches only for .pyre_configuration(.local)
files, and will kill the corresponding server when a configuration changes.
Logs are found in
.pyre/<local-root>/configuration_monitor/configuration_monitor.log
To kill a monitor, get pid from
.pyre/<local-root>/configuration_monitor/configuration_monitor.pid
and kill <pid>.
"""
def __init__(
self,
command_arguments: CommandArguments,
configuration: Configuration,
analysis_directory: AnalysisDirectory,
project_root: str,
original_directory: str,
local_configuration_root: Optional[str],
other_critical_files: List[str],
) -> None:
super(ConfigurationMonitor, self).__init__(self.base_path(configuration))
self._command_arguments = command_arguments
self._configuration = configuration
self._analysis_directory = analysis_directory
self._project_root_path: Path = Path(project_root).resolve()
self._original_directory = original_directory
self._local_configuration_root = local_configuration_root
self._other_critical_files = other_critical_files
NAME = "configuration_monitor"
@property
def _name(self) -> str:
return ConfigurationMonitor.NAME
@staticmethod
def base_path(configuration: Configuration) -> str:
return os.path.join(configuration.log_directory, ConfigurationMonitor.NAME)
@property
def _subscriptions(self) -> List[Subscription]:
roots = self._watchman_client.query("watch-list")["roots"]
LOG.debug(f"Watchman roots: {roots}")
for root in roots:
root_path = Path(root).resolve()
if root_path in (self._project_root_path, *self._project_root_path.parents):
name = "pyre_monitor_{}".format(os.path.basename(root))
subscription = {
"empty_on_fresh_instance": True,
"expression": [
"allof",
["type", "f"],
[
"anyof",
["suffix", "pyre_configuration.local"],
["suffix", "pyre_configuration"],
*[
["match", critical]
for critical in self._other_critical_files
],
],
],
"fields": ["name"],
}
subscription = Subscription(root, name, subscription)
LOG.debug(f"Configuration monitor subscription: {subscription}")
return [subscription]
LOG.debug("Configuration monitor is not subscribed to any paths.")
return []
def _stop(self) -> None:
stop.Stop(
self._command_arguments,
self._original_directory,
configuration=self._configuration,
analysis_directory=self._analysis_directory,
).run()
def _handle_response(self, response: Dict[str, Any]) -> None:
watchman_root = response.get("root", None)
paths = response.get("files", None)
if watchman_root is None or paths is None:
# Skip the Watchman response because it does not have both `root`
# and `files`.
return
absolute_paths = [Path(watchman_root, path).resolve() for path in paths]
LOG.info(f"Update to configuration or other critical files at {absolute_paths}")
root_configuration_path = self._project_root_path / CONFIGURATION_FILE
local_configuration_root = self._local_configuration_root
if root_configuration_path in absolute_paths:
LOG.info("Pyre configuration changed. Stopping pyre server.")
# TODO(T54088045): Find all local pyre servers running underneath
# and stop them.
self._stop()
elif (
local_configuration_root is not None
and Path(local_configuration_root, LOCAL_CONFIGURATION_FILE).resolve()
in absolute_paths
):
LOG.info("Local configuration changed. Stopping pyre server.")
self._stop()
elif any(
self._project_root_path / critical in absolute_paths
for critical in self._other_critical_files
):
LOG.info("Critical file changed. Stopping pyre server.")
self._stop()
else:
LOG.info(
"None of the changed paths correspond to the root "
"configuration, a local pyre configuration, or any other critical file."
)