blob: 8551b9247b84857f4c67825a574b35e1be1fdbb1 [file] [log] [blame]
Junji Watanabe607284d2023-04-20 03:14:52 +00001# Copyright 2023 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""This helper provides a build context that handles
5the reclient lifecycle safely. It will automatically start
6reproxy before running ninja and stop reproxy when build stops
7for any reason e.g. build completion, keyboard interrupt etc."""
8
9import contextlib
10import hashlib
11import os
Ben Segall2e673842023-06-21 14:23:37 +000012import shutil
Ben Segallb64ee7f2023-09-07 15:11:31 +000013import socket
Junji Watanabe607284d2023-04-20 03:14:52 +000014import subprocess
15import sys
Bruce Dawson6d0c2352023-08-06 02:38:51 +000016import time
Junji Watanabe607284d2023-04-20 03:14:52 +000017
18import gclient_paths
Ben Segall530d86d2023-05-29 16:11:00 +000019import reclient_metrics
Junji Watanabe607284d2023-04-20 03:14:52 +000020
21
22def find_reclient_bin_dir():
Mike Frysinger124bb8e2023-09-06 05:48:55 +000023 tools_path = gclient_paths.GetBuildtoolsPath()
24 if not tools_path:
25 return None
Junji Watanabe607284d2023-04-20 03:14:52 +000026
Mike Frysinger124bb8e2023-09-06 05:48:55 +000027 reclient_bin_dir = os.path.join(tools_path, 'reclient')
28 if os.path.isdir(reclient_bin_dir):
29 return reclient_bin_dir
30 return None
Junji Watanabe607284d2023-04-20 03:14:52 +000031
32
33def find_reclient_cfg():
Mike Frysinger124bb8e2023-09-06 05:48:55 +000034 tools_path = gclient_paths.GetBuildtoolsPath()
35 if not tools_path:
36 return None
Junji Watanabe607284d2023-04-20 03:14:52 +000037
Mike Frysinger124bb8e2023-09-06 05:48:55 +000038 reclient_cfg = os.path.join(tools_path, 'reclient_cfgs', 'reproxy.cfg')
39 if os.path.isfile(reclient_cfg):
40 return reclient_cfg
41 return None
Junji Watanabe607284d2023-04-20 03:14:52 +000042
43
44def run(cmd_args):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000045 if os.environ.get('NINJA_SUMMARIZE_BUILD') == '1':
46 print(' '.join(cmd_args))
47 return subprocess.call(cmd_args)
Junji Watanabe607284d2023-04-20 03:14:52 +000048
49
50def start_reproxy(reclient_cfg, reclient_bin_dir):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000051 return run([
52 os.path.join(reclient_bin_dir,
53 'bootstrap' + gclient_paths.GetExeSuffix()),
54 '--re_proxy=' + os.path.join(reclient_bin_dir,
55 'reproxy' + gclient_paths.GetExeSuffix()),
56 '--cfg=' + reclient_cfg
57 ])
Junji Watanabe607284d2023-04-20 03:14:52 +000058
59
60def stop_reproxy(reclient_cfg, reclient_bin_dir):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000061 return run([
62 os.path.join(reclient_bin_dir,
63 'bootstrap' + gclient_paths.GetExeSuffix()), '--shutdown',
64 '--cfg=' + reclient_cfg
65 ])
Junji Watanabe607284d2023-04-20 03:14:52 +000066
67
68def find_ninja_out_dir(args):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000069 # Ninja uses getopt_long, which allows to intermix non-option arguments.
70 # To leave non supported parameters untouched, we do not use getopt.
71 for index, arg in enumerate(args[1:]):
72 if arg == '-C':
73 # + 1 to get the next argument and +1 because we trimmed off args[0]
74 return args[index + 2]
75 if arg.startswith('-C'):
76 # Support -Cout/Default
77 return arg[2:]
78 return '.'
Junji Watanabe607284d2023-04-20 03:14:52 +000079
80
Ben Segall7a0fe8b2023-04-24 20:36:55 +000081def find_cache_dir(tmp_dir):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000082 """Helper to find the correct cache directory for a build.
Ben Segall7a0fe8b2023-04-24 20:36:55 +000083
84 tmp_dir should be a build specific temp directory within the out directory.
85
86 If this is called from within a gclient checkout, the cache dir will be:
87 <gclient_root>/.reproxy_cache/md5(tmp_dir)/
88 If this is not called from within a gclient checkout, the cache dir will be:
89 tmp_dir/cache
90 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +000091 gclient_root = gclient_paths.FindGclientRoot(os.getcwd())
92 if gclient_root:
93 return os.path.join(gclient_root, '.reproxy_cache',
94 hashlib.md5(tmp_dir.encode()).hexdigest())
95 return os.path.join(tmp_dir, 'cache')
Ben Segall7a0fe8b2023-04-24 20:36:55 +000096
97
Ben Segall29282b52023-09-07 14:52:08 +000098def auth_cache_status():
99 cred_file = os.path.join(os.environ["RBE_cache_dir"], "reproxy.creds")
100 if not os.path.isfile(cred_file):
101 return "missing", "UNSPECIFIED"
102 try:
103 with open(cred_file) as f:
104 status = "valid"
105 mechanism = "UNSPECIFIED"
106 for line in f.readlines():
107 if "seconds:" in line:
108 exp = int(line.strip()[len("seconds:"):].strip())
109 if exp < (time.time() + 5 * 60):
110 status = "expired"
111 elif "mechanism:" in line:
112 mechanism = line.strip()[len("mechanism:"):].strip()
113 return status, mechanism
114 except OSError:
115 return "missing", "UNSPECIFIED"
116
117
Ben Segallb64ee7f2023-09-07 15:11:31 +0000118def get_hostname():
119 hostname = socket.gethostname()
120 try:
121 return socket.gethostbyaddr(hostname)[0]
122 except socket.gaierror:
123 return hostname
124
125
Ben Segalle49349b2023-06-01 02:54:56 +0000126def set_reproxy_metrics_flags(tool):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000127 """Helper to setup metrics collection flags for reproxy.
Ben Segall530d86d2023-05-29 16:11:00 +0000128
129 The following env vars are set if not already set:
130 RBE_metrics_project=chromium-reclient-metrics
131 RBE_invocation_id=$AUTONINJA_BUILD_ID
132 RBE_metrics_table=rbe_metrics.builds
Ben Segalle49349b2023-06-01 02:54:56 +0000133 RBE_metrics_labels=source=developer,tool={tool}
Ben Segall530d86d2023-05-29 16:11:00 +0000134 RBE_metrics_prefix=go.chromium.org
135 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000136 autoninja_id = os.environ.get("AUTONINJA_BUILD_ID")
137 if autoninja_id is not None:
Ben Segallb64ee7f2023-09-07 15:11:31 +0000138 os.environ.setdefault("RBE_invocation_id",
139 "%s/%s" % (get_hostname(), autoninja_id))
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000140 os.environ.setdefault("RBE_metrics_project", "chromium-reclient-metrics")
141 os.environ.setdefault("RBE_metrics_table", "rbe_metrics.builds")
Ben Segall29282b52023-09-07 14:52:08 +0000142 labels = "source=developer,tool=" + tool
143 auth_status, auth_mechanism = auth_cache_status()
144 labels += ",creds_cache_status=" + auth_status
145 labels += ",creds_cache_mechanism=" + auth_mechanism
146 os.environ.setdefault("RBE_metrics_labels", labels)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000147 os.environ.setdefault("RBE_metrics_prefix", "go.chromium.org")
Ben Segall530d86d2023-05-29 16:11:00 +0000148
149
Ben Segall3589c482023-07-24 17:42:55 +0000150def remove_mdproxy_from_path():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000151 os.environ["PATH"] = os.pathsep.join(
152 d for d in os.environ.get("PATH", "").split(os.pathsep)
153 if "mdproxy" not in d)
Ben Segall3589c482023-07-24 17:42:55 +0000154
155
Junji Watanabe607284d2023-04-20 03:14:52 +0000156def set_reproxy_path_flags(out_dir, make_dirs=True):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000157 """Helper to setup the logs and cache directories for reclient.
Junji Watanabe607284d2023-04-20 03:14:52 +0000158
159 Creates the following directory structure if make_dirs is true:
Ben Segall7a0fe8b2023-04-24 20:36:55 +0000160 If in a gclient checkout
161 out_dir/
162 .reproxy_tmp/
163 logs/
164 <gclient_root>
165 .reproxy_cache/
166 md5(out_dir/.reproxy_tmp)/
167
168 If not in a gclient checkout
Junji Watanabe607284d2023-04-20 03:14:52 +0000169 out_dir/
170 .reproxy_tmp/
171 logs/
172 cache/
173
174 The following env vars are set if not already set:
175 RBE_output_dir=out_dir/.reproxy_tmp/logs
176 RBE_proxy_log_dir=out_dir/.reproxy_tmp/logs
177 RBE_log_dir=out_dir/.reproxy_tmp/logs
178 RBE_cache_dir=out_dir/.reproxy_tmp/cache
179 *Nix Only:
180 RBE_server_address=unix://out_dir/.reproxy_tmp/reproxy.sock
181 Windows Only:
182 RBE_server_address=pipe://md5(out_dir/.reproxy_tmp)/reproxy.pipe
183 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000184 tmp_dir = os.path.abspath(os.path.join(out_dir, '.reproxy_tmp'))
185 log_dir = os.path.join(tmp_dir, 'logs')
186 racing_dir = os.path.join(tmp_dir, 'racing')
187 cache_dir = find_cache_dir(tmp_dir)
188 if make_dirs:
189 if os.path.exists(log_dir):
190 try:
191 # Clear log dir before each build to ensure correct metric
192 # aggregation.
193 shutil.rmtree(log_dir)
194 except OSError:
195 print(
196 "Couldn't clear logs because reproxy did "
197 "not shutdown after the last build",
198 file=sys.stderr)
199 os.makedirs(tmp_dir, exist_ok=True)
200 os.makedirs(log_dir, exist_ok=True)
201 os.makedirs(cache_dir, exist_ok=True)
202 os.makedirs(racing_dir, exist_ok=True)
203 os.environ.setdefault("RBE_output_dir", log_dir)
204 os.environ.setdefault("RBE_proxy_log_dir", log_dir)
205 os.environ.setdefault("RBE_log_dir", log_dir)
206 os.environ.setdefault("RBE_cache_dir", cache_dir)
207 os.environ.setdefault("RBE_racing_tmp_dir", racing_dir)
208 if sys.platform.startswith('win'):
209 pipe_dir = hashlib.md5(tmp_dir.encode()).hexdigest()
210 os.environ.setdefault("RBE_server_address",
211 "pipe://%s/reproxy.pipe" % pipe_dir)
212 else:
213 # unix domain socket has path length limit, so use fixed size path here.
214 # ref: https://www.man7.org/linux/man-pages/man7/unix.7.html
215 os.environ.setdefault(
216 "RBE_server_address", "unix:///tmp/reproxy_%s.sock" %
217 hashlib.sha256(tmp_dir.encode()).hexdigest())
Junji Watanabe607284d2023-04-20 03:14:52 +0000218
219
Ben Segalld3e43dd2023-07-25 02:32:31 +0000220def set_racing_defaults():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000221 os.environ.setdefault("RBE_local_resource_fraction", "0.2")
222 os.environ.setdefault("RBE_racing_bias", "0.95")
Ben Segalld3e43dd2023-07-25 02:32:31 +0000223
Ben Segall04ca3832023-07-19 01:50:59 +0000224
Junji Watanabe607284d2023-04-20 03:14:52 +0000225@contextlib.contextmanager
Ben Segalle49349b2023-06-01 02:54:56 +0000226def build_context(argv, tool):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000227 # If use_remoteexec is set, but the reclient binaries or configs don't
228 # exist, display an error message and stop. Otherwise, the build will
229 # attempt to run with rewrapper wrapping actions, but will fail with
230 # possible non-obvious problems.
231 reclient_bin_dir = find_reclient_bin_dir()
232 reclient_cfg = find_reclient_cfg()
233 if reclient_bin_dir is None or reclient_cfg is None:
234 print(
235 'Build is configured to use reclient but necessary binaries '
236 "or config files can't be found.\n"
237 'Please check if `"download_remoteexec_cfg": True` custom var is '
238 'set in `.gclient`, and run `gclient sync`.',
239 file=sys.stderr)
240 yield 1
241 return
Ben Segall530d86d2023-05-29 16:11:00 +0000242
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000243 ninja_out = find_ninja_out_dir(argv)
Ben Segall530d86d2023-05-29 16:11:00 +0000244
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000245 try:
246 set_reproxy_path_flags(ninja_out)
247 except OSError:
248 print("Error creating reproxy_tmp in output dir", file=sys.stderr)
249 yield 1
250 return
Ben Segall530d86d2023-05-29 16:11:00 +0000251
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000252 if reclient_metrics.check_status(ninja_out):
253 set_reproxy_metrics_flags(tool)
Ben Segall530d86d2023-05-29 16:11:00 +0000254
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000255 if os.environ.get('RBE_instance', None):
256 print('WARNING: Using RBE_instance=%s\n' %
257 os.environ.get('RBE_instance', ''))
Fumitoshi Ukaidedeb882023-06-15 03:58:35 +0000258
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000259 remote_disabled = os.environ.get('RBE_remote_disabled')
260 if remote_disabled not in ('1', 't', 'T', 'true', 'TRUE', 'True'):
261 set_racing_defaults()
Ben Segalld3e43dd2023-07-25 02:32:31 +0000262
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000263 # TODO(b/292523514) remove this once a fix is landed in reproxy
264 remove_mdproxy_from_path()
Ben Segall3589c482023-07-24 17:42:55 +0000265
Bruce Dawson6d0c2352023-08-06 02:38:51 +0000266 start = time.time()
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000267 reproxy_ret_code = start_reproxy(reclient_cfg, reclient_bin_dir)
Bruce Dawson6d0c2352023-08-06 02:38:51 +0000268 elapsed = time.time() - start
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000269 print('%1.3f s to start reproxy' % elapsed)
270 if reproxy_ret_code != 0:
271 yield reproxy_ret_code
272 return
273 try:
274 yield
275 finally:
276 print("Shutting down reproxy...", file=sys.stderr)
277 start = time.time()
278 stop_reproxy(reclient_cfg, reclient_bin_dir)
279 elapsed = time.time() - start
280 print('%1.3f s to stop reproxy' % elapsed)