blob: c12aac4ba409f062a05bc4ef3082c6cb8132f1d1 [file] [log] [blame]
Takuto Ikuta237cc462022-01-19 18:04:15 +00001#!/usr/bin/env python3
Bruce Dawsonebebd952017-05-31 14:24:38 -07002# Copyright (c) 2017 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""
7This script (intended to be invoked by autoninja or autoninja.bat) detects
Simeon Anfinrud5dba9c92020-09-03 20:02:05 +00008whether a build is accelerated using a service like goma. If so, it runs with a
9large -j value, and otherwise it chooses a small one. This auto-adjustment
10makes using remote build acceleration simpler and safer, and avoids errors that
11can cause slow goma builds or swap-storms on unaccelerated builds.
Bruce Dawsonebebd952017-05-31 14:24:38 -070012"""
13
Raul Tambre80ee78e2019-05-06 22:41:05 +000014from __future__ import print_function
15
Bruce Dawsone952fae2021-02-27 23:33:37 +000016import multiprocessing
Bruce Dawsonebebd952017-05-31 14:24:38 -070017import os
Takuto Ikuta6a1494e2022-05-06 01:22:16 +000018import platform
Bruce Dawsonebebd952017-05-31 14:24:38 -070019import re
Bruce Dawsone952fae2021-02-27 23:33:37 +000020import subprocess
Bruce Dawsonebebd952017-05-31 14:24:38 -070021import sys
22
Yoshisato Yanagisawa4b497072018-11-07 02:52:33 +000023SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
24
Yoshisato Yanagisawaf66e5512018-11-15 00:40:39 +000025
Michael Savignyf52f44b2022-10-26 14:25:55 +000026def exit_with_message(msg):
27 print(msg, file=sys.stderr)
28 if sys.platform.startswith('win'):
29 # Set an exit code of 1 in the batch file.
30 print('cmd "/c exit 1"')
31 else:
32 # Set an exit code of 1 by executing 'false' in the bash script.
33 print('false')
34 sys.exit(1)
35
36
Takuto Ikuta381db682022-04-27 23:54:02 +000037def main(args):
38 # The -t tools are incompatible with -j
39 t_specified = False
40 j_specified = False
41 offline = False
42 output_dir = '.'
43 input_args = args
44 # On Windows the autoninja.bat script passes along the arguments enclosed in
45 # double quotes. This prevents multiple levels of parsing of the special '^'
46 # characters needed when compiling a single file but means that this script
47 # gets called with a single argument containing all of the actual arguments,
48 # separated by spaces. When this case is detected we need to do argument
49 # splitting ourselves. This means that arguments containing actual spaces are
50 # not supported by autoninja, but that is not a real limitation.
51 if (sys.platform.startswith('win') and len(args) == 2
52 and input_args[1].count(' ') > 0):
53 input_args = args[:1] + args[1].split()
Bruce Dawson655afeb2020-11-02 23:30:37 +000054
Takuto Ikuta381db682022-04-27 23:54:02 +000055 # Ninja uses getopt_long, which allow to intermix non-option arguments.
56 # To leave non supported parameters untouched, we do not use getopt.
57 for index, arg in enumerate(input_args[1:]):
58 if arg.startswith('-j'):
59 j_specified = True
60 if arg.startswith('-t'):
61 t_specified = True
62 if arg == '-C':
63 # + 1 to get the next argument and +1 because we trimmed off input_args[0]
64 output_dir = input_args[index + 2]
65 elif arg.startswith('-C'):
66 # Support -Cout/Default
67 output_dir = arg[2:]
68 elif arg in ('-o', '--offline'):
69 offline = True
70 elif arg == '-h':
71 print('autoninja: Use -o/--offline to temporary disable goma.',
Bruce Dawsone952fae2021-02-27 23:33:37 +000072 file=sys.stderr)
Takuto Ikuta381db682022-04-27 23:54:02 +000073 print(file=sys.stderr)
Bruce Dawsone952fae2021-02-27 23:33:37 +000074
Takuto Ikuta381db682022-04-27 23:54:02 +000075 # Strip -o/--offline so ninja doesn't see them.
76 input_args = [arg for arg in input_args if arg not in ('-o', '--offline')]
Allen Bauer75fa8552018-11-07 22:43:39 +000077
Michael Savignyf52f44b2022-10-26 14:25:55 +000078 # Arguments to detect for remote execution. The use_rbe and
79 # reclient_bootstrap_enabled are intended to be deprecated in the future
80 # but need special handling for the moment.
Takuto Ikuta381db682022-04-27 23:54:02 +000081 use_goma = False
82 use_remoteexec = False
Michael Savignyf52f44b2022-10-26 14:25:55 +000083 use_rbe = False
84 reclient_bootstrap_enabled = True
Peter McNeeley958dc622020-10-18 17:05:04 +000085
Takuto Ikuta381db682022-04-27 23:54:02 +000086 # Currently get reclient binary and config dirs relative to output_dir. If
87 # they exist and using remoteexec, then automatically call bootstrap to start
88 # reproxy. This works under the current assumption that the output
89 # directory is two levels up from chromium/src.
90 reclient_bin_dir = os.path.join(output_dir, '..', '..', 'buildtools',
91 'reclient')
92 reclient_cfg = os.path.join(output_dir, '..', '..', 'buildtools',
93 'reclient_cfgs', 'reproxy.cfg')
Peter McNeeley958dc622020-10-18 17:05:04 +000094
Takuto Ikuta381db682022-04-27 23:54:02 +000095 # Attempt to auto-detect remote build acceleration. We support gn-based
96 # builds, where we look for args.gn in the build tree, and cmake-based builds
97 # where we look for rules.ninja.
98 if os.path.exists(os.path.join(output_dir, 'args.gn')):
99 with open(os.path.join(output_dir, 'args.gn')) as file_handle:
100 for line in file_handle:
Richard Wangbb07d9e2022-07-07 02:28:59 +0000101 # Either use_goma or use_remoteexec will activate build acceleration.
Takuto Ikuta381db682022-04-27 23:54:02 +0000102 #
Michael Savignyf52f44b2022-10-26 14:25:55 +0000103 # The older use_rbe argument will also activate build acceleration, but
104 # will also trigger checks against the enable_rbe_bootstrap argument.
105 #
Takuto Ikuta381db682022-04-27 23:54:02 +0000106 # This test can match multi-argument lines. Examples of this are:
107 # is_debug=false use_goma=true is_official_build=false
108 # use_goma=false# use_goma=true This comment is ignored
109 #
110 # Anything after a comment is not consider a valid argument.
111 line_without_comment = line.split('#')[0]
112 if re.search(r'(^|\s)(use_goma)\s*=\s*true($|\s)',
113 line_without_comment):
114 use_goma = True
115 continue
Richard Wangbb07d9e2022-07-07 02:28:59 +0000116 if re.search(r'(^|\s)(use_remoteexec)\s*=\s*true($|\s)',
Takuto Ikuta381db682022-04-27 23:54:02 +0000117 line_without_comment):
118 use_remoteexec = True
119 continue
Michael Savignyf52f44b2022-10-26 14:25:55 +0000120 if re.search(r'(^|\s)(use_rbe)\s*=\s*true($|\s)', line_without_comment):
121 # Set both use_remoteexec and use_rbe when use_rbe arg is found.
122 # The use_remoteexec argument triggers the start/stop of reproxy
123 # and use_rbe triggers the checks against reclient_bootstrap_enabled.
124 use_remoteexec = True
125 use_rbe = True
126 continue
127 if re.search(r'(^|\s)(enable_rbe_bootstrap)\s*=\s*false($|\s)',
128 line_without_comment):
129 reclient_bootstrap_enabled = False
130 continue
Bruce Dawsonebebd952017-05-31 14:24:38 -0700131 else:
Takuto Ikuta381db682022-04-27 23:54:02 +0000132 for relative_path in [
133 '', # GN keeps them in the root of output_dir
134 'CMakeFiles'
135 ]:
136 path = os.path.join(output_dir, relative_path, 'rules.ninja')
137 if os.path.exists(path):
138 with open(path) as file_handle:
139 for line in file_handle:
140 if re.match(r'^\s*command\s*=\s*\S+gomacc', line):
141 use_goma = True
142 break
Bruce Dawsonebebd952017-05-31 14:24:38 -0700143
Michael Savigny1cf1fb52022-08-18 19:57:21 +0000144 # If use_remoteexec is set, but the reclient binaries or configs don't
145 # exist, display an error message and stop. Otherwise, the build will
146 # attempt to run with rewrapper wrapping actions, but will fail with
147 # possible non-obvious problems.
148 # As of August 2022, dev builds with reclient are not supported, so
149 # indicate that use_goma should be swapped for use_remoteexec. This
150 # message will be changed when dev builds are fully supported.
151 if (not offline and use_remoteexec and (
152 not os.path.exists(reclient_bin_dir) or not os.path.exists(reclient_cfg))
153 ):
Michael Savignyf52f44b2022-10-26 14:25:55 +0000154 exit_with_message(
155 "Build is configured to use reclient but necessary binaries "
156 "or config files can't be found. Developer builds with "
157 "reclient are not yet supported. Try regenerating your "
158 "build with use_goma in place of use_remoteexec for now.")
159
160 # Some builds bootstrap reproxy as part of the build graph. This is being
161 # phased out. For now don't let autoninja start a build that has the
162 # enable_rbe_bootstrap gn argument set. Instead, display an error to re-gen
163 # the build with the argument explicitly set to false.
164 #
165 # These builds have the enable_rbe_bootstrap argument set to true by default,
166 # so they don't appear in the args.gn file.
167 if (not offline and use_rbe and reclient_bootstrap_enabled):
168 exit_with_message(
169 "Build is configured to start reproxy as an action in the build. "
170 "Builds with this enabled should not use autoninja to start them. "
171 "Regenerate the build with the enable_rbe_bootstrap gn argument "
172 "explicitly set to false.")
Michael Savigny1cf1fb52022-08-18 19:57:21 +0000173
Takuto Ikuta381db682022-04-27 23:54:02 +0000174 # If GOMA_DISABLED is set to "true", "t", "yes", "y", or "1"
175 # (case-insensitive) then gomacc will use the local compiler instead of doing
176 # a goma compile. This is convenient if you want to briefly disable goma. It
177 # avoids having to rebuild the world when transitioning between goma/non-goma
178 # builds. However, it is not as fast as doing a "normal" non-goma build
179 # because an extra process is created for each compile step. Checking this
180 # environment variable ensures that autoninja uses an appropriate -j value in
181 # this situation.
182 goma_disabled_env = os.environ.get('GOMA_DISABLED', '0').lower()
183 if offline or goma_disabled_env in ['true', 't', 'yes', 'y', '1']:
184 use_goma = False
Yoshisato Yanagisawa43a35d22018-11-15 03:00:51 +0000185
Takuto Ikuta381db682022-04-27 23:54:02 +0000186 if use_goma:
187 gomacc_file = 'gomacc.exe' if sys.platform.startswith('win') else 'gomacc'
188 goma_dir = os.environ.get('GOMA_DIR', os.path.join(SCRIPT_DIR, '.cipd_bin'))
189 gomacc_path = os.path.join(goma_dir, gomacc_file)
190 # Don't invoke gomacc if it doesn't exist.
191 if os.path.exists(gomacc_path):
192 # Check to make sure that goma is running. If not, don't start the build.
193 status = subprocess.call([gomacc_path, 'port'],
194 stdout=subprocess.PIPE,
195 stderr=subprocess.PIPE,
196 shell=False)
197 if status == 1:
Michael Savignyf52f44b2022-10-26 14:25:55 +0000198 exit_with_message(
199 'Goma is not running. Use "goma_ctl ensure_start" to start it.')
Bruce Dawsonb3b46a22019-09-06 15:57:52 +0000200
Takuto Ikuta381db682022-04-27 23:54:02 +0000201 # Specify ninja.exe on Windows so that ninja.bat can call autoninja and not
202 # be called back.
203 ninja_exe = 'ninja.exe' if sys.platform.startswith('win') else 'ninja'
204 ninja_exe_path = os.path.join(SCRIPT_DIR, ninja_exe)
Michael Savigny20eda952021-01-20 01:16:27 +0000205
Takuto Ikuta381db682022-04-27 23:54:02 +0000206 # A large build (with or without goma) tends to hog all system resources.
207 # Launching the ninja process with 'nice' priorities improves this situation.
208 prefix_args = []
209 if (sys.platform.startswith('linux')
210 and os.environ.get('NINJA_BUILD_IN_BACKGROUND', '0') == '1'):
211 # nice -10 is process priority 10 lower than default 0
212 # ionice -c 3 is IO priority IDLE
213 prefix_args = ['nice'] + ['-10']
Michael Savigny20eda952021-01-20 01:16:27 +0000214
Takuto Ikuta381db682022-04-27 23:54:02 +0000215 # Use absolute path for ninja path,
216 # or fail to execute ninja if depot_tools is not in PATH.
217 args = prefix_args + [ninja_exe_path] + input_args[1:]
Michael Savigny20eda952021-01-20 01:16:27 +0000218
Takuto Ikuta381db682022-04-27 23:54:02 +0000219 num_cores = multiprocessing.cpu_count()
220 if not j_specified and not t_specified:
221 if use_goma or use_remoteexec:
222 args.append('-j')
Takuto Ikuta6a1494e2022-05-06 01:22:16 +0000223 default_core_multiplier = 80
224 if platform.machine() in ('x86_64', 'AMD64'):
225 # Assume simultaneous multithreading and therefore half as many cores as
226 # logical processors.
227 num_cores //= 2
228
229 core_multiplier = int(
230 os.environ.get('NINJA_CORE_MULTIPLIER', default_core_multiplier))
Takuto Ikuta381db682022-04-27 23:54:02 +0000231 j_value = num_cores * core_multiplier
232
Aleksey Khoroshilov1bc3cd22022-05-09 19:49:42 +0000233 core_limit = int(os.environ.get('NINJA_CORE_LIMIT', j_value))
234 j_value = min(j_value, core_limit)
235
Takuto Ikuta381db682022-04-27 23:54:02 +0000236 if sys.platform.startswith('win'):
237 # On windows, j value higher than 1000 does not improve build
238 # performance.
239 j_value = min(j_value, 1000)
Sylvain Defresnecb2cef92022-05-10 08:57:20 +0000240 elif sys.platform == 'darwin':
241 # On macOS, j value higher than 800 causes 'Too many open files' error
242 # (crbug.com/936864).
243 j_value = min(j_value, 800)
Takuto Ikuta381db682022-04-27 23:54:02 +0000244
245 args.append('%d' % j_value)
246 else:
247 j_value = num_cores
248 # Ninja defaults to |num_cores + 2|
249 j_value += int(os.environ.get('NINJA_CORE_ADDITION', '2'))
250 args.append('-j')
251 args.append('%d' % j_value)
252
253 # On Windows, fully quote the path so that the command processor doesn't think
254 # the whole output is the command.
255 # On Linux and Mac, if people put depot_tools in directories with ' ',
256 # shell would misunderstand ' ' as a path separation.
257 # TODO(yyanagisawa): provide proper quoting for Windows.
258 # see https://cs.chromium.org/chromium/src/tools/mb/mb.py
259 for i in range(len(args)):
260 if (i == 0 and sys.platform.startswith('win')) or ' ' in args[i]:
261 args[i] = '"%s"' % args[i].replace('"', '\\"')
262
263 if os.environ.get('NINJA_SUMMARIZE_BUILD', '0') == '1':
264 args += ['-d', 'stats']
265
266 # If using remoteexec and the necessary environment variables are set,
267 # also start reproxy (via bootstrap) before running ninja.
268 if (not offline and use_remoteexec and os.path.exists(reclient_bin_dir)
269 and os.path.exists(reclient_cfg)):
270 bootstrap = os.path.join(reclient_bin_dir, 'bootstrap')
271 setup_args = [
272 bootstrap, '--cfg=' + reclient_cfg,
273 '--re_proxy=' + os.path.join(reclient_bin_dir, 'reproxy')
274 ]
275
276 teardown_args = [bootstrap, '--cfg=' + reclient_cfg, '--shutdown']
277
278 cmd_sep = '\n' if sys.platform.startswith('win') else '&&'
279 args = setup_args + [cmd_sep] + args + [cmd_sep] + teardown_args
280
281 if offline and not sys.platform.startswith('win'):
282 # Tell goma or reclient to do local compiles. On Windows these environment
283 # variables are set by the wrapper batch file.
284 return 'RBE_remote_disabled=1 GOMA_DISABLED=1 ' + ' '.join(args)
285
286 return ' '.join(args)
287
288
289if __name__ == '__main__':
290 print(main(sys.argv))