blob: 2b99707e61b185e87a9dbe2ac7fe9b8c489f9eb2 [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]
Anas Sulaiman7a9b7092023-11-14 03:43:15 +0000128 except Exception as e:
129 print("socket.gethostbyaddr failed " +
130 "(falling back to socket.gethostname): %s" % e,
131 file=sys.stderr)
Ben Segallb64ee7f2023-09-07 15:11:31 +0000132 return hostname
133
134
Ben Segalle49349b2023-06-01 02:54:56 +0000135def set_reproxy_metrics_flags(tool):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000136 """Helper to setup metrics collection flags for reproxy.
Ben Segall530d86d2023-05-29 16:11:00 +0000137
138 The following env vars are set if not already set:
139 RBE_metrics_project=chromium-reclient-metrics
140 RBE_invocation_id=$AUTONINJA_BUILD_ID
141 RBE_metrics_table=rbe_metrics.builds
Ben Segalle49349b2023-06-01 02:54:56 +0000142 RBE_metrics_labels=source=developer,tool={tool}
Ben Segall530d86d2023-05-29 16:11:00 +0000143 RBE_metrics_prefix=go.chromium.org
144 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000145 autoninja_id = os.environ.get("AUTONINJA_BUILD_ID")
146 if autoninja_id is not None:
Ben Segallb64ee7f2023-09-07 15:11:31 +0000147 os.environ.setdefault("RBE_invocation_id",
148 "%s/%s" % (get_hostname(), autoninja_id))
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000149 os.environ.setdefault("RBE_metrics_project", "chromium-reclient-metrics")
150 os.environ.setdefault("RBE_metrics_table", "rbe_metrics.builds")
Ben Segall29282b52023-09-07 14:52:08 +0000151 labels = "source=developer,tool=" + tool
152 auth_status, auth_mechanism = auth_cache_status()
153 labels += ",creds_cache_status=" + auth_status
154 labels += ",creds_cache_mechanism=" + auth_mechanism
155 os.environ.setdefault("RBE_metrics_labels", labels)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000156 os.environ.setdefault("RBE_metrics_prefix", "go.chromium.org")
Ben Segall530d86d2023-05-29 16:11:00 +0000157
158
Ben Segall3589c482023-07-24 17:42:55 +0000159def remove_mdproxy_from_path():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000160 os.environ["PATH"] = os.pathsep.join(
161 d for d in os.environ.get("PATH", "").split(os.pathsep)
162 if "mdproxy" not in d)
Ben Segall3589c482023-07-24 17:42:55 +0000163
164
Ben Segall27ea34f2023-10-24 16:03:33 +0000165# Mockable datetime.datetime.utcnow for testing.
166def datetime_now():
167 return datetime.datetime.utcnow()
168
169
Junji Watanabe607284d2023-04-20 03:14:52 +0000170def set_reproxy_path_flags(out_dir, make_dirs=True):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000171 """Helper to setup the logs and cache directories for reclient.
Junji Watanabe607284d2023-04-20 03:14:52 +0000172
173 Creates the following directory structure if make_dirs is true:
Ben Segall7a0fe8b2023-04-24 20:36:55 +0000174 If in a gclient checkout
175 out_dir/
176 .reproxy_tmp/
177 logs/
178 <gclient_root>
179 .reproxy_cache/
180 md5(out_dir/.reproxy_tmp)/
181
182 If not in a gclient checkout
Junji Watanabe607284d2023-04-20 03:14:52 +0000183 out_dir/
184 .reproxy_tmp/
185 logs/
186 cache/
187
188 The following env vars are set if not already set:
189 RBE_output_dir=out_dir/.reproxy_tmp/logs
190 RBE_proxy_log_dir=out_dir/.reproxy_tmp/logs
191 RBE_log_dir=out_dir/.reproxy_tmp/logs
192 RBE_cache_dir=out_dir/.reproxy_tmp/cache
193 *Nix Only:
194 RBE_server_address=unix://out_dir/.reproxy_tmp/reproxy.sock
195 Windows Only:
196 RBE_server_address=pipe://md5(out_dir/.reproxy_tmp)/reproxy.pipe
197 """
Ben Segall27ea34f2023-10-24 16:03:33 +0000198 os.environ.setdefault("AUTONINJA_BUILD_ID", str(uuid.uuid4()))
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000199 tmp_dir = os.path.abspath(os.path.join(out_dir, '.reproxy_tmp'))
200 log_dir = os.path.join(tmp_dir, 'logs')
Ben Segall27ea34f2023-10-24 16:03:33 +0000201 run_log_dir = os.path.join(
202 log_dir,
203 datetime_now().strftime('%Y%m%dT%H%M%S.%f') + "_" +
204 os.environ["AUTONINJA_BUILD_ID"])
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000205 racing_dir = os.path.join(tmp_dir, 'racing')
206 cache_dir = find_cache_dir(tmp_dir)
207 if make_dirs:
Ben Segall27ea34f2023-10-24 16:03:33 +0000208 if os.path.isfile(os.path.join(log_dir, "rbe_metrics.txt")):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000209 try:
Ben Segall27ea34f2023-10-24 16:03:33 +0000210 # Delete entire log dir if it is in the old format
211 # which had no subdirectories for each build.
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000212 shutil.rmtree(log_dir)
213 except OSError:
214 print(
215 "Couldn't clear logs because reproxy did "
216 "not shutdown after the last build",
217 file=sys.stderr)
218 os.makedirs(tmp_dir, exist_ok=True)
219 os.makedirs(log_dir, exist_ok=True)
Ben Segall27ea34f2023-10-24 16:03:33 +0000220 os.makedirs(run_log_dir, exist_ok=True)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000221 os.makedirs(cache_dir, exist_ok=True)
222 os.makedirs(racing_dir, exist_ok=True)
Ben Segall27ea34f2023-10-24 16:03:33 +0000223 old_log_dirs = os.listdir(log_dir)
224 if len(old_log_dirs) > 5:
225 old_log_dirs.sort(key=lambda dir: dir.split("_"), reverse=True)
226 for d in old_log_dirs[5:]:
Takuto Ikuta53889592023-10-31 04:55:37 +0000227 shutil.rmtree(os.path.join(log_dir, d))
228
Ben Segall27ea34f2023-10-24 16:03:33 +0000229 os.environ.setdefault("RBE_output_dir", run_log_dir)
230 os.environ.setdefault("RBE_proxy_log_dir", run_log_dir)
231 os.environ.setdefault("RBE_log_dir", run_log_dir)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000232 os.environ.setdefault("RBE_cache_dir", cache_dir)
233 os.environ.setdefault("RBE_racing_tmp_dir", racing_dir)
234 if sys.platform.startswith('win'):
235 pipe_dir = hashlib.md5(tmp_dir.encode()).hexdigest()
236 os.environ.setdefault("RBE_server_address",
237 "pipe://%s/reproxy.pipe" % pipe_dir)
238 else:
239 # unix domain socket has path length limit, so use fixed size path here.
240 # ref: https://www.man7.org/linux/man-pages/man7/unix.7.html
241 os.environ.setdefault(
242 "RBE_server_address", "unix:///tmp/reproxy_%s.sock" %
243 hashlib.sha256(tmp_dir.encode()).hexdigest())
Junji Watanabe607284d2023-04-20 03:14:52 +0000244
245
Ben Segalld3e43dd2023-07-25 02:32:31 +0000246def set_racing_defaults():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000247 os.environ.setdefault("RBE_local_resource_fraction", "0.2")
248 os.environ.setdefault("RBE_racing_bias", "0.95")
Ben Segalld3e43dd2023-07-25 02:32:31 +0000249
Ben Segall04ca3832023-07-19 01:50:59 +0000250
Junji Watanabe607284d2023-04-20 03:14:52 +0000251@contextlib.contextmanager
Ben Segalle49349b2023-06-01 02:54:56 +0000252def build_context(argv, tool):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000253 # If use_remoteexec is set, but the reclient binaries or configs don't
254 # exist, display an error message and stop. Otherwise, the build will
255 # attempt to run with rewrapper wrapping actions, but will fail with
256 # possible non-obvious problems.
257 reclient_bin_dir = find_reclient_bin_dir()
258 reclient_cfg = find_reclient_cfg()
259 if reclient_bin_dir is None or reclient_cfg is None:
260 print(
261 'Build is configured to use reclient but necessary binaries '
262 "or config files can't be found.\n"
263 'Please check if `"download_remoteexec_cfg": True` custom var is '
264 'set in `.gclient`, and run `gclient sync`.',
265 file=sys.stderr)
266 yield 1
267 return
Ben Segall530d86d2023-05-29 16:11:00 +0000268
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000269 ninja_out = find_ninja_out_dir(argv)
Ben Segall530d86d2023-05-29 16:11:00 +0000270
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000271 try:
272 set_reproxy_path_flags(ninja_out)
273 except OSError:
274 print("Error creating reproxy_tmp in output dir", file=sys.stderr)
275 yield 1
276 return
Ben Segall530d86d2023-05-29 16:11:00 +0000277
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000278 if reclient_metrics.check_status(ninja_out):
279 set_reproxy_metrics_flags(tool)
Ben Segall530d86d2023-05-29 16:11:00 +0000280
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000281 if os.environ.get('RBE_instance', None):
282 print('WARNING: Using RBE_instance=%s\n' %
283 os.environ.get('RBE_instance', ''))
Fumitoshi Ukaidedeb882023-06-15 03:58:35 +0000284
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000285 remote_disabled = os.environ.get('RBE_remote_disabled')
286 if remote_disabled not in ('1', 't', 'T', 'true', 'TRUE', 'True'):
287 set_racing_defaults()
Ben Segalld3e43dd2023-07-25 02:32:31 +0000288
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000289 # TODO(b/292523514) remove this once a fix is landed in reproxy
290 remove_mdproxy_from_path()
Ben Segall3589c482023-07-24 17:42:55 +0000291
Bruce Dawson6d0c2352023-08-06 02:38:51 +0000292 start = time.time()
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000293 reproxy_ret_code = start_reproxy(reclient_cfg, reclient_bin_dir)
Ben Segallc18c91e2023-09-14 15:25:04 +0000294 if os.environ.get('NINJA_SUMMARIZE_BUILD') == '1':
295 elapsed = time.time() - start
296 print('%1.3f s to start reproxy' % elapsed)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000297 if reproxy_ret_code != 0:
298 yield reproxy_ret_code
299 return
300 try:
301 yield
302 finally:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000303 start = time.time()
304 stop_reproxy(reclient_cfg, reclient_bin_dir)
Ben Segallc18c91e2023-09-14 15:25:04 +0000305 if os.environ.get('NINJA_SUMMARIZE_BUILD') == '1':
306 elapsed = time.time() - start
307 print('%1.3f s to stop reproxy' % elapsed)