blob: 820b070d31a38188006b75d678be911bcd9bd34f [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
Ben Segall27ea34f2023-10-24 16:03:33 +000010import datetime
Junji Watanabe607284d2023-04-20 03:14:52 +000011import hashlib
12import os
Ben Segall2e673842023-06-21 14:23:37 +000013import shutil
Ben Segallb64ee7f2023-09-07 15:11:31 +000014import socket
Junji Watanabe607284d2023-04-20 03:14:52 +000015import subprocess
16import sys
Bruce Dawson6d0c2352023-08-06 02:38:51 +000017import time
Ben Segall27ea34f2023-10-24 16:03:33 +000018import uuid
Junji Watanabe607284d2023-04-20 03:14:52 +000019
20import gclient_paths
Ben Segall530d86d2023-05-29 16:11:00 +000021import reclient_metrics
Junji Watanabe607284d2023-04-20 03:14:52 +000022
23
Ben Segall27ea34f2023-10-24 16:03:33 +000024THIS_DIR = os.path.dirname(__file__)
25RECLIENT_LOG_CLEANUP = os.path.join(THIS_DIR, 'reclient_log_cleanup.py')
26
27
Junji Watanabe607284d2023-04-20 03:14:52 +000028def find_reclient_bin_dir():
Mike Frysinger124bb8e2023-09-06 05:48:55 +000029 tools_path = gclient_paths.GetBuildtoolsPath()
30 if not tools_path:
31 return None
Junji Watanabe607284d2023-04-20 03:14:52 +000032
Mike Frysinger124bb8e2023-09-06 05:48:55 +000033 reclient_bin_dir = os.path.join(tools_path, 'reclient')
34 if os.path.isdir(reclient_bin_dir):
35 return reclient_bin_dir
36 return None
Junji Watanabe607284d2023-04-20 03:14:52 +000037
38
39def find_reclient_cfg():
Mike Frysinger124bb8e2023-09-06 05:48:55 +000040 tools_path = gclient_paths.GetBuildtoolsPath()
41 if not tools_path:
42 return None
Junji Watanabe607284d2023-04-20 03:14:52 +000043
Mike Frysinger124bb8e2023-09-06 05:48:55 +000044 reclient_cfg = os.path.join(tools_path, 'reclient_cfgs', 'reproxy.cfg')
45 if os.path.isfile(reclient_cfg):
46 return reclient_cfg
47 return None
Junji Watanabe607284d2023-04-20 03:14:52 +000048
49
50def run(cmd_args):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000051 if os.environ.get('NINJA_SUMMARIZE_BUILD') == '1':
52 print(' '.join(cmd_args))
53 return subprocess.call(cmd_args)
Junji Watanabe607284d2023-04-20 03:14:52 +000054
55
56def start_reproxy(reclient_cfg, reclient_bin_dir):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000057 return run([
58 os.path.join(reclient_bin_dir,
59 'bootstrap' + gclient_paths.GetExeSuffix()),
60 '--re_proxy=' + os.path.join(reclient_bin_dir,
61 'reproxy' + gclient_paths.GetExeSuffix()),
62 '--cfg=' + reclient_cfg
63 ])
Junji Watanabe607284d2023-04-20 03:14:52 +000064
65
66def stop_reproxy(reclient_cfg, reclient_bin_dir):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000067 return run([
68 os.path.join(reclient_bin_dir,
69 'bootstrap' + gclient_paths.GetExeSuffix()), '--shutdown',
70 '--cfg=' + reclient_cfg
71 ])
Junji Watanabe607284d2023-04-20 03:14:52 +000072
73
74def find_ninja_out_dir(args):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000075 # Ninja uses getopt_long, which allows to intermix non-option arguments.
76 # To leave non supported parameters untouched, we do not use getopt.
77 for index, arg in enumerate(args[1:]):
78 if arg == '-C':
79 # + 1 to get the next argument and +1 because we trimmed off args[0]
80 return args[index + 2]
81 if arg.startswith('-C'):
82 # Support -Cout/Default
83 return arg[2:]
84 return '.'
Junji Watanabe607284d2023-04-20 03:14:52 +000085
86
Ben Segall7a0fe8b2023-04-24 20:36:55 +000087def find_cache_dir(tmp_dir):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000088 """Helper to find the correct cache directory for a build.
Ben Segall7a0fe8b2023-04-24 20:36:55 +000089
90 tmp_dir should be a build specific temp directory within the out directory.
91
92 If this is called from within a gclient checkout, the cache dir will be:
93 <gclient_root>/.reproxy_cache/md5(tmp_dir)/
94 If this is not called from within a gclient checkout, the cache dir will be:
95 tmp_dir/cache
96 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +000097 gclient_root = gclient_paths.FindGclientRoot(os.getcwd())
98 if gclient_root:
99 return os.path.join(gclient_root, '.reproxy_cache',
100 hashlib.md5(tmp_dir.encode()).hexdigest())
101 return os.path.join(tmp_dir, 'cache')
Ben Segall7a0fe8b2023-04-24 20:36:55 +0000102
103
Ben Segall29282b52023-09-07 14:52:08 +0000104def auth_cache_status():
105 cred_file = os.path.join(os.environ["RBE_cache_dir"], "reproxy.creds")
106 if not os.path.isfile(cred_file):
107 return "missing", "UNSPECIFIED"
108 try:
109 with open(cred_file) as f:
110 status = "valid"
111 mechanism = "UNSPECIFIED"
112 for line in f.readlines():
113 if "seconds:" in line:
114 exp = int(line.strip()[len("seconds:"):].strip())
115 if exp < (time.time() + 5 * 60):
116 status = "expired"
117 elif "mechanism:" in line:
118 mechanism = line.strip()[len("mechanism:"):].strip()
119 return status, mechanism
120 except OSError:
121 return "missing", "UNSPECIFIED"
122
123
Ben Segallb64ee7f2023-09-07 15:11:31 +0000124def get_hostname():
125 hostname = socket.gethostname()
126 try:
127 return socket.gethostbyaddr(hostname)[0]
128 except socket.gaierror:
129 return hostname
130
131
Ben Segalle49349b2023-06-01 02:54:56 +0000132def set_reproxy_metrics_flags(tool):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000133 """Helper to setup metrics collection flags for reproxy.
Ben Segall530d86d2023-05-29 16:11:00 +0000134
135 The following env vars are set if not already set:
136 RBE_metrics_project=chromium-reclient-metrics
137 RBE_invocation_id=$AUTONINJA_BUILD_ID
138 RBE_metrics_table=rbe_metrics.builds
Ben Segalle49349b2023-06-01 02:54:56 +0000139 RBE_metrics_labels=source=developer,tool={tool}
Ben Segall530d86d2023-05-29 16:11:00 +0000140 RBE_metrics_prefix=go.chromium.org
141 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000142 autoninja_id = os.environ.get("AUTONINJA_BUILD_ID")
143 if autoninja_id is not None:
Ben Segallb64ee7f2023-09-07 15:11:31 +0000144 os.environ.setdefault("RBE_invocation_id",
145 "%s/%s" % (get_hostname(), autoninja_id))
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000146 os.environ.setdefault("RBE_metrics_project", "chromium-reclient-metrics")
147 os.environ.setdefault("RBE_metrics_table", "rbe_metrics.builds")
Ben Segall29282b52023-09-07 14:52:08 +0000148 labels = "source=developer,tool=" + tool
149 auth_status, auth_mechanism = auth_cache_status()
150 labels += ",creds_cache_status=" + auth_status
151 labels += ",creds_cache_mechanism=" + auth_mechanism
152 os.environ.setdefault("RBE_metrics_labels", labels)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000153 os.environ.setdefault("RBE_metrics_prefix", "go.chromium.org")
Ben Segall530d86d2023-05-29 16:11:00 +0000154
155
Ben Segall3589c482023-07-24 17:42:55 +0000156def remove_mdproxy_from_path():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000157 os.environ["PATH"] = os.pathsep.join(
158 d for d in os.environ.get("PATH", "").split(os.pathsep)
159 if "mdproxy" not in d)
Ben Segall3589c482023-07-24 17:42:55 +0000160
161
Ben Segall27ea34f2023-10-24 16:03:33 +0000162# Mockable datetime.datetime.utcnow for testing.
163def datetime_now():
164 return datetime.datetime.utcnow()
165
166
167_test_only_cleanup_logdir_handles = []
168
169
170def cleanup_logdir(log_dir):
171 # Run deletetion command without waiting
172 if sys.platform.startswith('win'):
173 _test_only_cleanup_logdir_handles.append(
174 subprocess.Popen(["rmdir", "/s/q", log_dir],
175 stdout=subprocess.DEVNULL,
176 stderr=subprocess.DEVNULL,
177 shell=True,
178 creationflags=subprocess.CREATE_NEW_PROCESS_GROUP))
179 else:
180 _test_only_cleanup_logdir_handles.append(
181 subprocess.Popen(["rm", "-rf", log_dir],
182 stdout=subprocess.DEVNULL,
183 stderr=subprocess.DEVNULL))
184
185
Junji Watanabe607284d2023-04-20 03:14:52 +0000186def set_reproxy_path_flags(out_dir, make_dirs=True):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000187 """Helper to setup the logs and cache directories for reclient.
Junji Watanabe607284d2023-04-20 03:14:52 +0000188
189 Creates the following directory structure if make_dirs is true:
Ben Segall7a0fe8b2023-04-24 20:36:55 +0000190 If in a gclient checkout
191 out_dir/
192 .reproxy_tmp/
193 logs/
194 <gclient_root>
195 .reproxy_cache/
196 md5(out_dir/.reproxy_tmp)/
197
198 If not in a gclient checkout
Junji Watanabe607284d2023-04-20 03:14:52 +0000199 out_dir/
200 .reproxy_tmp/
201 logs/
202 cache/
203
204 The following env vars are set if not already set:
205 RBE_output_dir=out_dir/.reproxy_tmp/logs
206 RBE_proxy_log_dir=out_dir/.reproxy_tmp/logs
207 RBE_log_dir=out_dir/.reproxy_tmp/logs
208 RBE_cache_dir=out_dir/.reproxy_tmp/cache
209 *Nix Only:
210 RBE_server_address=unix://out_dir/.reproxy_tmp/reproxy.sock
211 Windows Only:
212 RBE_server_address=pipe://md5(out_dir/.reproxy_tmp)/reproxy.pipe
213 """
Ben Segall27ea34f2023-10-24 16:03:33 +0000214 os.environ.setdefault("AUTONINJA_BUILD_ID", str(uuid.uuid4()))
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000215 tmp_dir = os.path.abspath(os.path.join(out_dir, '.reproxy_tmp'))
216 log_dir = os.path.join(tmp_dir, 'logs')
Ben Segall27ea34f2023-10-24 16:03:33 +0000217 run_log_dir = os.path.join(
218 log_dir,
219 datetime_now().strftime('%Y%m%dT%H%M%S.%f') + "_" +
220 os.environ["AUTONINJA_BUILD_ID"])
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000221 racing_dir = os.path.join(tmp_dir, 'racing')
222 cache_dir = find_cache_dir(tmp_dir)
223 if make_dirs:
Ben Segall27ea34f2023-10-24 16:03:33 +0000224 if os.path.isfile(os.path.join(log_dir, "rbe_metrics.txt")):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000225 try:
Ben Segall27ea34f2023-10-24 16:03:33 +0000226 # Delete entire log dir if it is in the old format
227 # which had no subdirectories for each build.
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000228 shutil.rmtree(log_dir)
229 except OSError:
230 print(
231 "Couldn't clear logs because reproxy did "
232 "not shutdown after the last build",
233 file=sys.stderr)
234 os.makedirs(tmp_dir, exist_ok=True)
235 os.makedirs(log_dir, exist_ok=True)
Ben Segall27ea34f2023-10-24 16:03:33 +0000236 os.makedirs(run_log_dir, exist_ok=True)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000237 os.makedirs(cache_dir, exist_ok=True)
238 os.makedirs(racing_dir, exist_ok=True)
Ben Segall27ea34f2023-10-24 16:03:33 +0000239 old_log_dirs = os.listdir(log_dir)
240 if len(old_log_dirs) > 5:
241 old_log_dirs.sort(key=lambda dir: dir.split("_"), reverse=True)
242 for d in old_log_dirs[5:]:
243 cleanup_logdir(os.path.join(log_dir, d))
244 os.environ.setdefault("RBE_output_dir", run_log_dir)
245 os.environ.setdefault("RBE_proxy_log_dir", run_log_dir)
246 os.environ.setdefault("RBE_log_dir", run_log_dir)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000247 os.environ.setdefault("RBE_cache_dir", cache_dir)
248 os.environ.setdefault("RBE_racing_tmp_dir", racing_dir)
249 if sys.platform.startswith('win'):
250 pipe_dir = hashlib.md5(tmp_dir.encode()).hexdigest()
251 os.environ.setdefault("RBE_server_address",
252 "pipe://%s/reproxy.pipe" % pipe_dir)
253 else:
254 # unix domain socket has path length limit, so use fixed size path here.
255 # ref: https://www.man7.org/linux/man-pages/man7/unix.7.html
256 os.environ.setdefault(
257 "RBE_server_address", "unix:///tmp/reproxy_%s.sock" %
258 hashlib.sha256(tmp_dir.encode()).hexdigest())
Junji Watanabe607284d2023-04-20 03:14:52 +0000259
260
Ben Segalld3e43dd2023-07-25 02:32:31 +0000261def set_racing_defaults():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000262 os.environ.setdefault("RBE_local_resource_fraction", "0.2")
263 os.environ.setdefault("RBE_racing_bias", "0.95")
Ben Segalld3e43dd2023-07-25 02:32:31 +0000264
Ben Segall04ca3832023-07-19 01:50:59 +0000265
Junji Watanabe607284d2023-04-20 03:14:52 +0000266@contextlib.contextmanager
Ben Segalle49349b2023-06-01 02:54:56 +0000267def build_context(argv, tool):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000268 # If use_remoteexec is set, but the reclient binaries or configs don't
269 # exist, display an error message and stop. Otherwise, the build will
270 # attempt to run with rewrapper wrapping actions, but will fail with
271 # possible non-obvious problems.
272 reclient_bin_dir = find_reclient_bin_dir()
273 reclient_cfg = find_reclient_cfg()
274 if reclient_bin_dir is None or reclient_cfg is None:
275 print(
276 'Build is configured to use reclient but necessary binaries '
277 "or config files can't be found.\n"
278 'Please check if `"download_remoteexec_cfg": True` custom var is '
279 'set in `.gclient`, and run `gclient sync`.',
280 file=sys.stderr)
281 yield 1
282 return
Ben Segall530d86d2023-05-29 16:11:00 +0000283
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000284 ninja_out = find_ninja_out_dir(argv)
Ben Segall530d86d2023-05-29 16:11:00 +0000285
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000286 try:
287 set_reproxy_path_flags(ninja_out)
288 except OSError:
289 print("Error creating reproxy_tmp in output dir", file=sys.stderr)
290 yield 1
291 return
Ben Segall530d86d2023-05-29 16:11:00 +0000292
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000293 if reclient_metrics.check_status(ninja_out):
294 set_reproxy_metrics_flags(tool)
Ben Segall530d86d2023-05-29 16:11:00 +0000295
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000296 if os.environ.get('RBE_instance', None):
297 print('WARNING: Using RBE_instance=%s\n' %
298 os.environ.get('RBE_instance', ''))
Fumitoshi Ukaidedeb882023-06-15 03:58:35 +0000299
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000300 remote_disabled = os.environ.get('RBE_remote_disabled')
301 if remote_disabled not in ('1', 't', 'T', 'true', 'TRUE', 'True'):
302 set_racing_defaults()
Ben Segalld3e43dd2023-07-25 02:32:31 +0000303
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000304 # TODO(b/292523514) remove this once a fix is landed in reproxy
305 remove_mdproxy_from_path()
Ben Segall3589c482023-07-24 17:42:55 +0000306
Bruce Dawson6d0c2352023-08-06 02:38:51 +0000307 start = time.time()
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000308 reproxy_ret_code = start_reproxy(reclient_cfg, reclient_bin_dir)
Ben Segallc18c91e2023-09-14 15:25:04 +0000309 if os.environ.get('NINJA_SUMMARIZE_BUILD') == '1':
310 elapsed = time.time() - start
311 print('%1.3f s to start reproxy' % elapsed)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000312 if reproxy_ret_code != 0:
313 yield reproxy_ret_code
314 return
315 try:
316 yield
317 finally:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000318 start = time.time()
319 stop_reproxy(reclient_cfg, reclient_bin_dir)
Ben Segallc18c91e2023-09-14 15:25:04 +0000320 if os.environ.get('NINJA_SUMMARIZE_BUILD') == '1':
321 elapsed = time.time() - start
322 print('%1.3f s to stop reproxy' % elapsed)