blob: 7686fa518ac4593482189e3f924a78b4e41a4806 [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()
Anas Sulaiman1e503bf2023-11-15 04:30:40 +0000126 # In case that returned an address, make a best effort attempt to get
127 # the hostname and ignore any errors.
Ben Segallb64ee7f2023-09-07 15:11:31 +0000128 try:
129 return socket.gethostbyaddr(hostname)[0]
Anas Sulaiman1e503bf2023-11-15 04:30:40 +0000130 except Exception:
Ben Segallb64ee7f2023-09-07 15:11:31 +0000131 return hostname
132
133
Ben Segalle49349b2023-06-01 02:54:56 +0000134def set_reproxy_metrics_flags(tool):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000135 """Helper to setup metrics collection flags for reproxy.
Ben Segall530d86d2023-05-29 16:11:00 +0000136
137 The following env vars are set if not already set:
138 RBE_metrics_project=chromium-reclient-metrics
139 RBE_invocation_id=$AUTONINJA_BUILD_ID
140 RBE_metrics_table=rbe_metrics.builds
Ben Segalle49349b2023-06-01 02:54:56 +0000141 RBE_metrics_labels=source=developer,tool={tool}
Ben Segall530d86d2023-05-29 16:11:00 +0000142 RBE_metrics_prefix=go.chromium.org
143 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000144 autoninja_id = os.environ.get("AUTONINJA_BUILD_ID")
145 if autoninja_id is not None:
Ben Segallb64ee7f2023-09-07 15:11:31 +0000146 os.environ.setdefault("RBE_invocation_id",
147 "%s/%s" % (get_hostname(), autoninja_id))
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000148 os.environ.setdefault("RBE_metrics_project", "chromium-reclient-metrics")
149 os.environ.setdefault("RBE_metrics_table", "rbe_metrics.builds")
Ben Segall29282b52023-09-07 14:52:08 +0000150 labels = "source=developer,tool=" + tool
151 auth_status, auth_mechanism = auth_cache_status()
152 labels += ",creds_cache_status=" + auth_status
153 labels += ",creds_cache_mechanism=" + auth_mechanism
154 os.environ.setdefault("RBE_metrics_labels", labels)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000155 os.environ.setdefault("RBE_metrics_prefix", "go.chromium.org")
Ben Segall530d86d2023-05-29 16:11:00 +0000156
157
Ben Segall3589c482023-07-24 17:42:55 +0000158def remove_mdproxy_from_path():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000159 os.environ["PATH"] = os.pathsep.join(
160 d for d in os.environ.get("PATH", "").split(os.pathsep)
161 if "mdproxy" not in d)
Ben Segall3589c482023-07-24 17:42:55 +0000162
163
Ben Segall27ea34f2023-10-24 16:03:33 +0000164# Mockable datetime.datetime.utcnow for testing.
165def datetime_now():
166 return datetime.datetime.utcnow()
167
168
Junji Watanabe607284d2023-04-20 03:14:52 +0000169def set_reproxy_path_flags(out_dir, make_dirs=True):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000170 """Helper to setup the logs and cache directories for reclient.
Junji Watanabe607284d2023-04-20 03:14:52 +0000171
172 Creates the following directory structure if make_dirs is true:
Ben Segall7a0fe8b2023-04-24 20:36:55 +0000173 If in a gclient checkout
174 out_dir/
175 .reproxy_tmp/
176 logs/
177 <gclient_root>
178 .reproxy_cache/
179 md5(out_dir/.reproxy_tmp)/
180
181 If not in a gclient checkout
Junji Watanabe607284d2023-04-20 03:14:52 +0000182 out_dir/
183 .reproxy_tmp/
184 logs/
185 cache/
186
187 The following env vars are set if not already set:
188 RBE_output_dir=out_dir/.reproxy_tmp/logs
189 RBE_proxy_log_dir=out_dir/.reproxy_tmp/logs
190 RBE_log_dir=out_dir/.reproxy_tmp/logs
191 RBE_cache_dir=out_dir/.reproxy_tmp/cache
192 *Nix Only:
193 RBE_server_address=unix://out_dir/.reproxy_tmp/reproxy.sock
194 Windows Only:
195 RBE_server_address=pipe://md5(out_dir/.reproxy_tmp)/reproxy.pipe
196 """
Ben Segall27ea34f2023-10-24 16:03:33 +0000197 os.environ.setdefault("AUTONINJA_BUILD_ID", str(uuid.uuid4()))
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000198 tmp_dir = os.path.abspath(os.path.join(out_dir, '.reproxy_tmp'))
199 log_dir = os.path.join(tmp_dir, 'logs')
Ben Segall27ea34f2023-10-24 16:03:33 +0000200 run_log_dir = os.path.join(
201 log_dir,
202 datetime_now().strftime('%Y%m%dT%H%M%S.%f') + "_" +
203 os.environ["AUTONINJA_BUILD_ID"])
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000204 racing_dir = os.path.join(tmp_dir, 'racing')
205 cache_dir = find_cache_dir(tmp_dir)
206 if make_dirs:
Ben Segall27ea34f2023-10-24 16:03:33 +0000207 if os.path.isfile(os.path.join(log_dir, "rbe_metrics.txt")):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000208 try:
Ben Segall27ea34f2023-10-24 16:03:33 +0000209 # Delete entire log dir if it is in the old format
210 # which had no subdirectories for each build.
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000211 shutil.rmtree(log_dir)
212 except OSError:
213 print(
214 "Couldn't clear logs because reproxy did "
215 "not shutdown after the last build",
216 file=sys.stderr)
217 os.makedirs(tmp_dir, exist_ok=True)
218 os.makedirs(log_dir, exist_ok=True)
Ben Segall27ea34f2023-10-24 16:03:33 +0000219 os.makedirs(run_log_dir, exist_ok=True)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000220 os.makedirs(cache_dir, exist_ok=True)
221 os.makedirs(racing_dir, exist_ok=True)
Michael Savignyaf692492023-11-17 20:16:53 +0000222 old_log_dirs = [
223 d for d in os.listdir(log_dir)
224 if os.path.isdir(os.path.join(log_dir, d))
225 ]
226
Ben Segall27ea34f2023-10-24 16:03:33 +0000227 if len(old_log_dirs) > 5:
228 old_log_dirs.sort(key=lambda dir: dir.split("_"), reverse=True)
229 for d in old_log_dirs[5:]:
Takuto Ikuta53889592023-10-31 04:55:37 +0000230 shutil.rmtree(os.path.join(log_dir, d))
231
Ben Segall27ea34f2023-10-24 16:03:33 +0000232 os.environ.setdefault("RBE_output_dir", run_log_dir)
233 os.environ.setdefault("RBE_proxy_log_dir", run_log_dir)
234 os.environ.setdefault("RBE_log_dir", run_log_dir)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000235 os.environ.setdefault("RBE_cache_dir", cache_dir)
236 os.environ.setdefault("RBE_racing_tmp_dir", racing_dir)
237 if sys.platform.startswith('win'):
238 pipe_dir = hashlib.md5(tmp_dir.encode()).hexdigest()
239 os.environ.setdefault("RBE_server_address",
240 "pipe://%s/reproxy.pipe" % pipe_dir)
241 else:
242 # unix domain socket has path length limit, so use fixed size path here.
243 # ref: https://www.man7.org/linux/man-pages/man7/unix.7.html
244 os.environ.setdefault(
245 "RBE_server_address", "unix:///tmp/reproxy_%s.sock" %
246 hashlib.sha256(tmp_dir.encode()).hexdigest())
Junji Watanabe607284d2023-04-20 03:14:52 +0000247
248
Ben Segalld3e43dd2023-07-25 02:32:31 +0000249def set_racing_defaults():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000250 os.environ.setdefault("RBE_local_resource_fraction", "0.2")
251 os.environ.setdefault("RBE_racing_bias", "0.95")
Ben Segalld3e43dd2023-07-25 02:32:31 +0000252
Ben Segall04ca3832023-07-19 01:50:59 +0000253
Michael Savigny8d97b6d2023-12-01 16:53:43 +0000254def set_mac_defaults():
255 # Reduce the cas concurrency on macs. Lower value doesn't impact
256 # performance when on high-speed connection, but does show improvements
257 # on easily congested networks.
258 os.environ.setdefault("RBE_cas_concurrency", "100")
259
260
Junji Watanabe607284d2023-04-20 03:14:52 +0000261@contextlib.contextmanager
Ben Segalle49349b2023-06-01 02:54:56 +0000262def build_context(argv, tool):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000263 # If use_remoteexec is set, but the reclient binaries or configs don't
264 # exist, display an error message and stop. Otherwise, the build will
265 # attempt to run with rewrapper wrapping actions, but will fail with
266 # possible non-obvious problems.
267 reclient_bin_dir = find_reclient_bin_dir()
268 reclient_cfg = find_reclient_cfg()
269 if reclient_bin_dir is None or reclient_cfg is None:
270 print(
271 'Build is configured to use reclient but necessary binaries '
272 "or config files can't be found.\n"
273 'Please check if `"download_remoteexec_cfg": True` custom var is '
274 'set in `.gclient`, and run `gclient sync`.',
275 file=sys.stderr)
276 yield 1
277 return
Ben Segall530d86d2023-05-29 16:11:00 +0000278
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000279 ninja_out = find_ninja_out_dir(argv)
Ben Segall530d86d2023-05-29 16:11:00 +0000280
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000281 try:
282 set_reproxy_path_flags(ninja_out)
Michael Savignyaf692492023-11-17 20:16:53 +0000283 except OSError as e:
284 print(f"Error creating reproxy_tmp in output dir: {e}", file=sys.stderr)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000285 yield 1
286 return
Ben Segall530d86d2023-05-29 16:11:00 +0000287
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000288 if reclient_metrics.check_status(ninja_out):
289 set_reproxy_metrics_flags(tool)
Ben Segall530d86d2023-05-29 16:11:00 +0000290
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000291 if os.environ.get('RBE_instance', None):
292 print('WARNING: Using RBE_instance=%s\n' %
293 os.environ.get('RBE_instance', ''))
Fumitoshi Ukaidedeb882023-06-15 03:58:35 +0000294
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000295 remote_disabled = os.environ.get('RBE_remote_disabled')
296 if remote_disabled not in ('1', 't', 'T', 'true', 'TRUE', 'True'):
297 set_racing_defaults()
Michael Savigny8d97b6d2023-12-01 16:53:43 +0000298 if sys.platform == "darwin":
299 set_mac_defaults()
Ben Segalld3e43dd2023-07-25 02:32:31 +0000300
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000301 # TODO(b/292523514) remove this once a fix is landed in reproxy
302 remove_mdproxy_from_path()
Ben Segall3589c482023-07-24 17:42:55 +0000303
Bruce Dawson6d0c2352023-08-06 02:38:51 +0000304 start = time.time()
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000305 reproxy_ret_code = start_reproxy(reclient_cfg, reclient_bin_dir)
Ben Segallc18c91e2023-09-14 15:25:04 +0000306 if os.environ.get('NINJA_SUMMARIZE_BUILD') == '1':
307 elapsed = time.time() - start
308 print('%1.3f s to start reproxy' % elapsed)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000309 if reproxy_ret_code != 0:
310 yield reproxy_ret_code
311 return
312 try:
313 yield
314 finally:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000315 start = time.time()
316 stop_reproxy(reclient_cfg, reclient_bin_dir)
Ben Segallc18c91e2023-09-14 15:25:04 +0000317 if os.environ.get('NINJA_SUMMARIZE_BUILD') == '1':
318 elapsed = time.time() - start
319 print('%1.3f s to stop reproxy' % elapsed)