blob: 05fdc3b03a6608d7d1eebc298802aaf173250e5c [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 Segalle49349b2023-06-01 02:54:56 +000097def set_reproxy_metrics_flags(tool):
Mike Frysinger124bb8e2023-09-06 05:48:55 +000098 """Helper to setup metrics collection flags for reproxy.
Ben Segall530d86d2023-05-29 16:11:00 +000099
100 The following env vars are set if not already set:
101 RBE_metrics_project=chromium-reclient-metrics
102 RBE_invocation_id=$AUTONINJA_BUILD_ID
103 RBE_metrics_table=rbe_metrics.builds
Ben Segalle49349b2023-06-01 02:54:56 +0000104 RBE_metrics_labels=source=developer,tool={tool}
Ben Segall530d86d2023-05-29 16:11:00 +0000105 RBE_metrics_prefix=go.chromium.org
106 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000107 autoninja_id = os.environ.get("AUTONINJA_BUILD_ID")
108 if autoninja_id is not None:
109 os.environ.setdefault("RBE_invocation_id", autoninja_id)
110 os.environ.setdefault("RBE_metrics_project", "chromium-reclient-metrics")
111 os.environ.setdefault("RBE_metrics_table", "rbe_metrics.builds")
112 os.environ.setdefault("RBE_metrics_labels", "source=developer,tool=" + tool)
113 os.environ.setdefault("RBE_metrics_prefix", "go.chromium.org")
Ben Segall530d86d2023-05-29 16:11:00 +0000114
115
Ben Segall3589c482023-07-24 17:42:55 +0000116def remove_mdproxy_from_path():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000117 os.environ["PATH"] = os.pathsep.join(
118 d for d in os.environ.get("PATH", "").split(os.pathsep)
119 if "mdproxy" not in d)
Ben Segall3589c482023-07-24 17:42:55 +0000120
121
Junji Watanabe607284d2023-04-20 03:14:52 +0000122def set_reproxy_path_flags(out_dir, make_dirs=True):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000123 """Helper to setup the logs and cache directories for reclient.
Junji Watanabe607284d2023-04-20 03:14:52 +0000124
125 Creates the following directory structure if make_dirs is true:
Ben Segall7a0fe8b2023-04-24 20:36:55 +0000126 If in a gclient checkout
127 out_dir/
128 .reproxy_tmp/
129 logs/
130 <gclient_root>
131 .reproxy_cache/
132 md5(out_dir/.reproxy_tmp)/
133
134 If not in a gclient checkout
Junji Watanabe607284d2023-04-20 03:14:52 +0000135 out_dir/
136 .reproxy_tmp/
137 logs/
138 cache/
139
140 The following env vars are set if not already set:
141 RBE_output_dir=out_dir/.reproxy_tmp/logs
142 RBE_proxy_log_dir=out_dir/.reproxy_tmp/logs
143 RBE_log_dir=out_dir/.reproxy_tmp/logs
144 RBE_cache_dir=out_dir/.reproxy_tmp/cache
145 *Nix Only:
146 RBE_server_address=unix://out_dir/.reproxy_tmp/reproxy.sock
147 Windows Only:
148 RBE_server_address=pipe://md5(out_dir/.reproxy_tmp)/reproxy.pipe
149 """
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000150 tmp_dir = os.path.abspath(os.path.join(out_dir, '.reproxy_tmp'))
151 log_dir = os.path.join(tmp_dir, 'logs')
152 racing_dir = os.path.join(tmp_dir, 'racing')
153 cache_dir = find_cache_dir(tmp_dir)
154 if make_dirs:
155 if os.path.exists(log_dir):
156 try:
157 # Clear log dir before each build to ensure correct metric
158 # aggregation.
159 shutil.rmtree(log_dir)
160 except OSError:
161 print(
162 "Couldn't clear logs because reproxy did "
163 "not shutdown after the last build",
164 file=sys.stderr)
165 os.makedirs(tmp_dir, exist_ok=True)
166 os.makedirs(log_dir, exist_ok=True)
167 os.makedirs(cache_dir, exist_ok=True)
168 os.makedirs(racing_dir, exist_ok=True)
169 os.environ.setdefault("RBE_output_dir", log_dir)
170 os.environ.setdefault("RBE_proxy_log_dir", log_dir)
171 os.environ.setdefault("RBE_log_dir", log_dir)
172 os.environ.setdefault("RBE_cache_dir", cache_dir)
173 os.environ.setdefault("RBE_racing_tmp_dir", racing_dir)
174 if sys.platform.startswith('win'):
175 pipe_dir = hashlib.md5(tmp_dir.encode()).hexdigest()
176 os.environ.setdefault("RBE_server_address",
177 "pipe://%s/reproxy.pipe" % pipe_dir)
178 else:
179 # unix domain socket has path length limit, so use fixed size path here.
180 # ref: https://www.man7.org/linux/man-pages/man7/unix.7.html
181 os.environ.setdefault(
182 "RBE_server_address", "unix:///tmp/reproxy_%s.sock" %
183 hashlib.sha256(tmp_dir.encode()).hexdigest())
Junji Watanabe607284d2023-04-20 03:14:52 +0000184
185
Ben Segalld3e43dd2023-07-25 02:32:31 +0000186def set_racing_defaults():
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000187 os.environ.setdefault("RBE_local_resource_fraction", "0.2")
188 os.environ.setdefault("RBE_racing_bias", "0.95")
Ben Segalld3e43dd2023-07-25 02:32:31 +0000189
Ben Segall04ca3832023-07-19 01:50:59 +0000190
Junji Watanabe607284d2023-04-20 03:14:52 +0000191@contextlib.contextmanager
Ben Segalle49349b2023-06-01 02:54:56 +0000192def build_context(argv, tool):
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000193 # If use_remoteexec is set, but the reclient binaries or configs don't
194 # exist, display an error message and stop. Otherwise, the build will
195 # attempt to run with rewrapper wrapping actions, but will fail with
196 # possible non-obvious problems.
197 reclient_bin_dir = find_reclient_bin_dir()
198 reclient_cfg = find_reclient_cfg()
199 if reclient_bin_dir is None or reclient_cfg is None:
200 print(
201 'Build is configured to use reclient but necessary binaries '
202 "or config files can't be found.\n"
203 'Please check if `"download_remoteexec_cfg": True` custom var is '
204 'set in `.gclient`, and run `gclient sync`.',
205 file=sys.stderr)
206 yield 1
207 return
Ben Segall530d86d2023-05-29 16:11:00 +0000208
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000209 ninja_out = find_ninja_out_dir(argv)
Ben Segall530d86d2023-05-29 16:11:00 +0000210
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000211 try:
212 set_reproxy_path_flags(ninja_out)
213 except OSError:
214 print("Error creating reproxy_tmp in output dir", file=sys.stderr)
215 yield 1
216 return
Ben Segall530d86d2023-05-29 16:11:00 +0000217
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000218 if reclient_metrics.check_status(ninja_out):
219 set_reproxy_metrics_flags(tool)
Ben Segall530d86d2023-05-29 16:11:00 +0000220
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000221 if os.environ.get('RBE_instance', None):
222 print('WARNING: Using RBE_instance=%s\n' %
223 os.environ.get('RBE_instance', ''))
Fumitoshi Ukaidedeb882023-06-15 03:58:35 +0000224
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000225 remote_disabled = os.environ.get('RBE_remote_disabled')
226 if remote_disabled not in ('1', 't', 'T', 'true', 'TRUE', 'True'):
227 set_racing_defaults()
Ben Segalld3e43dd2023-07-25 02:32:31 +0000228
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000229 # TODO(b/292523514) remove this once a fix is landed in reproxy
230 remove_mdproxy_from_path()
Ben Segall3589c482023-07-24 17:42:55 +0000231
Bruce Dawson6d0c2352023-08-06 02:38:51 +0000232 start = time.time()
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000233 reproxy_ret_code = start_reproxy(reclient_cfg, reclient_bin_dir)
Bruce Dawson6d0c2352023-08-06 02:38:51 +0000234 elapsed = time.time() - start
Mike Frysinger124bb8e2023-09-06 05:48:55 +0000235 print('%1.3f s to start reproxy' % elapsed)
236 if reproxy_ret_code != 0:
237 yield reproxy_ret_code
238 return
239 try:
240 yield
241 finally:
242 print("Shutting down reproxy...", file=sys.stderr)
243 start = time.time()
244 stop_reproxy(reclient_cfg, reclient_bin_dir)
245 elapsed = time.time() - start
246 print('%1.3f s to stop reproxy' % elapsed)