blob: 0e38776ff3769ba45895d191b8708b6073515fb4 [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
Junji Watanabe607284d2023-04-20 03:14:52 +000013import subprocess
14import sys
Bruce Dawson6d0c2352023-08-06 02:38:51 +000015import time
Junji Watanabe607284d2023-04-20 03:14:52 +000016
17import gclient_paths
Ben Segall530d86d2023-05-29 16:11:00 +000018import reclient_metrics
Junji Watanabe607284d2023-04-20 03:14:52 +000019
20
21def find_reclient_bin_dir():
Mike Frysinger124bb8e2023-09-06 05:48:55 +000022 tools_path = gclient_paths.GetBuildtoolsPath()
23 if not tools_path:
24 return None
Junji Watanabe607284d2023-04-20 03:14:52 +000025
Mike Frysinger124bb8e2023-09-06 05:48:55 +000026 reclient_bin_dir = os.path.join(tools_path, 'reclient')
27 if os.path.isdir(reclient_bin_dir):
28 return reclient_bin_dir
29 return None
Junji Watanabe607284d2023-04-20 03:14:52 +000030
31
32def find_reclient_cfg():
Mike Frysinger124bb8e2023-09-06 05:48:55 +000033 tools_path = gclient_paths.GetBuildtoolsPath()
34 if not tools_path:
35 return None
Junji Watanabe607284d2023-04-20 03:14:52 +000036
Mike Frysinger124bb8e2023-09-06 05:48:55 +000037 reclient_cfg = os.path.join(tools_path, 'reclient_cfgs', 'reproxy.cfg')
38 if os.path.isfile(reclient_cfg):
39 return reclient_cfg
40 return None
Junji Watanabe607284d2023-04-20 03:14:52 +000041
42
43def run(cmd_args):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000044 if os.environ.get('NINJA_SUMMARIZE_BUILD') == '1':
45 print(' '.join(cmd_args))
46 return subprocess.call(cmd_args)
Junji Watanabe607284d2023-04-20 03:14:52 +000047
48
49def start_reproxy(reclient_cfg, reclient_bin_dir):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000050 return run([
51 os.path.join(reclient_bin_dir,
52 'bootstrap' + gclient_paths.GetExeSuffix()),
53 '--re_proxy=' + os.path.join(reclient_bin_dir,
54 'reproxy' + gclient_paths.GetExeSuffix()),
55 '--cfg=' + reclient_cfg
56 ])
Junji Watanabe607284d2023-04-20 03:14:52 +000057
58
59def stop_reproxy(reclient_cfg, reclient_bin_dir):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000060 return run([
61 os.path.join(reclient_bin_dir,
62 'bootstrap' + gclient_paths.GetExeSuffix()), '--shutdown',
63 '--cfg=' + reclient_cfg
64 ])
Junji Watanabe607284d2023-04-20 03:14:52 +000065
66
67def find_ninja_out_dir(args):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000068 # Ninja uses getopt_long, which allows to intermix non-option arguments.
69 # To leave non supported parameters untouched, we do not use getopt.
70 for index, arg in enumerate(args[1:]):
71 if arg == '-C':
72 # + 1 to get the next argument and +1 because we trimmed off args[0]
73 return args[index + 2]
74 if arg.startswith('-C'):
75 # Support -Cout/Default
76 return arg[2:]
77 return '.'
Junji Watanabe607284d2023-04-20 03:14:52 +000078
79
Ben Segall7a0fe8b2023-04-24 20:36:55 +000080def find_cache_dir(tmp_dir):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000081 """Helper to find the correct cache directory for a build.
Ben Segall7a0fe8b2023-04-24 20:36:55 +000082
83 tmp_dir should be a build specific temp directory within the out directory.
84
85 If this is called from within a gclient checkout, the cache dir will be:
86 <gclient_root>/.reproxy_cache/md5(tmp_dir)/
87 If this is not called from within a gclient checkout, the cache dir will be:
88 tmp_dir/cache
89 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +000090 gclient_root = gclient_paths.FindGclientRoot(os.getcwd())
91 if gclient_root:
92 return os.path.join(gclient_root, '.reproxy_cache',
93 hashlib.md5(tmp_dir.encode()).hexdigest())
94 return os.path.join(tmp_dir, 'cache')
Ben Segall7a0fe8b2023-04-24 20:36:55 +000095
96
Ben Segall29282b52023-09-07 14:52:08 +000097def auth_cache_status():
98 cred_file = os.path.join(os.environ["RBE_cache_dir"], "reproxy.creds")
99 if not os.path.isfile(cred_file):
100 return "missing", "UNSPECIFIED"
101 try:
102 with open(cred_file) as f:
103 status = "valid"
104 mechanism = "UNSPECIFIED"
105 for line in f.readlines():
106 if "seconds:" in line:
107 exp = int(line.strip()[len("seconds:"):].strip())
108 if exp < (time.time() + 5 * 60):
109 status = "expired"
110 elif "mechanism:" in line:
111 mechanism = line.strip()[len("mechanism:"):].strip()
112 return status, mechanism
113 except OSError:
114 return "missing", "UNSPECIFIED"
115
116
Ben Segalle49349b2023-06-01 02:54:56 +0000117def set_reproxy_metrics_flags(tool):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000118 """Helper to setup metrics collection flags for reproxy.
Ben Segall530d86d2023-05-29 16:11:00 +0000119
120 The following env vars are set if not already set:
121 RBE_metrics_project=chromium-reclient-metrics
122 RBE_invocation_id=$AUTONINJA_BUILD_ID
123 RBE_metrics_table=rbe_metrics.builds
Ben Segalle49349b2023-06-01 02:54:56 +0000124 RBE_metrics_labels=source=developer,tool={tool}
Ben Segall530d86d2023-05-29 16:11:00 +0000125 RBE_metrics_prefix=go.chromium.org
126 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000127 autoninja_id = os.environ.get("AUTONINJA_BUILD_ID")
128 if autoninja_id is not None:
129 os.environ.setdefault("RBE_invocation_id", autoninja_id)
130 os.environ.setdefault("RBE_metrics_project", "chromium-reclient-metrics")
131 os.environ.setdefault("RBE_metrics_table", "rbe_metrics.builds")
Ben Segall29282b52023-09-07 14:52:08 +0000132 labels = "source=developer,tool=" + tool
133 auth_status, auth_mechanism = auth_cache_status()
134 labels += ",creds_cache_status=" + auth_status
135 labels += ",creds_cache_mechanism=" + auth_mechanism
136 os.environ.setdefault("RBE_metrics_labels", labels)
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000137 os.environ.setdefault("RBE_metrics_prefix", "go.chromium.org")
Ben Segall530d86d2023-05-29 16:11:00 +0000138
139
Ben Segall3589c482023-07-24 17:42:55 +0000140def remove_mdproxy_from_path():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000141 os.environ["PATH"] = os.pathsep.join(
142 d for d in os.environ.get("PATH", "").split(os.pathsep)
143 if "mdproxy" not in d)
Ben Segall3589c482023-07-24 17:42:55 +0000144
145
Junji Watanabe607284d2023-04-20 03:14:52 +0000146def set_reproxy_path_flags(out_dir, make_dirs=True):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000147 """Helper to setup the logs and cache directories for reclient.
Junji Watanabe607284d2023-04-20 03:14:52 +0000148
149 Creates the following directory structure if make_dirs is true:
Ben Segall7a0fe8b2023-04-24 20:36:55 +0000150 If in a gclient checkout
151 out_dir/
152 .reproxy_tmp/
153 logs/
154 <gclient_root>
155 .reproxy_cache/
156 md5(out_dir/.reproxy_tmp)/
157
158 If not in a gclient checkout
Junji Watanabe607284d2023-04-20 03:14:52 +0000159 out_dir/
160 .reproxy_tmp/
161 logs/
162 cache/
163
164 The following env vars are set if not already set:
165 RBE_output_dir=out_dir/.reproxy_tmp/logs
166 RBE_proxy_log_dir=out_dir/.reproxy_tmp/logs
167 RBE_log_dir=out_dir/.reproxy_tmp/logs
168 RBE_cache_dir=out_dir/.reproxy_tmp/cache
169 *Nix Only:
170 RBE_server_address=unix://out_dir/.reproxy_tmp/reproxy.sock
171 Windows Only:
172 RBE_server_address=pipe://md5(out_dir/.reproxy_tmp)/reproxy.pipe
173 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000174 tmp_dir = os.path.abspath(os.path.join(out_dir, '.reproxy_tmp'))
175 log_dir = os.path.join(tmp_dir, 'logs')
176 racing_dir = os.path.join(tmp_dir, 'racing')
177 cache_dir = find_cache_dir(tmp_dir)
178 if make_dirs:
179 if os.path.exists(log_dir):
180 try:
181 # Clear log dir before each build to ensure correct metric
182 # aggregation.
183 shutil.rmtree(log_dir)
184 except OSError:
185 print(
186 "Couldn't clear logs because reproxy did "
187 "not shutdown after the last build",
188 file=sys.stderr)
189 os.makedirs(tmp_dir, exist_ok=True)
190 os.makedirs(log_dir, exist_ok=True)
191 os.makedirs(cache_dir, exist_ok=True)
192 os.makedirs(racing_dir, exist_ok=True)
193 os.environ.setdefault("RBE_output_dir", log_dir)
194 os.environ.setdefault("RBE_proxy_log_dir", log_dir)
195 os.environ.setdefault("RBE_log_dir", log_dir)
196 os.environ.setdefault("RBE_cache_dir", cache_dir)
197 os.environ.setdefault("RBE_racing_tmp_dir", racing_dir)
198 if sys.platform.startswith('win'):
199 pipe_dir = hashlib.md5(tmp_dir.encode()).hexdigest()
200 os.environ.setdefault("RBE_server_address",
201 "pipe://%s/reproxy.pipe" % pipe_dir)
202 else:
203 # unix domain socket has path length limit, so use fixed size path here.
204 # ref: https://www.man7.org/linux/man-pages/man7/unix.7.html
205 os.environ.setdefault(
206 "RBE_server_address", "unix:///tmp/reproxy_%s.sock" %
207 hashlib.sha256(tmp_dir.encode()).hexdigest())
Junji Watanabe607284d2023-04-20 03:14:52 +0000208
209
Ben Segalld3e43dd2023-07-25 02:32:31 +0000210def set_racing_defaults():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000211 os.environ.setdefault("RBE_local_resource_fraction", "0.2")
212 os.environ.setdefault("RBE_racing_bias", "0.95")
Ben Segalld3e43dd2023-07-25 02:32:31 +0000213
Ben Segall04ca3832023-07-19 01:50:59 +0000214
Junji Watanabe607284d2023-04-20 03:14:52 +0000215@contextlib.contextmanager
Ben Segalle49349b2023-06-01 02:54:56 +0000216def build_context(argv, tool):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000217 # If use_remoteexec is set, but the reclient binaries or configs don't
218 # exist, display an error message and stop. Otherwise, the build will
219 # attempt to run with rewrapper wrapping actions, but will fail with
220 # possible non-obvious problems.
221 reclient_bin_dir = find_reclient_bin_dir()
222 reclient_cfg = find_reclient_cfg()
223 if reclient_bin_dir is None or reclient_cfg is None:
224 print(
225 'Build is configured to use reclient but necessary binaries '
226 "or config files can't be found.\n"
227 'Please check if `"download_remoteexec_cfg": True` custom var is '
228 'set in `.gclient`, and run `gclient sync`.',
229 file=sys.stderr)
230 yield 1
231 return
Ben Segall530d86d2023-05-29 16:11:00 +0000232
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000233 ninja_out = find_ninja_out_dir(argv)
Ben Segall530d86d2023-05-29 16:11:00 +0000234
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000235 try:
236 set_reproxy_path_flags(ninja_out)
237 except OSError:
238 print("Error creating reproxy_tmp in output dir", file=sys.stderr)
239 yield 1
240 return
Ben Segall530d86d2023-05-29 16:11:00 +0000241
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000242 if reclient_metrics.check_status(ninja_out):
243 set_reproxy_metrics_flags(tool)
Ben Segall530d86d2023-05-29 16:11:00 +0000244
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000245 if os.environ.get('RBE_instance', None):
246 print('WARNING: Using RBE_instance=%s\n' %
247 os.environ.get('RBE_instance', ''))
Fumitoshi Ukaidedeb882023-06-15 03:58:35 +0000248
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000249 remote_disabled = os.environ.get('RBE_remote_disabled')
250 if remote_disabled not in ('1', 't', 'T', 'true', 'TRUE', 'True'):
251 set_racing_defaults()
Ben Segalld3e43dd2023-07-25 02:32:31 +0000252
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000253 # TODO(b/292523514) remove this once a fix is landed in reproxy
254 remove_mdproxy_from_path()
Ben Segall3589c482023-07-24 17:42:55 +0000255
Bruce Dawson6d0c2352023-08-06 02:38:51 +0000256 start = time.time()
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000257 reproxy_ret_code = start_reproxy(reclient_cfg, reclient_bin_dir)
Bruce Dawson6d0c2352023-08-06 02:38:51 +0000258 elapsed = time.time() - start
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000259 print('%1.3f s to start reproxy' % elapsed)
260 if reproxy_ret_code != 0:
261 yield reproxy_ret_code
262 return
263 try:
264 yield
265 finally:
266 print("Shutting down reproxy...", file=sys.stderr)
267 start = time.time()
268 stop_reproxy(reclient_cfg, reclient_bin_dir)
269 elapsed = time.time() - start
270 print('%1.3f s to stop reproxy' % elapsed)