blob: ddeb403c57c9a6e4ded011ed8884f1aac83d976f [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
Junji Watanabe607284d2023-04-20 03:14:52 +0000167def set_reproxy_path_flags(out_dir, make_dirs=True):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000168 """Helper to setup the logs and cache directories for reclient.
Junji Watanabe607284d2023-04-20 03:14:52 +0000169
170 Creates the following directory structure if make_dirs is true:
Ben Segall7a0fe8b2023-04-24 20:36:55 +0000171 If in a gclient checkout
172 out_dir/
173 .reproxy_tmp/
174 logs/
175 <gclient_root>
176 .reproxy_cache/
177 md5(out_dir/.reproxy_tmp)/
178
179 If not in a gclient checkout
Junji Watanabe607284d2023-04-20 03:14:52 +0000180 out_dir/
181 .reproxy_tmp/
182 logs/
183 cache/
184
185 The following env vars are set if not already set:
186 RBE_output_dir=out_dir/.reproxy_tmp/logs
187 RBE_proxy_log_dir=out_dir/.reproxy_tmp/logs
188 RBE_log_dir=out_dir/.reproxy_tmp/logs
189 RBE_cache_dir=out_dir/.reproxy_tmp/cache
190 *Nix Only:
191 RBE_server_address=unix://out_dir/.reproxy_tmp/reproxy.sock
192 Windows Only:
193 RBE_server_address=pipe://md5(out_dir/.reproxy_tmp)/reproxy.pipe
194 """
Ben Segall27ea34f2023-10-24 16:03:33 +0000195 os.environ.setdefault("AUTONINJA_BUILD_ID", str(uuid.uuid4()))
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000196 tmp_dir = os.path.abspath(os.path.join(out_dir, '.reproxy_tmp'))
197 log_dir = os.path.join(tmp_dir, 'logs')
Ben Segall27ea34f2023-10-24 16:03:33 +0000198 run_log_dir = os.path.join(
199 log_dir,
200 datetime_now().strftime('%Y%m%dT%H%M%S.%f') + "_" +
201 os.environ["AUTONINJA_BUILD_ID"])
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000202 racing_dir = os.path.join(tmp_dir, 'racing')
203 cache_dir = find_cache_dir(tmp_dir)
204 if make_dirs:
Ben Segall27ea34f2023-10-24 16:03:33 +0000205 if os.path.isfile(os.path.join(log_dir, "rbe_metrics.txt")):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000206 try:
Ben Segall27ea34f2023-10-24 16:03:33 +0000207 # Delete entire log dir if it is in the old format
208 # which had no subdirectories for each build.
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000209 shutil.rmtree(log_dir)
210 except OSError:
211 print(
212 "Couldn't clear logs because reproxy did "
213 "not shutdown after the last build",
214 file=sys.stderr)
215 os.makedirs(tmp_dir, exist_ok=True)
216 os.makedirs(log_dir, exist_ok=True)
Ben Segall27ea34f2023-10-24 16:03:33 +0000217 os.makedirs(run_log_dir, exist_ok=True)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000218 os.makedirs(cache_dir, exist_ok=True)
219 os.makedirs(racing_dir, exist_ok=True)
Ben Segall27ea34f2023-10-24 16:03:33 +0000220 old_log_dirs = os.listdir(log_dir)
221 if len(old_log_dirs) > 5:
222 old_log_dirs.sort(key=lambda dir: dir.split("_"), reverse=True)
223 for d in old_log_dirs[5:]:
Takuto Ikuta53889592023-10-31 04:55:37 +0000224 shutil.rmtree(os.path.join(log_dir, d))
225
Ben Segall27ea34f2023-10-24 16:03:33 +0000226 os.environ.setdefault("RBE_output_dir", run_log_dir)
227 os.environ.setdefault("RBE_proxy_log_dir", run_log_dir)
228 os.environ.setdefault("RBE_log_dir", run_log_dir)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000229 os.environ.setdefault("RBE_cache_dir", cache_dir)
230 os.environ.setdefault("RBE_racing_tmp_dir", racing_dir)
231 if sys.platform.startswith('win'):
232 pipe_dir = hashlib.md5(tmp_dir.encode()).hexdigest()
233 os.environ.setdefault("RBE_server_address",
234 "pipe://%s/reproxy.pipe" % pipe_dir)
235 else:
236 # unix domain socket has path length limit, so use fixed size path here.
237 # ref: https://www.man7.org/linux/man-pages/man7/unix.7.html
238 os.environ.setdefault(
239 "RBE_server_address", "unix:///tmp/reproxy_%s.sock" %
240 hashlib.sha256(tmp_dir.encode()).hexdigest())
Junji Watanabe607284d2023-04-20 03:14:52 +0000241
242
Ben Segalld3e43dd2023-07-25 02:32:31 +0000243def set_racing_defaults():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000244 os.environ.setdefault("RBE_local_resource_fraction", "0.2")
245 os.environ.setdefault("RBE_racing_bias", "0.95")
Ben Segalld3e43dd2023-07-25 02:32:31 +0000246
Ben Segall04ca3832023-07-19 01:50:59 +0000247
Junji Watanabe607284d2023-04-20 03:14:52 +0000248@contextlib.contextmanager
Ben Segalle49349b2023-06-01 02:54:56 +0000249def build_context(argv, tool):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000250 # If use_remoteexec is set, but the reclient binaries or configs don't
251 # exist, display an error message and stop. Otherwise, the build will
252 # attempt to run with rewrapper wrapping actions, but will fail with
253 # possible non-obvious problems.
254 reclient_bin_dir = find_reclient_bin_dir()
255 reclient_cfg = find_reclient_cfg()
256 if reclient_bin_dir is None or reclient_cfg is None:
257 print(
258 'Build is configured to use reclient but necessary binaries '
259 "or config files can't be found.\n"
260 'Please check if `"download_remoteexec_cfg": True` custom var is '
261 'set in `.gclient`, and run `gclient sync`.',
262 file=sys.stderr)
263 yield 1
264 return
Ben Segall530d86d2023-05-29 16:11:00 +0000265
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000266 ninja_out = find_ninja_out_dir(argv)
Ben Segall530d86d2023-05-29 16:11:00 +0000267
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000268 try:
269 set_reproxy_path_flags(ninja_out)
270 except OSError:
271 print("Error creating reproxy_tmp in output dir", file=sys.stderr)
272 yield 1
273 return
Ben Segall530d86d2023-05-29 16:11:00 +0000274
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000275 if reclient_metrics.check_status(ninja_out):
276 set_reproxy_metrics_flags(tool)
Ben Segall530d86d2023-05-29 16:11:00 +0000277
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000278 if os.environ.get('RBE_instance', None):
279 print('WARNING: Using RBE_instance=%s\n' %
280 os.environ.get('RBE_instance', ''))
Fumitoshi Ukaidedeb882023-06-15 03:58:35 +0000281
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000282 remote_disabled = os.environ.get('RBE_remote_disabled')
283 if remote_disabled not in ('1', 't', 'T', 'true', 'TRUE', 'True'):
284 set_racing_defaults()
Ben Segalld3e43dd2023-07-25 02:32:31 +0000285
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000286 # TODO(b/292523514) remove this once a fix is landed in reproxy
287 remove_mdproxy_from_path()
Ben Segall3589c482023-07-24 17:42:55 +0000288
Bruce Dawson6d0c2352023-08-06 02:38:51 +0000289 start = time.time()
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000290 reproxy_ret_code = start_reproxy(reclient_cfg, reclient_bin_dir)
Ben Segallc18c91e2023-09-14 15:25:04 +0000291 if os.environ.get('NINJA_SUMMARIZE_BUILD') == '1':
292 elapsed = time.time() - start
293 print('%1.3f s to start reproxy' % elapsed)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000294 if reproxy_ret_code != 0:
295 yield reproxy_ret_code
296 return
297 try:
298 yield
299 finally:
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000300 start = time.time()
301 stop_reproxy(reclient_cfg, reclient_bin_dir)
Ben Segallc18c91e2023-09-14 15:25:04 +0000302 if os.environ.get('NINJA_SUMMARIZE_BUILD') == '1':
303 elapsed = time.time() - start
304 print('%1.3f s to stop reproxy' % elapsed)