blob: be01f29a78cc40da7c0d09271cf4c25b91082200 [file] [log] [blame]
dprankefe4602312015-04-08 16:20:351#!/usr/bin/env python
2# Copyright 2015 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
Dirk Pranke8cb6aa782017-12-16 02:31:336"""MB - the Meta-Build wrapper around GN.
dprankefe4602312015-04-08 16:20:357
Dirk Pranked181a1a2017-12-14 01:47:118MB is a wrapper script for GN that can be used to generate build files
dprankefe4602312015-04-08 16:20:359for sets of canned configurations and analyze them.
10"""
11
12from __future__ import print_function
13
14import argparse
15import ast
dprankec3441d12015-06-23 23:01:3516import errno
dprankefe4602312015-04-08 16:20:3517import json
18import os
dpranke68d1cb182015-09-17 23:30:0019import pipes
Dirk Pranke8cb6aa782017-12-16 02:31:3320import platform
dpranked8113582015-06-05 20:08:2521import pprint
dpranke3cec199c2015-09-22 23:29:0222import re
dprankefe4602312015-04-08 16:20:3523import shutil
24import sys
25import subprocess
dprankef61de2f2015-05-14 04:09:5626import tempfile
dprankebbe6d4672016-04-19 06:56:5727import traceback
dpranke867bcf4a2016-03-14 22:28:3228import urllib2
Dirk Prankef24e6b22018-03-27 20:12:3029import zipfile
dpranke867bcf4a2016-03-14 22:28:3230
31from collections import OrderedDict
dprankefe4602312015-04-08 16:20:3532
dprankeeca4a782016-04-14 01:42:3833CHROMIUM_SRC_DIR = os.path.dirname(os.path.dirname(os.path.dirname(
34 os.path.abspath(__file__))))
35sys.path = [os.path.join(CHROMIUM_SRC_DIR, 'build')] + sys.path
36
37import gn_helpers
38
39
dprankefe4602312015-04-08 16:20:3540def main(args):
dprankeee5b51f62015-04-09 00:03:2241 mbw = MetaBuildWrapper()
dpranke255085e2016-03-16 05:23:5942 return mbw.Main(args)
dprankefe4602312015-04-08 16:20:3543
44
45class MetaBuildWrapper(object):
46 def __init__(self):
dprankeeca4a782016-04-14 01:42:3847 self.chromium_src_dir = CHROMIUM_SRC_DIR
48 self.default_config = os.path.join(self.chromium_src_dir, 'tools', 'mb',
49 'mb_config.pyl')
kjellander902bcb62016-10-26 06:20:5050 self.default_isolate_map = os.path.join(self.chromium_src_dir, 'testing',
51 'buildbot', 'gn_isolate_map.pyl')
dpranke8c2cfd32015-09-17 20:12:3352 self.executable = sys.executable
dpranked1fba482015-04-14 20:54:5153 self.platform = sys.platform
dpranke8c2cfd32015-09-17 20:12:3354 self.sep = os.sep
dprankefe4602312015-04-08 16:20:3555 self.args = argparse.Namespace()
56 self.configs = {}
dprankefe4602312015-04-08 16:20:3557 self.masters = {}
58 self.mixins = {}
dprankefe4602312015-04-08 16:20:3559
dpranke255085e2016-03-16 05:23:5960 def Main(self, args):
61 self.ParseArgs(args)
62 try:
63 ret = self.args.func()
64 if ret:
65 self.DumpInputFiles()
66 return ret
67 except KeyboardInterrupt:
dprankecb4a2e242016-09-19 01:13:1468 self.Print('interrupted, exiting')
dpranke255085e2016-03-16 05:23:5969 return 130
dprankebbe6d4672016-04-19 06:56:5770 except Exception:
dpranke255085e2016-03-16 05:23:5971 self.DumpInputFiles()
dprankebbe6d4672016-04-19 06:56:5772 s = traceback.format_exc()
73 for l in s.splitlines():
74 self.Print(l)
dpranke255085e2016-03-16 05:23:5975 return 1
76
dprankefe4602312015-04-08 16:20:3577 def ParseArgs(self, argv):
78 def AddCommonOptions(subp):
79 subp.add_argument('-b', '--builder',
80 help='builder name to look up config from')
81 subp.add_argument('-m', '--master',
82 help='master name to look up config from')
83 subp.add_argument('-c', '--config',
84 help='configuration to analyze')
shenghuazhang804b21542016-10-11 02:06:4985 subp.add_argument('--phase',
86 help='optional phase name (used when builders '
87 'do multiple compiles with different '
88 'arguments in a single build)')
dprankefe4602312015-04-08 16:20:3589 subp.add_argument('-f', '--config-file', metavar='PATH',
90 default=self.default_config,
91 help='path to config file '
kjellander902bcb62016-10-26 06:20:5092 '(default is %(default)s)')
93 subp.add_argument('-i', '--isolate-map-file', metavar='PATH',
kjellander902bcb62016-10-26 06:20:5094 help='path to isolate map file '
Zhiling Huang66958462018-02-03 00:28:2095 '(default is %(default)s)',
96 default=[],
97 action='append',
98 dest='isolate_map_files')
dpranked0c138b2016-04-13 18:28:4799 subp.add_argument('-g', '--goma-dir',
100 help='path to goma directory')
agrieve41d21a72016-04-14 18:02:26101 subp.add_argument('--android-version-code',
Dirk Pranked181a1a2017-12-14 01:47:11102 help='Sets GN arg android_default_version_code')
agrieve41d21a72016-04-14 18:02:26103 subp.add_argument('--android-version-name',
Dirk Pranked181a1a2017-12-14 01:47:11104 help='Sets GN arg android_default_version_name')
dprankefe4602312015-04-08 16:20:35105 subp.add_argument('-n', '--dryrun', action='store_true',
106 help='Do a dry run (i.e., do nothing, just print '
107 'the commands that will run)')
dprankee0547cd2015-09-15 01:27:40108 subp.add_argument('-v', '--verbose', action='store_true',
109 help='verbose logging')
dprankefe4602312015-04-08 16:20:35110
111 parser = argparse.ArgumentParser(prog='mb')
112 subps = parser.add_subparsers()
113
114 subp = subps.add_parser('analyze',
115 help='analyze whether changes to a set of files '
116 'will cause a set of binaries to be rebuilt.')
117 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30118 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35119 help='path build was generated into.')
Dirk Prankef24e6b22018-03-27 20:12:30120 subp.add_argument('input_path',
dprankefe4602312015-04-08 16:20:35121 help='path to a file containing the input arguments '
122 'as a JSON object.')
Dirk Prankef24e6b22018-03-27 20:12:30123 subp.add_argument('output_path',
dprankefe4602312015-04-08 16:20:35124 help='path to a file containing the output arguments '
125 'as a JSON object.')
126 subp.set_defaults(func=self.CmdAnalyze)
127
dprankef37aebb92016-09-23 01:14:49128 subp = subps.add_parser('export',
129 help='print out the expanded configuration for'
130 'each builder as a JSON object')
131 subp.add_argument('-f', '--config-file', metavar='PATH',
132 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50133 help='path to config file (default is %(default)s)')
dprankef37aebb92016-09-23 01:14:49134 subp.add_argument('-g', '--goma-dir',
135 help='path to goma directory')
136 subp.set_defaults(func=self.CmdExport)
137
dprankefe4602312015-04-08 16:20:35138 subp = subps.add_parser('gen',
139 help='generate a new set of build files')
140 AddCommonOptions(subp)
dpranke74559b52015-06-10 21:20:39141 subp.add_argument('--swarming-targets-file',
142 help='save runtime dependencies for targets listed '
143 'in file.')
Dirk Prankef24e6b22018-03-27 20:12:30144 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35145 help='path to generate build into')
146 subp.set_defaults(func=self.CmdGen)
147
Erik Chen42df41d2018-08-21 17:13:31148 subp = subps.add_parser('isolate-everything',
149 help='generates a .isolate for all targets. '
150 'Requires that mb.py gen has already been '
151 'run.')
152 AddCommonOptions(subp)
153 subp.set_defaults(func=self.CmdIsolateEverything)
154 subp.add_argument('path',
155 help='path build was generated into')
156
dpranke751516a2015-10-03 01:11:34157 subp = subps.add_parser('isolate',
158 help='generate the .isolate files for a given'
159 'binary')
160 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30161 subp.add_argument('--no-build', dest='build', default=True,
162 action='store_false',
163 help='Do not build, just isolate')
164 subp.add_argument('-j', '--jobs', type=int,
165 help='Number of jobs to pass to ninja')
166 subp.add_argument('path',
dpranke751516a2015-10-03 01:11:34167 help='path build was generated into')
Dirk Prankef24e6b22018-03-27 20:12:30168 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34169 help='ninja target to generate the isolate for')
170 subp.set_defaults(func=self.CmdIsolate)
171
dprankefe4602312015-04-08 16:20:35172 subp = subps.add_parser('lookup',
173 help='look up the command for a given config or '
174 'builder')
175 AddCommonOptions(subp)
Garrett Beatyb6cee042019-04-22 18:42:09176 subp.add_argument('--quiet', default=False, action='store_true',
177 help='Print out just the arguments, '
178 'do not emulate the output of the gen subcommand.')
179 subp.add_argument('--recursive', default=False, action='store_true',
180 help='Lookup arguments from imported files, '
181 'implies --quiet')
dprankefe4602312015-04-08 16:20:35182 subp.set_defaults(func=self.CmdLookup)
183
dpranke030d7a6d2016-03-26 17:23:50184 subp = subps.add_parser(
185 'run',
186 help='build and run the isolated version of a '
187 'binary',
188 formatter_class=argparse.RawDescriptionHelpFormatter)
189 subp.description = (
190 'Build, isolate, and run the given binary with the command line\n'
191 'listed in the isolate. You may pass extra arguments after the\n'
192 'target; use "--" if the extra arguments need to include switches.\n'
193 '\n'
194 'Examples:\n'
195 '\n'
196 ' % tools/mb/mb.py run -m chromium.linux -b "Linux Builder" \\\n'
197 ' //out/Default content_browsertests\n'
198 '\n'
199 ' % tools/mb/mb.py run out/Default content_browsertests\n'
200 '\n'
201 ' % tools/mb/mb.py run out/Default content_browsertests -- \\\n'
202 ' --test-launcher-retry-limit=0'
203 '\n'
204 )
dpranke751516a2015-10-03 01:11:34205 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30206 subp.add_argument('-j', '--jobs', type=int,
dpranke751516a2015-10-03 01:11:34207 help='Number of jobs to pass to ninja')
208 subp.add_argument('--no-build', dest='build', default=True,
209 action='store_false',
210 help='Do not build, just isolate and run')
Dirk Prankef24e6b22018-03-27 20:12:30211 subp.add_argument('path',
dpranke030d7a6d2016-03-26 17:23:50212 help=('path to generate build into (or use).'
213 ' This can be either a regular path or a '
214 'GN-style source-relative path like '
215 '//out/Default.'))
Dirk Pranke8cb6aa782017-12-16 02:31:33216 subp.add_argument('-s', '--swarmed', action='store_true',
217 help='Run under swarming with the default dimensions')
218 subp.add_argument('-d', '--dimension', default=[], action='append', nargs=2,
219 dest='dimensions', metavar='FOO bar',
220 help='dimension to filter on')
221 subp.add_argument('--no-default-dimensions', action='store_false',
222 dest='default_dimensions', default=True,
223 help='Do not automatically add dimensions to the task')
Dirk Prankef24e6b22018-03-27 20:12:30224 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34225 help='ninja target to build and run')
dpranke030d7a6d2016-03-26 17:23:50226 subp.add_argument('extra_args', nargs='*',
227 help=('extra args to pass to the isolate to run. Use '
228 '"--" as the first arg if you need to pass '
229 'switches'))
dpranke751516a2015-10-03 01:11:34230 subp.set_defaults(func=self.CmdRun)
231
dprankefe4602312015-04-08 16:20:35232 subp = subps.add_parser('validate',
233 help='validate the config file')
dprankea5a77ca2015-07-16 23:24:17234 subp.add_argument('-f', '--config-file', metavar='PATH',
235 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50236 help='path to config file (default is %(default)s)')
dprankefe4602312015-04-08 16:20:35237 subp.set_defaults(func=self.CmdValidate)
238
Dirk Prankef24e6b22018-03-27 20:12:30239 subp = subps.add_parser('zip',
240 help='generate a .zip containing the files needed '
241 'for a given binary')
242 AddCommonOptions(subp)
243 subp.add_argument('--no-build', dest='build', default=True,
244 action='store_false',
245 help='Do not build, just isolate')
246 subp.add_argument('-j', '--jobs', type=int,
247 help='Number of jobs to pass to ninja')
248 subp.add_argument('path',
249 help='path build was generated into')
250 subp.add_argument('target',
251 help='ninja target to generate the isolate for')
252 subp.add_argument('zip_path',
253 help='path to zip file to create')
254 subp.set_defaults(func=self.CmdZip)
255
dprankefe4602312015-04-08 16:20:35256 subp = subps.add_parser('help',
257 help='Get help on a subcommand.')
258 subp.add_argument(nargs='?', action='store', dest='subcommand',
259 help='The command to get help for.')
260 subp.set_defaults(func=self.CmdHelp)
261
262 self.args = parser.parse_args(argv)
263
dprankeb2be10a2016-02-22 17:11:00264 def DumpInputFiles(self):
265
dprankef7b7eb7a2016-03-28 22:42:59266 def DumpContentsOfFilePassedTo(arg_name, path):
dprankeb2be10a2016-02-22 17:11:00267 if path and self.Exists(path):
dprankef7b7eb7a2016-03-28 22:42:59268 self.Print("\n# To recreate the file passed to %s:" % arg_name)
dprankecb4a2e242016-09-19 01:13:14269 self.Print("%% cat > %s <<EOF" % path)
dprankeb2be10a2016-02-22 17:11:00270 contents = self.ReadFile(path)
dprankef7b7eb7a2016-03-28 22:42:59271 self.Print(contents)
272 self.Print("EOF\n%\n")
dprankeb2be10a2016-02-22 17:11:00273
dprankef7b7eb7a2016-03-28 22:42:59274 if getattr(self.args, 'input_path', None):
275 DumpContentsOfFilePassedTo(
Dirk Prankef24e6b22018-03-27 20:12:30276 'argv[0] (input_path)', self.args.input_path)
dprankef7b7eb7a2016-03-28 22:42:59277 if getattr(self.args, 'swarming_targets_file', None):
278 DumpContentsOfFilePassedTo(
279 '--swarming-targets-file', self.args.swarming_targets_file)
dprankeb2be10a2016-02-22 17:11:00280
dprankefe4602312015-04-08 16:20:35281 def CmdAnalyze(self):
dpranke751516a2015-10-03 01:11:34282 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11283 return self.RunGNAnalyze(vals)
dprankefe4602312015-04-08 16:20:35284
dprankef37aebb92016-09-23 01:14:49285 def CmdExport(self):
286 self.ReadConfigFile()
287 obj = {}
288 for master, builders in self.masters.items():
289 obj[master] = {}
290 for builder in builders:
291 config = self.masters[master][builder]
292 if not config:
293 continue
294
shenghuazhang804b21542016-10-11 02:06:49295 if isinstance(config, dict):
296 args = {k: self.FlattenConfig(v)['gn_args']
297 for k, v in config.items()}
dprankef37aebb92016-09-23 01:14:49298 elif config.startswith('//'):
299 args = config
300 else:
301 args = self.FlattenConfig(config)['gn_args']
302 if 'error' in args:
303 continue
304
305 obj[master][builder] = args
306
307 # Dump object and trim trailing whitespace.
308 s = '\n'.join(l.rstrip() for l in
309 json.dumps(obj, sort_keys=True, indent=2).splitlines())
310 self.Print(s)
311 return 0
312
dprankefe4602312015-04-08 16:20:35313 def CmdGen(self):
dpranke751516a2015-10-03 01:11:34314 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11315 return self.RunGNGen(vals)
dprankefe4602312015-04-08 16:20:35316
Erik Chen42df41d2018-08-21 17:13:31317 def CmdIsolateEverything(self):
318 vals = self.Lookup()
319 return self.RunGNGenAllIsolates(vals)
320
dprankefe4602312015-04-08 16:20:35321 def CmdHelp(self):
322 if self.args.subcommand:
323 self.ParseArgs([self.args.subcommand, '--help'])
324 else:
325 self.ParseArgs(['--help'])
326
dpranke751516a2015-10-03 01:11:34327 def CmdIsolate(self):
328 vals = self.GetConfig()
329 if not vals:
330 return 1
Dirk Prankef24e6b22018-03-27 20:12:30331 if self.args.build:
332 ret = self.Build(self.args.target)
333 if ret:
334 return ret
Dirk Pranked181a1a2017-12-14 01:47:11335 return self.RunGNIsolate(vals)
dpranke751516a2015-10-03 01:11:34336
337 def CmdLookup(self):
338 vals = self.Lookup()
Garrett Beatyb6cee042019-04-22 18:42:09339 gn_args = self.GNArgs(vals, expand_imports=self.args.recursive)
340 if self.args.quiet or self.args.recursive:
341 self.Print(gn_args, end='')
342 else:
343 cmd = self.GNCmd('gen', '_path_')
344 self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args)
345 env = None
dpranke751516a2015-10-03 01:11:34346
Garrett Beatyb6cee042019-04-22 18:42:09347 self.PrintCmd(cmd, env)
dpranke751516a2015-10-03 01:11:34348 return 0
349
350 def CmdRun(self):
351 vals = self.GetConfig()
352 if not vals:
353 return 1
Dirk Pranked181a1a2017-12-14 01:47:11354 if self.args.build:
Dirk Pranke5f22a822019-05-23 22:55:25355 self.Print('')
Dirk Prankef24e6b22018-03-27 20:12:30356 ret = self.Build(self.args.target)
dpranke751516a2015-10-03 01:11:34357 if ret:
358 return ret
Dirk Pranke5f22a822019-05-23 22:55:25359
360 self.Print('')
Dirk Pranked181a1a2017-12-14 01:47:11361 ret = self.RunGNIsolate(vals)
362 if ret:
363 return ret
dpranke751516a2015-10-03 01:11:34364
Dirk Pranke5f22a822019-05-23 22:55:25365 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33366 if self.args.swarmed:
Dirk Prankef24e6b22018-03-27 20:12:30367 return self._RunUnderSwarming(self.args.path, self.args.target)
Dirk Pranke8cb6aa782017-12-16 02:31:33368 else:
Dirk Prankef24e6b22018-03-27 20:12:30369 return self._RunLocallyIsolated(self.args.path, self.args.target)
370
371 def CmdZip(self):
372 ret = self.CmdIsolate()
373 if ret:
374 return ret
375
376 zip_dir = None
377 try:
378 zip_dir = self.TempDir()
379 remap_cmd = [
380 self.executable,
381 self.PathJoin(self.chromium_src_dir, 'tools', 'swarming_client',
382 'isolate.py'),
383 'remap',
Kenneth Russell2e75e2f2018-11-15 22:37:28384 '--collapse_symlinks',
Dirk Prankef24e6b22018-03-27 20:12:30385 '-s', self.PathJoin(self.args.path, self.args.target + '.isolated'),
386 '-o', zip_dir
387 ]
388 self.Run(remap_cmd)
389
390 zip_path = self.args.zip_path
391 with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as fp:
392 for root, _, files in os.walk(zip_dir):
393 for filename in files:
394 path = self.PathJoin(root, filename)
395 fp.write(path, self.RelPath(path, zip_dir))
396 finally:
397 if zip_dir:
398 self.RemoveDirectory(zip_dir)
Dirk Pranke8cb6aa782017-12-16 02:31:33399
Robert Iannucci5a9d75f62018-03-02 05:28:20400 @staticmethod
401 def _AddBaseSoftware(cmd):
402 # HACK(iannucci): These packages SHOULD NOT BE HERE.
403 # Remove method once Swarming Pool Task Templates are implemented.
404 # crbug.com/812428
405
406 # Add in required base software. This should be kept in sync with the
John Budorick9d9175372019-04-01 19:04:24407 # `chromium_swarming` recipe module in build.git. All references to
408 # `swarming_module` below are purely due to this.
Robert Iannucci5a9d75f62018-03-02 05:28:20409 cipd_packages = [
410 ('infra/python/cpython/${platform}',
411 'version:2.7.14.chromium14'),
412 ('infra/tools/luci/logdog/butler/${platform}',
413 'git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c'),
414 ('infra/tools/luci/vpython-native/${platform}',
Marc-Antoine Ruele2b07a32019-04-15 17:52:09415 'git_revision:cc09450f1c27c0034ec08b1f6d63bbc298294763'),
Robert Iannucci5a9d75f62018-03-02 05:28:20416 ('infra/tools/luci/vpython/${platform}',
Marc-Antoine Ruele2b07a32019-04-15 17:52:09417 'git_revision:cc09450f1c27c0034ec08b1f6d63bbc298294763'),
Robert Iannucci5a9d75f62018-03-02 05:28:20418 ]
419 for pkg, vers in cipd_packages:
420 cmd.append('--cipd-package=.swarming_module:%s:%s' % (pkg, vers))
421
422 # Add packages to $PATH
423 cmd.extend([
424 '--env-prefix=PATH', '.swarming_module',
425 '--env-prefix=PATH', '.swarming_module/bin',
426 ])
427
428 # Add cache directives for vpython.
429 vpython_cache_path = '.swarming_module_cache/vpython'
430 cmd.extend([
431 '--named-cache=swarming_module_cache_vpython', vpython_cache_path,
432 '--env-prefix=VPYTHON_VIRTUALENV_ROOT', vpython_cache_path,
433 ])
434
Dirk Pranke8cb6aa782017-12-16 02:31:33435 def _RunUnderSwarming(self, build_dir, target):
Marc-Antoine Ruel559cc4732019-03-19 22:20:46436 isolate_server = 'isolateserver.appspot.com'
437 namespace = 'default-gzip'
438 swarming_server = 'chromium-swarm.appspot.com'
Dirk Pranke8cb6aa782017-12-16 02:31:33439 # TODO(dpranke): Look up the information for the target in
440 # the //testing/buildbot.json file, if possible, so that we
441 # can determine the isolate target, command line, and additional
442 # swarming parameters, if possible.
443 #
444 # TODO(dpranke): Also, add support for sharding and merging results.
445 dimensions = []
446 for k, v in self._DefaultDimensions() + self.args.dimensions:
447 dimensions += ['-d', k, v]
448
449 cmd = [
450 self.executable,
451 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
452 'archive',
Marc-Antoine Ruel559cc4732019-03-19 22:20:46453 '-s', self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
454 '-I', isolate_server,
455 '--namespace', namespace,
Dirk Pranke8cb6aa782017-12-16 02:31:33456 ]
Dirk Pranke5f22a822019-05-23 22:55:25457
458 # Talking to the isolateserver may fail because we're not logged in.
459 # We trap the command explicitly and rewrite the error output so that
460 # the error message is actually correct for a Chromium check out.
461 self.PrintCmd(cmd, env=None)
462 ret, out, err = self.Run(cmd, force_verbose=False)
Dirk Pranke8cb6aa782017-12-16 02:31:33463 if ret:
Dirk Pranke5f22a822019-05-23 22:55:25464 self.Print(' -> returned %d' % ret)
465 if out:
466 self.Print(out, end='')
467 if err:
468 # The swarming client will return an exit code of 2 (via
469 # argparse.ArgumentParser.error()) and print a message to indicate
470 # that auth failed, so we have to parse the message to check.
471 if (ret == 2 and 'Please login to' in err):
472 err = err.replace(' auth.py', ' tools/swarming_client/auth.py')
473 self.Print(err, end='', file=sys.stderr)
474
Dirk Pranke8cb6aa782017-12-16 02:31:33475 return ret
476
477 isolated_hash = out.splitlines()[0].split()[0]
478 cmd = [
479 self.executable,
480 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
481 'run',
482 '-s', isolated_hash,
Marc-Antoine Ruel559cc4732019-03-19 22:20:46483 '-I', isolate_server,
484 '--namespace', namespace,
485 '-S', swarming_server,
Dirk Pranke8cb6aa782017-12-16 02:31:33486 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20487 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33488 if self.args.extra_args:
489 cmd += ['--'] + self.args.extra_args
Dirk Pranke5f22a822019-05-23 22:55:25490 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33491 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
492 return ret
493
494 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50495 cmd = [
dpranke751516a2015-10-03 01:11:34496 self.executable,
497 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
498 'run',
499 '-s',
dpranke030d7a6d2016-03-26 17:23:50500 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33501 ]
dpranke030d7a6d2016-03-26 17:23:50502 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33503 cmd += ['--'] + self.args.extra_args
504 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34505 return ret
506
Dirk Pranke8cb6aa782017-12-16 02:31:33507 def _DefaultDimensions(self):
508 if not self.args.default_dimensions:
509 return []
510
511 # This code is naive and just picks reasonable defaults per platform.
512 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40513 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33514 elif self.platform.startswith('linux'):
515 os_dim = ('os', 'Ubuntu-14.04')
516 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40517 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33518 else:
519 raise MBErr('unrecognized platform string "%s"' % self.platform)
520
521 return [('pool', 'Chrome'),
522 ('cpu', 'x86-64'),
523 os_dim]
524
dpranke0cafc162016-03-19 00:41:10525 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35526 errs = []
527
528 # Read the file to make sure it parses.
529 self.ReadConfigFile()
530
dpranke3be00142016-03-17 22:46:04531 # Build a list of all of the configs referenced by builders.
dprankefe4602312015-04-08 16:20:35532 all_configs = {}
dprankefe4602312015-04-08 16:20:35533 for master in self.masters:
dpranke3be00142016-03-17 22:46:04534 for config in self.masters[master].values():
shenghuazhang804b21542016-10-11 02:06:49535 if isinstance(config, dict):
536 for c in config.values():
dprankeb9380a12016-07-21 21:44:09537 all_configs[c] = master
538 else:
539 all_configs[config] = master
dprankefe4602312015-04-08 16:20:35540
dpranke9dd5e252016-04-14 04:23:09541 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35542 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09543 if config.startswith('//'):
544 if not self.Exists(self.ToAbsPath(config)):
545 errs.append('Unknown args file "%s" referenced from "%s".' %
546 (config, loc))
547 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35548 errs.append('Unknown config "%s" referenced from "%s".' %
549 (config, loc))
550
551 # Check that every actual config is actually referenced.
552 for config in self.configs:
553 if not config in all_configs:
554 errs.append('Unused config "%s".' % config)
555
556 # Figure out the whole list of mixins, and check that every mixin
557 # listed by a config or another mixin actually exists.
558 referenced_mixins = set()
559 for config, mixins in self.configs.items():
560 for mixin in mixins:
561 if not mixin in self.mixins:
562 errs.append('Unknown mixin "%s" referenced by config "%s".' %
563 (mixin, config))
564 referenced_mixins.add(mixin)
565
566 for mixin in self.mixins:
567 for sub_mixin in self.mixins[mixin].get('mixins', []):
568 if not sub_mixin in self.mixins:
569 errs.append('Unknown mixin "%s" referenced by mixin "%s".' %
570 (sub_mixin, mixin))
571 referenced_mixins.add(sub_mixin)
572
573 # Check that every mixin defined is actually referenced somewhere.
574 for mixin in self.mixins:
575 if not mixin in referenced_mixins:
576 errs.append('Unreferenced mixin "%s".' % mixin)
577
dpranke255085e2016-03-16 05:23:59578 # If we're checking the Chromium config, check that the 'chromium' bots
579 # which build public artifacts do not include the chrome_with_codecs mixin.
580 if self.args.config_file == self.default_config:
581 if 'chromium' in self.masters:
582 for builder in self.masters['chromium']:
583 config = self.masters['chromium'][builder]
584 def RecurseMixins(current_mixin):
585 if current_mixin == 'chrome_with_codecs':
586 errs.append('Public artifact builder "%s" can not contain the '
587 '"chrome_with_codecs" mixin.' % builder)
588 return
589 if not 'mixins' in self.mixins[current_mixin]:
590 return
591 for mixin in self.mixins[current_mixin]['mixins']:
592 RecurseMixins(mixin)
dalecurtis56fd27e2016-03-09 23:06:41593
dpranke255085e2016-03-16 05:23:59594 for mixin in self.configs[config]:
595 RecurseMixins(mixin)
596 else:
597 errs.append('Missing "chromium" master. Please update this '
598 'proprietary codecs check with the name of the master '
599 'responsible for public build artifacts.')
dalecurtis56fd27e2016-03-09 23:06:41600
dprankefe4602312015-04-08 16:20:35601 if errs:
dpranke4323c80632015-08-10 22:53:54602 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17603 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35604
dpranke0cafc162016-03-19 00:41:10605 if print_ok:
606 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35607 return 0
608
609 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30610 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34611
dprankef37aebb92016-09-23 01:14:49612 vals = self.DefaultVals()
dpranke751516a2015-10-03 01:11:34613 if self.args.builder or self.args.master or self.args.config:
614 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11615 # Re-run gn gen in order to ensure the config is consistent with the
616 # build dir.
617 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34618 return vals
619
Dirk Pranked181a1a2017-12-14 01:47:11620 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
621 'toolchain.ninja')
622 if not self.Exists(toolchain_path):
623 self.Print('Must either specify a path to an existing GN build dir '
624 'or pass in a -m/-b pair or a -c flag to specify the '
625 'configuration')
626 return {}
dpranke751516a2015-10-03 01:11:34627
Dirk Pranked181a1a2017-12-14 01:47:11628 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34629 return vals
630
dprankef37aebb92016-09-23 01:14:49631 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58632 args_contents = ""
633 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
634 if self.Exists(gn_args_path):
635 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34636 gn_args = []
637 for l in args_contents.splitlines():
638 fields = l.split(' ')
639 name = fields[0]
640 val = ' '.join(fields[2:])
641 gn_args.append('%s=%s' % (name, val))
642
dprankef37aebb92016-09-23 01:14:49643 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34644
645 def Lookup(self):
Erik Chen238f4ac2019-04-12 19:02:50646 vals = self.ReadIOSBotConfig()
647 if not vals:
648 self.ReadConfigFile()
649 config = self.ConfigFromArgs()
650 if config.startswith('//'):
651 if not self.Exists(self.ToAbsPath(config)):
652 raise MBErr('args file "%s" not found' % config)
653 vals = self.DefaultVals()
654 vals['args_file'] = config
655 else:
656 if not config in self.configs:
657 raise MBErr('Config "%s" not found in %s' %
658 (config, self.args.config_file))
659 vals = self.FlattenConfig(config)
660 return vals
661
662 def ReadIOSBotConfig(self):
663 if not self.args.master or not self.args.builder:
664 return {}
665 path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
666 self.args.master, self.args.builder + '.json')
667 if not self.Exists(path):
668 return {}
669
670 contents = json.loads(self.ReadFile(path))
671 gn_args = ' '.join(contents.get('gn_args', []))
672
673 vals = self.DefaultVals()
674 vals['gn_args'] = gn_args
dprankef37aebb92016-09-23 01:14:49675 return vals
dprankee0f486f2015-11-19 23:42:00676
dprankefe4602312015-04-08 16:20:35677 def ReadConfigFile(self):
678 if not self.Exists(self.args.config_file):
679 raise MBErr('config file not found at %s' % self.args.config_file)
680
681 try:
682 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
683 except SyntaxError as e:
684 raise MBErr('Failed to parse config file "%s": %s' %
685 (self.args.config_file, e))
686
dprankefe4602312015-04-08 16:20:35687 self.configs = contents['configs']
dprankefe4602312015-04-08 16:20:35688 self.masters = contents['masters']
689 self.mixins = contents['mixins']
dprankefe4602312015-04-08 16:20:35690
dprankecb4a2e242016-09-19 01:13:14691 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20692 if not self.args.isolate_map_files:
693 self.args.isolate_map_files = [self.default_isolate_map]
694
695 for f in self.args.isolate_map_files:
696 if not self.Exists(f):
697 raise MBErr('isolate map file not found at %s' % f)
698 isolate_maps = {}
699 for isolate_map in self.args.isolate_map_files:
700 try:
701 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
702 duplicates = set(isolate_map).intersection(isolate_maps)
703 if duplicates:
704 raise MBErr(
705 'Duplicate targets in isolate map files: %s.' %
706 ', '.join(duplicates))
707 isolate_maps.update(isolate_map)
708 except SyntaxError as e:
709 raise MBErr(
710 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
711 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14712
dprankefe4602312015-04-08 16:20:35713 def ConfigFromArgs(self):
714 if self.args.config:
715 if self.args.master or self.args.builder:
716 raise MBErr('Can not specific both -c/--config and -m/--master or '
717 '-b/--builder')
718
719 return self.args.config
720
721 if not self.args.master or not self.args.builder:
722 raise MBErr('Must specify either -c/--config or '
723 '(-m/--master and -b/--builder)')
724
725 if not self.args.master in self.masters:
726 raise MBErr('Master name "%s" not found in "%s"' %
727 (self.args.master, self.args.config_file))
728
729 if not self.args.builder in self.masters[self.args.master]:
730 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
731 (self.args.builder, self.args.master, self.args.config_file))
732
dprankeb9380a12016-07-21 21:44:09733 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49734 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09735 if self.args.phase is None:
736 raise MBErr('Must specify a build --phase for %s on %s' %
737 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49738 phase = str(self.args.phase)
739 if phase not in config:
740 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09741 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49742 return config[phase]
dprankeb9380a12016-07-21 21:44:09743
744 if self.args.phase is not None:
745 raise MBErr('Must not specify a build --phase for %s on %s' %
746 (self.args.builder, self.args.master))
747 return config
dprankefe4602312015-04-08 16:20:35748
749 def FlattenConfig(self, config):
750 mixins = self.configs[config]
dprankef37aebb92016-09-23 01:14:49751 vals = self.DefaultVals()
dprankefe4602312015-04-08 16:20:35752
753 visited = []
754 self.FlattenMixins(mixins, vals, visited)
755 return vals
756
dprankef37aebb92016-09-23 01:14:49757 def DefaultVals(self):
758 return {
759 'args_file': '',
760 'cros_passthrough': False,
761 'gn_args': '',
dprankef37aebb92016-09-23 01:14:49762 }
763
dprankefe4602312015-04-08 16:20:35764 def FlattenMixins(self, mixins, vals, visited):
765 for m in mixins:
766 if m not in self.mixins:
767 raise MBErr('Unknown mixin "%s"' % m)
dprankeee5b51f62015-04-09 00:03:22768
dprankefe4602312015-04-08 16:20:35769 visited.append(m)
770
771 mixin_vals = self.mixins[m]
dpranke73ed0d62016-04-25 19:18:34772
773 if 'cros_passthrough' in mixin_vals:
774 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
Dirk Pranke6b99f072017-04-05 00:58:30775 if 'args_file' in mixin_vals:
776 if vals['args_file']:
777 raise MBErr('args_file specified multiple times in mixins '
778 'for %s on %s' % (self.args.builder, self.args.master))
779 vals['args_file'] = mixin_vals['args_file']
dprankefe4602312015-04-08 16:20:35780 if 'gn_args' in mixin_vals:
781 if vals['gn_args']:
782 vals['gn_args'] += ' ' + mixin_vals['gn_args']
783 else:
784 vals['gn_args'] = mixin_vals['gn_args']
dpranke73ed0d62016-04-25 19:18:34785
dprankefe4602312015-04-08 16:20:35786 if 'mixins' in mixin_vals:
787 self.FlattenMixins(mixin_vals['mixins'], vals, visited)
788 return vals
789
Takuto Ikuta9dffd7e2018-09-05 01:04:00790 def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
Dirk Prankef24e6b22018-03-27 20:12:30791 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50792
Takuto Ikuta9dffd7e2018-09-05 01:04:00793 if check:
794 cmd = self.GNCmd('gen', build_dir, '--check')
795 else:
796 cmd = self.GNCmd('gen', build_dir)
dprankeeca4a782016-04-14 01:42:38797 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:09798 if compute_inputs_for_analyze:
799 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:38800
801 # Since GN hasn't run yet, the build directory may not even exist.
802 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
803
804 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:54805 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:39806
dpranke751516a2015-10-03 01:11:34807 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:39808 # We need GN to generate the list of runtime dependencies for
809 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:14810 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:39811 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:00812 path = self.args.swarming_targets_file
813 if not self.Exists(path):
814 self.WriteFailureAndRaise('"%s" does not exist' % path,
815 output_path=None)
816 contents = self.ReadFile(path)
Erik Chen42df41d2018-08-21 17:13:31817 isolate_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:00818
dprankecb4a2e242016-09-19 01:13:14819 isolate_map = self.ReadIsolateMap()
Dirk Pranke7a7e9b62019-02-17 01:46:25820 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
821 isolate_map, build_dir)
822
Erik Chen42df41d2018-08-21 17:13:31823 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:00824 if err:
Dirk Pranke7a7e9b62019-02-17 01:46:25825 raise MBErr(err)
dpranke74559b52015-06-10 21:20:39826
dpranke751516a2015-10-03 01:11:34827 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:14828 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:39829 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
830
dprankefe4602312015-04-08 16:20:35831 ret, _, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:40832 if ret:
Dirk Pranke7a7e9b62019-02-17 01:46:25833 # If `gn gen` failed, we should exit early rather than trying to
834 # generate isolates. Run() will have already logged any error output.
835 self.Print('GN gen failed: %d' % ret)
836 return ret
dpranke74559b52015-06-10 21:20:39837
Erik Chen42df41d2018-08-21 17:13:31838 if getattr(self.args, 'swarming_targets_file', None):
Dirk Pranke7a7e9b62019-02-17 01:46:25839 self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
Erik Chen42df41d2018-08-21 17:13:31840
841 return 0
842
843 def RunGNGenAllIsolates(self, vals):
844 """
845 This command generates all .isolate files.
846
847 This command assumes that "mb.py gen" has already been run, as it relies on
848 "gn ls" to fetch all gn targets. If uses that output, combined with the
849 isolate_map, to determine all isolates that can be generated for the current
850 gn configuration.
851 """
852 build_dir = self.args.path
853 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
854 force_verbose=False)
855 if ret:
856 # If `gn ls` failed, we should exit early rather than trying to
857 # generate isolates.
858 self.Print('GN ls failed: %d' % ret)
859 return ret
860
861 # Create a reverse map from isolate label to isolate dict.
862 isolate_map = self.ReadIsolateMap()
863 isolate_dict_map = {}
864 for key, isolate_dict in isolate_map.iteritems():
865 isolate_dict_map[isolate_dict['label']] = isolate_dict
866 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
867
868 runtime_deps = []
869
870 isolate_targets = []
871 # For every GN target, look up the isolate dict.
872 for line in output.splitlines():
873 target = line.strip()
874 if target in isolate_dict_map:
875 if isolate_dict_map[target]['type'] == 'additional_compile_target':
876 # By definition, additional_compile_targets are not tests, so we
877 # shouldn't generate isolates for them.
878 continue
879
880 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
881 runtime_deps.append(target)
882
Dirk Pranke7a7e9b62019-02-17 01:46:25883 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
884 isolate_map, build_dir)
885
Erik Chen42df41d2018-08-21 17:13:31886 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
887 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
888 cmd = self.GNCmd('gen', build_dir)
889 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
890 self.Run(cmd)
891
892 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
893
Dirk Pranke7a7e9b62019-02-17 01:46:25894 def RemovePossiblyStaleRuntimeDepsFiles(self, vals, targets, isolate_map,
895 build_dir):
896 # TODO(crbug.com/932700): Because `gn gen --runtime-deps-list-file`
897 # puts the runtime_deps file in different locations based on the actual
898 # type of a target, we may end up with multiple possible runtime_deps
899 # files in a given build directory, where some of the entries might be
900 # stale (since we might be reusing an existing build directory).
901 #
902 # We need to be able to get the right one reliably; you might think
903 # we can just pick the newest file, but because GN won't update timestamps
904 # if the contents of the files change, an older runtime_deps
905 # file might actually be the one we should use over a newer one (see
906 # crbug.com/932387 for a more complete explanation and example).
907 #
908 # In order to avoid this, we need to delete any possible runtime_deps
909 # files *prior* to running GN. As long as the files aren't actually
910 # needed during the build, this hopefully will not cause unnecessary
911 # build work, and so it should be safe.
912 #
913 # Ultimately, we should just make sure we get the runtime_deps files
914 # in predictable locations so we don't have this issue at all, and
915 # that's what crbug.com/932700 is for.
916 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, targets, isolate_map)
917 for rpaths in possible_rpaths.values():
918 for rpath in rpaths:
919 path = self.ToAbsPath(build_dir, rpath)
920 if self.Exists(path):
921 self.RemoveFile(path)
922
Erik Chen42df41d2018-08-21 17:13:31923 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
924 """
925 Generates isolates for a list of ninja targets.
926
927 Ninja targets are transformed to GN targets via isolate_map.
928
929 This function assumes that a previous invocation of "mb.py gen" has
930 generated runtime deps for all targets.
931 """
Dirk Pranke7a7e9b62019-02-17 01:46:25932 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, ninja_targets,
933 isolate_map)
934
935 for target, rpaths in possible_rpaths.items():
936 # TODO(crbug.com/932700): We don't know where each .runtime_deps
937 # file might be, but assuming we called
938 # RemovePossiblyStaleRuntimeDepsFiles prior to calling `gn gen`,
939 # there should only be one file.
940 found_one = False
941 path_to_use = None
942 for r in rpaths:
943 path = self.ToAbsPath(build_dir, r)
944 if self.Exists(path):
945 if found_one:
946 raise MBErr('Found more than one of %s' % ', '.join(rpaths))
947 path_to_use = path
948 found_one = True
949
950 if not found_one:
951 raise MBErr('Did not find any of %s' % ', '.join(rpaths))
952
953 command, extra_files = self.GetIsolateCommand(target, vals)
954 runtime_deps = self.ReadFile(path_to_use).splitlines()
955
956 canonical_target = target.replace(':','_').replace('/','_')
957 self.WriteIsolateFiles(build_dir, command, canonical_target, runtime_deps,
958 extra_files)
959
960 def PossibleRuntimeDepsPaths(self, vals, ninja_targets, isolate_map):
961 """Returns a map of targets to possible .runtime_deps paths.
962
963 Each ninja target maps on to a GN label, but depending on the type
964 of the GN target, `gn gen --runtime-deps-list-file` will write
965 the .runtime_deps files into different locations. Unfortunately, in
966 some cases we don't actually know which of multiple locations will
967 actually be used, so we return all plausible candidates.
968
969 The paths that are returned are relative to the build directory.
970 """
971
jbudoricke3c4f95e2016-04-28 23:17:38972 android = 'target_os="android"' in vals['gn_args']
Dirk Pranke26de05aec2019-04-03 19:18:38973 ios = 'target_os="ios"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:42974 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:30975 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Dirk Pranke7a7e9b62019-02-17 01:46:25976 possible_runtime_deps_rpaths = {}
Erik Chen42df41d2018-08-21 17:13:31977 for target in ninja_targets:
John Budorick39f14962019-04-11 23:03:20978 target_type = isolate_map[target]['type']
979 label = isolate_map[target]['label']
980 stamp_runtime_deps = 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')
Erik Chen42df41d2018-08-21 17:13:31981 # TODO(https://crbug.com/876065): 'official_tests' use
982 # type='additional_compile_target' to isolate tests. This is not the
983 # intended use for 'additional_compile_target'.
John Budorick39f14962019-04-11 23:03:20984 if (target_type == 'additional_compile_target' and
Erik Chen42df41d2018-08-21 17:13:31985 target != 'official_tests'):
986 # By definition, additional_compile_targets are not tests, so we
987 # shouldn't generate isolates for them.
Dirk Pranke7a7e9b62019-02-17 01:46:25988 raise MBErr('Cannot generate isolate for %s since it is an '
989 'additional_compile_target.' % target)
John Budorick39f14962019-04-11 23:03:20990 elif fuchsia or ios or target_type == 'generated_script':
991 # iOS and Fuchsia targets end up as groups.
992 # generated_script targets are always actions.
993 rpaths = [stamp_runtime_deps]
Erik Chen42df41d2018-08-21 17:13:31994 elif android:
jbudoricke3c4f95e2016-04-28 23:17:38995 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:02996 # will result in runtime_deps associated with the stamp file, while the
997 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:44998 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:25999 rpaths = [
dprankecb4a2e242016-09-19 01:13:141000 target + '.runtime_deps',
John Budorick39f14962019-04-11 23:03:201001 stamp_runtime_deps]
1002 elif (target_type == 'script' or
1003 target_type == 'fuzzer' or
dprankecb4a2e242016-09-19 01:13:141004 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:111005 # For script targets, the build target is usually a group,
1006 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:491007 # for the label, which lives under the obj/ directory, but it may
1008 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:441009 label = isolate_map[target]['label']
John Budorick39f14962019-04-11 23:03:201010 rpaths = [stamp_runtime_deps]
Nico Weberd94b71a2018-02-22 22:00:301011 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:251012 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:491013 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251014 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:301015 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:251016 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:521017 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251018 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:021019
Dirk Pranke7a7e9b62019-02-17 01:46:251020 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:411021
Dirk Pranke7a7e9b62019-02-17 01:46:251022 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:341023
1024 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:301025 target = self.args.target
dprankecb4a2e242016-09-19 01:13:141026 isolate_map = self.ReadIsolateMap()
1027 err, labels = self.MapTargetsToLabels(isolate_map, [target])
1028 if err:
1029 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:251030
dprankecb4a2e242016-09-19 01:13:141031 label = labels[0]
dpranke751516a2015-10-03 01:11:341032
Dirk Prankef24e6b22018-03-27 20:12:301033 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:141034 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:341035
dprankeeca4a782016-04-14 01:42:381036 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:201037 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:341038 if ret:
dpranke030d7a6d2016-03-26 17:23:501039 if out:
1040 self.Print(out)
dpranke751516a2015-10-03 01:11:341041 return ret
1042
1043 runtime_deps = out.splitlines()
1044
1045 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
1046 extra_files)
1047
1048 ret, _, _ = self.Run([
1049 self.executable,
1050 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1051 'check',
1052 '-i',
1053 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1054 '-s',
1055 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1056 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301057
dprankefe4602312015-04-08 16:20:351058 return ret
1059
dpranke751516a2015-10-03 01:11:341060 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps,
1061 extra_files):
1062 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
1063 self.WriteFile(isolate_path,
1064 pprint.pformat({
1065 'variables': {
1066 'command': command,
Dirk Pranke8edeb682019-06-11 16:24:051067 'files': sorted(set(runtime_deps + extra_files)),
dpranke751516a2015-10-03 01:11:341068 }
1069 }) + '\n')
1070
1071 self.WriteJSON(
1072 {
1073 'args': [
1074 '--isolated',
1075 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1076 '--isolate',
1077 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1078 ],
1079 'dir': self.chromium_src_dir,
1080 'version': 1,
1081 },
1082 isolate_path + 'd.gen.json',
1083 )
1084
dprankecb4a2e242016-09-19 01:13:141085 def MapTargetsToLabels(self, isolate_map, targets):
1086 labels = []
1087 err = ''
1088
dprankecb4a2e242016-09-19 01:13:141089 for target in targets:
1090 if target == 'all':
1091 labels.append(target)
1092 elif target.startswith('//'):
1093 labels.append(target)
1094 else:
1095 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421096 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141097 err += ('test target "%s" type is unknown\n' % target)
1098 else:
thakis024d6f32017-05-16 23:21:421099 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141100 else:
1101 err += ('target "%s" not found in '
1102 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1103
1104 return err, labels
1105
dprankeeca4a782016-04-14 01:42:381106 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461107 if self.platform == 'linux2':
1108 subdir, exe = 'linux64', 'gn'
1109 elif self.platform == 'darwin':
1110 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251111 elif self.platform == 'aix6':
1112 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461113 else:
1114 subdir, exe = 'win', 'gn.exe'
1115
1116 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081117 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521118
dprankecb4a2e242016-09-19 01:13:141119
Garrett Beatyb6cee042019-04-22 18:42:091120 def GNArgs(self, vals, expand_imports=False):
dpranke73ed0d62016-04-25 19:18:341121 if vals['cros_passthrough']:
1122 if not 'GN_ARGS' in os.environ:
1123 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1124 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161125 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301126 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341127 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351128 if vals['gn_args']:
1129 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341130 else:
1131 gn_args = vals['gn_args']
1132
dpranked0c138b2016-04-13 18:28:471133 if self.args.goma_dir:
1134 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381135
agrieve41d21a72016-04-14 18:02:261136 android_version_code = self.args.android_version_code
1137 if android_version_code:
1138 gn_args += ' android_default_version_code="%s"' % android_version_code
1139
1140 android_version_name = self.args.android_version_name
1141 if android_version_name:
1142 gn_args += ' android_default_version_name="%s"' % android_version_name
1143
Garrett Beatyb6cee042019-04-22 18:42:091144 args_gn_lines = []
1145 parsed_gn_args = {}
dprankeeca4a782016-04-14 01:42:381146
Ben Pastene65ccf6132018-11-08 00:47:591147 # If we're using the Simple Chrome SDK, add a comment at the top that
1148 # points to the doc. This must happen after the gn_helpers.ToGNString()
1149 # call above since gn_helpers strips comments.
1150 if vals['cros_passthrough']:
Garrett Beatyb6cee042019-04-22 18:42:091151 args_gn_lines.extend([
Ben Pastene65ccf6132018-11-08 00:47:591152 '# These args are generated via the Simple Chrome SDK. See the link',
1153 '# below for more details:',
1154 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
Garrett Beatyb6cee042019-04-22 18:42:091155 ])
Ben Pastene65ccf6132018-11-08 00:47:591156
dpranke9dd5e252016-04-14 04:23:091157 args_file = vals.get('args_file', None)
1158 if args_file:
Garrett Beatyb6cee042019-04-22 18:42:091159 if expand_imports:
1160 content = self.ReadFile(self.ToAbsPath(args_file))
1161 parsed_gn_args = gn_helpers.FromGNArgs(content)
1162 else:
1163 args_gn_lines.append('import("%s")' % args_file)
1164
1165 # Canonicalize the arg string into a sorted, newline-separated list
1166 # of key-value pairs, and de-dup the keys if need be so that only
1167 # the last instance of each arg is listed.
1168 parsed_gn_args.update(gn_helpers.FromGNArgs(gn_args))
1169 args_gn_lines.append(gn_helpers.ToGNString(parsed_gn_args))
1170
1171 return '\n'.join(args_gn_lines)
dprankefe4602312015-04-08 16:20:351172
dprankecb4a2e242016-09-19 01:13:141173 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071174 isolate_map = self.ReadIsolateMap()
1175
Scott Graham3be4b4162017-09-12 00:41:411176 is_android = 'target_os="android"' in vals['gn_args']
Benjamin Pastene3bce864e2018-04-14 01:16:321177 is_simplechrome = vals.get('cros_passthrough', False)
Scott Graham3be4b4162017-09-12 00:41:411178 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:301179 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061180
kylechar39705682017-01-19 14:37:231181 # This should be true if tests with type='windowed_test_launcher' are
1182 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081183 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1184 # backends. Currently, tests are executed for the headless and X11 backends
1185 # and both can run under Xvfb.
1186 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1187 # backend.
Scott Graham3be4b4162017-09-12 00:41:411188 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251189
1190 asan = 'is_asan=true' in vals['gn_args']
1191 msan = 'is_msan=true' in vals['gn_args']
1192 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411193 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251194
dprankecb4a2e242016-09-19 01:13:141195 test_type = isolate_map[target]['type']
dprankefe0d35e2016-02-05 02:43:591196
dprankecb4a2e242016-09-19 01:13:141197 executable = isolate_map[target].get('executable', target)
bsheedy9c16ed62019-04-10 20:32:111198 executable_suffix = isolate_map[target].get(
1199 'executable_suffix', '.exe' if is_win else '')
dprankefe0d35e2016-02-05 02:43:591200
dprankea55584f12015-07-22 00:52:471201 cmdline = []
Andrii Shyshkalovc158e0102018-01-10 05:52:001202 extra_files = [
1203 '../../.vpython',
1204 '../../testing/test_env.py',
1205 ]
dpranked8113582015-06-05 20:08:251206
dprankecb4a2e242016-09-19 01:13:141207 if test_type == 'nontest':
1208 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1209 output_path=None)
1210
John Budorick93e88ac82019-04-12 18:39:111211 if test_type == 'generated_script':
Ben Pastenecb0fb412019-06-11 02:31:541212 script = isolate_map[target]['script']
1213 if self.platform == 'win32':
1214 script += '.bat'
John Budorick93e88ac82019-04-12 18:39:111215 cmdline = [
1216 '../../testing/test_env.py',
Ben Pastenecb0fb412019-06-11 02:31:541217 script,
John Budorick93e88ac82019-04-12 18:39:111218 ]
1219 elif test_type == 'fuzzer':
Roberto Carrillo1460da852018-12-14 17:10:391220 cmdline = [
1221 '../../testing/test_env.py',
1222 '../../tools/code_coverage/run_fuzz_target.py',
1223 '--fuzzer', './' + target,
1224 '--output-dir', '${ISOLATED_OUTDIR}',
1225 '--timeout', '3600']
1226 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011227 cmdline = []
1228 if asan:
John Budorick31cdce62019-04-03 20:56:111229 cmdline += [os.path.join('bin', 'run_with_asan'), '--']
John Budorick8c4203042019-03-19 17:22:011230 cmdline += [
John Budorickfb97a852017-12-20 20:10:191231 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041232 '../../build/android/test_wrapper/logdog_wrapper.py',
1233 '--target', target,
hzl9ae14452017-04-04 23:38:021234 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481235 '--store-tombstones']
Scott Graham3be4b4162017-09-12 00:41:411236 elif is_fuchsia and test_type != 'script':
John Budorickfb97a852017-12-20 20:10:191237 cmdline = [
1238 '../../testing/test_env.py',
1239 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441240 '--test-launcher-bot-mode',
John Budorickfb97a852017-12-20 20:10:191241 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321242 elif is_simplechrome and test_type != 'script':
1243 cmdline = [
1244 '../../testing/test_env.py',
1245 os.path.join('bin', 'run_%s' % target),
1246 ]
kylechar39705682017-01-19 14:37:231247 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001248 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471249 cmdline = [
dprankefe0d35e2016-02-05 02:43:591250 '../../testing/xvfb.py',
dprankefe0d35e2016-02-05 02:43:591251 './' + str(executable) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591252 '--test-launcher-bot-mode',
1253 '--asan=%d' % asan,
1254 '--msan=%d' % msan,
1255 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411256 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471257 ]
1258 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471259 cmdline = [
1260 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591261 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251262 '--test-launcher-bot-mode',
1263 '--asan=%d' % asan,
1264 '--msan=%d' % msan,
1265 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411266 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471267 ]
dpranke6abd8652015-08-28 03:21:111268 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241269 cmdline = []
1270 # If we're testing a CrOS simplechrome build, assume we need to launch a
1271 # VM first. So prepend the command to run with the VM launcher.
1272 # TODO(bpastene): Differentiate between CrOS VM and hardware tests.
1273 if is_simplechrome:
1274 cmdline = [os.path.join('bin', 'launch_cros_vm')]
1275 cmdline += [
dpranke6abd8652015-08-28 03:21:111276 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141277 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591278 ]
Dirk Prankef24e6b22018-03-27 20:12:301279 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471280 cmdline = [
1281 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591282 ]
dprankea55584f12015-07-22 00:52:471283 else:
1284 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1285 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251286
Abhishek Arya2f5f7342018-06-13 16:59:441287 if is_win and asan:
Alexander Dunaev384ba482018-03-21 17:56:121288 # Sandbox is not yet supported by ASAN for Windows.
1289 # Perhaps this is only needed for tests that use the sandbox?
1290 cmdline.append('--no-sandbox')
1291
dprankecb4a2e242016-09-19 01:13:141292 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591293
dpranked8113582015-06-05 20:08:251294 return cmdline, extra_files
1295
dpranke74559b52015-06-10 21:20:391296 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331297 return self.PathJoin(self.chromium_src_dir,
1298 self.ToSrcRelPath(build_path),
1299 *comps)
dpranked8113582015-06-05 20:08:251300
dprankeee5b51f62015-04-09 00:03:221301 def ToSrcRelPath(self, path):
1302 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501303 if path.startswith('//'):
1304 return path[2:].replace('/', self.sep)
1305 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351306
Dirk Pranke0fd41bcd2015-06-19 00:05:501307 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141308 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501309 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001310 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501311 if ret:
1312 return ret
1313
Dirk Prankef24e6b22018-03-27 20:12:301314 build_path = self.args.path
1315 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141316 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301317 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141318 gn_output_path = output_path + '.gn'
1319
dpranke7837fc362015-11-19 03:54:161320 inp = self.ReadInputJSON(['files', 'test_targets',
1321 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321322 if self.args.verbose:
1323 self.Print()
1324 self.Print('analyze input:')
1325 self.PrintJSON(inp)
1326 self.Print()
1327
dpranke76734662015-04-16 02:17:501328
dpranke7c5f614d2015-07-22 23:43:391329 # This shouldn't normally happen, but could due to unusual race conditions,
1330 # like a try job that gets scheduled before a patch lands but runs after
1331 # the patch has landed.
1332 if not inp['files']:
1333 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161334 self.WriteJSON({
1335 'status': 'No dependency',
1336 'compile_targets': [],
1337 'test_targets': [],
1338 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391339 return 0
1340
dprankecb4a2e242016-09-19 01:13:141341 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161342 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561343
dprankecb4a2e242016-09-19 01:13:141344 isolate_map = self.ReadIsolateMap()
1345 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1346 isolate_map, inp['additional_compile_targets'])
1347 if err:
1348 raise MBErr(err)
1349
1350 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1351 isolate_map, inp['test_targets'])
1352 if err:
1353 raise MBErr(err)
1354 labels_to_targets = {}
1355 for i, label in enumerate(gn_inp['test_targets']):
1356 labels_to_targets[label] = inp['test_targets'][i]
1357
dprankef61de2f2015-05-14 04:09:561358 try:
dprankecb4a2e242016-09-19 01:13:141359 self.WriteJSON(gn_inp, gn_input_path)
1360 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
1361 ret, _, _ = self.Run(cmd, force_verbose=True)
1362 if ret:
1363 return ret
dpranke067d0142015-05-14 22:52:451364
dprankecb4a2e242016-09-19 01:13:141365 gn_outp_str = self.ReadFile(gn_output_path)
1366 try:
1367 gn_outp = json.loads(gn_outp_str)
1368 except Exception as e:
1369 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1370 % (repr(gn_outp_str), str(e)))
1371 raise
1372
1373 outp = {}
1374 if 'status' in gn_outp:
1375 outp['status'] = gn_outp['status']
1376 if 'error' in gn_outp:
1377 outp['error'] = gn_outp['error']
1378 if 'invalid_targets' in gn_outp:
1379 outp['invalid_targets'] = gn_outp['invalid_targets']
1380 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491381 all_input_compile_targets = sorted(
1382 set(inp['test_targets'] + inp['additional_compile_targets']))
1383
1384 # If we're building 'all', we can throw away the rest of the targets
1385 # since they're redundant.
dpranke385a3102016-09-20 22:04:081386 if 'all' in gn_outp['compile_targets']:
1387 outp['compile_targets'] = ['all']
1388 else:
Dirk Pranke45165072017-11-08 04:57:491389 outp['compile_targets'] = gn_outp['compile_targets']
1390
1391 # crbug.com/736215: When GN returns targets back, for targets in
1392 # the default toolchain, GN will have generated a phony ninja
1393 # target matching the label, and so we can safely (and easily)
1394 # transform any GN label into the matching ninja target. For
1395 # targets in other toolchains, though, GN doesn't generate the
1396 # phony targets, and we don't know how to turn the labels into
1397 # compile targets. In this case, we also conservatively give up
1398 # and build everything. Probably the right thing to do here is
1399 # to have GN return the compile targets directly.
1400 if any("(" in target for target in outp['compile_targets']):
1401 self.Print('WARNING: targets with non-default toolchains were '
1402 'found, building everything instead.')
1403 outp['compile_targets'] = all_input_compile_targets
1404 else:
dpranke385a3102016-09-20 22:04:081405 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491406 label.replace('//', '') for label in outp['compile_targets']]
1407
1408 # Windows has a maximum command line length of 8k; even Linux
1409 # maxes out at 128k; if analyze returns a *really long* list of
1410 # targets, we just give up and conservatively build everything instead.
1411 # Probably the right thing here is for ninja to support response
1412 # files as input on the command line
1413 # (see https://github.com/ninja-build/ninja/issues/1355).
1414 if len(' '.join(outp['compile_targets'])) > 7*1024:
1415 self.Print('WARNING: Too many compile targets were affected.')
1416 self.Print('WARNING: Building everything instead to avoid '
1417 'command-line length issues.')
1418 outp['compile_targets'] = all_input_compile_targets
1419
1420
dprankecb4a2e242016-09-19 01:13:141421 if 'test_targets' in gn_outp:
1422 outp['test_targets'] = [
1423 labels_to_targets[label] for label in gn_outp['test_targets']]
1424
1425 if self.args.verbose:
1426 self.Print()
1427 self.Print('analyze output:')
1428 self.PrintJSON(outp)
1429 self.Print()
1430
1431 self.WriteJSON(outp, output_path)
1432
dprankef61de2f2015-05-14 04:09:561433 finally:
dprankecb4a2e242016-09-19 01:13:141434 if self.Exists(gn_input_path):
1435 self.RemoveFile(gn_input_path)
1436 if self.Exists(gn_output_path):
1437 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351438
1439 return 0
1440
dpranked8113582015-06-05 20:08:251441 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301442 path = self.args.input_path
1443 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351444 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321445 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351446
1447 try:
1448 inp = json.loads(self.ReadFile(path))
1449 except Exception as e:
1450 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321451 (path, e), output_path)
dpranked8113582015-06-05 20:08:251452
1453 for k in required_keys:
1454 if not k in inp:
1455 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1456 output_path)
dprankefe4602312015-04-08 16:20:351457
1458 return inp
1459
dpranked5b2b9432015-06-23 16:55:301460 def WriteFailureAndRaise(self, msg, output_path):
1461 if output_path:
dprankee0547cd2015-09-15 01:27:401462 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351463 raise MBErr(msg)
1464
dprankee0547cd2015-09-15 01:27:401465 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321466 try:
dprankee0547cd2015-09-15 01:27:401467 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1468 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321469 except Exception as e:
1470 raise MBErr('Error %s writing to the output path "%s"' %
1471 (e, path))
dprankefe4602312015-04-08 16:20:351472
aneeshmde50f472016-04-01 01:13:101473 def CheckCompile(self, master, builder):
1474 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1475 url = urllib2.quote(url_template.format(master=master, builder=builder),
1476 safe=':/()?=')
1477 try:
1478 builds = json.loads(self.Fetch(url))
1479 except Exception as e:
1480 return str(e)
1481 successes = sorted(
1482 [int(x) for x in builds.keys() if "text" in builds[x] and
1483 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1484 reverse=True)
1485 if not successes:
1486 return "no successful builds"
1487 build = builds[str(successes[0])]
1488 step_names = set([step["name"] for step in build["steps"]])
1489 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1490 if compile_indicators & step_names:
1491 return "compiles"
1492 return "does not compile"
1493
dpranke3cec199c2015-09-22 23:29:021494 def PrintCmd(self, cmd, env):
1495 if self.platform == 'win32':
1496 env_prefix = 'set '
1497 env_quoter = QuoteForSet
1498 shell_quoter = QuoteForCmd
1499 else:
1500 env_prefix = ''
1501 env_quoter = pipes.quote
1502 shell_quoter = pipes.quote
1503
1504 def print_env(var):
1505 if env and var in env:
1506 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1507
dprankeec079262016-06-07 02:21:201508 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021509
dpranke8c2cfd32015-09-17 20:12:331510 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351511 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021512 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351513
dprankecda00332015-04-11 04:18:321514 def PrintJSON(self, obj):
1515 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1516
dpranke751516a2015-10-03 01:11:341517 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301518 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381519 if self.platform == 'win32':
1520 # On Windows use the batch script since there is no exe
1521 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1522 else:
1523 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341524 if self.args.jobs:
1525 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1526 ninja_cmd.append(target)
Dirk Pranke5f22a822019-05-23 22:55:251527 ret, _, _ = self.Run(ninja_cmd, buffer_output=False)
dpranke751516a2015-10-03 01:11:341528 return ret
1529
1530 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351531 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401532 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021533 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351534 if self.args.dryrun:
1535 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401536
dpranke751516a2015-10-03 01:11:341537 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401538 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341539 if ret:
1540 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351541 if out:
dprankeee5b51f62015-04-09 00:03:221542 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351543 if err:
dprankeee5b51f62015-04-09 00:03:221544 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351545 return ret, out, err
1546
dpranke751516a2015-10-03 01:11:341547 def Call(self, cmd, env=None, buffer_output=True):
1548 if buffer_output:
1549 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1550 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1551 env=env)
1552 out, err = p.communicate()
1553 else:
1554 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1555 env=env)
1556 p.wait()
1557 out = err = ''
dprankefe4602312015-04-08 16:20:351558 return p.returncode, out, err
1559
1560 def ExpandUser(self, path):
1561 # This function largely exists so it can be overridden for testing.
1562 return os.path.expanduser(path)
1563
1564 def Exists(self, path):
1565 # This function largely exists so it can be overridden for testing.
1566 return os.path.exists(path)
1567
dpranke867bcf4a2016-03-14 22:28:321568 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501569 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321570 f = urllib2.urlopen(url)
1571 contents = f.read()
1572 f.close()
1573 return contents
1574
dprankec3441d12015-06-23 23:01:351575 def MaybeMakeDirectory(self, path):
1576 try:
1577 os.makedirs(path)
1578 except OSError, e:
1579 if e.errno != errno.EEXIST:
1580 raise
1581
dpranke8c2cfd32015-09-17 20:12:331582 def PathJoin(self, *comps):
1583 # This function largely exists so it can be overriden for testing.
1584 return os.path.join(*comps)
1585
dpranke030d7a6d2016-03-26 17:23:501586 def Print(self, *args, **kwargs):
1587 # This function largely exists so it can be overridden for testing.
1588 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101589 if kwargs.get('stream', sys.stdout) == sys.stdout:
1590 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501591
dprankefe4602312015-04-08 16:20:351592 def ReadFile(self, path):
1593 # This function largely exists so it can be overriden for testing.
1594 with open(path) as fp:
1595 return fp.read()
1596
dpranke030d7a6d2016-03-26 17:23:501597 def RelPath(self, path, start='.'):
1598 # This function largely exists so it can be overriden for testing.
1599 return os.path.relpath(path, start)
1600
dprankef61de2f2015-05-14 04:09:561601 def RemoveFile(self, path):
1602 # This function largely exists so it can be overriden for testing.
1603 os.remove(path)
1604
dprankec161aa92015-09-14 20:21:131605 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331606 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131607 # In other places in chromium, we often have to retry this command
1608 # because we're worried about other processes still holding on to
1609 # file handles, but when MB is invoked, it will be early enough in the
1610 # build that their should be no other processes to interfere. We
1611 # can change this if need be.
1612 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1613 else:
1614 shutil.rmtree(abs_path, ignore_errors=True)
1615
Dirk Prankef24e6b22018-03-27 20:12:301616 def TempDir(self):
1617 # This function largely exists so it can be overriden for testing.
1618 return tempfile.mkdtemp(prefix='mb_')
1619
dprankef61de2f2015-05-14 04:09:561620 def TempFile(self, mode='w'):
1621 # This function largely exists so it can be overriden for testing.
1622 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1623
dprankee0547cd2015-09-15 01:27:401624 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351625 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401626 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301627 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351628 with open(path, 'w') as fp:
1629 return fp.write(contents)
1630
dprankef61de2f2015-05-14 04:09:561631
dprankefe4602312015-04-08 16:20:351632class MBErr(Exception):
1633 pass
1634
1635
dpranke3cec199c2015-09-22 23:29:021636# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1637# details of this next section, which handles escaping command lines
1638# so that they can be copied and pasted into a cmd window.
1639UNSAFE_FOR_SET = set('^<>&|')
1640UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1641ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1642
1643
1644def QuoteForSet(arg):
1645 if any(a in UNSAFE_FOR_SET for a in arg):
1646 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1647 return arg
1648
1649
1650def QuoteForCmd(arg):
1651 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021652 if arg == '' or ' ' in arg or '"' in arg:
1653 quote_re = re.compile(r'(\\*)"')
1654 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1655
1656 # Then check to see if the arg contains any metacharacters other than
1657 # double quotes; if it does, quote everything (including the double
1658 # quotes) for safety.
1659 if any(a in UNSAFE_FOR_CMD for a in arg):
1660 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1661 return arg
1662
1663
dprankefe4602312015-04-08 16:20:351664if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591665 sys.exit(main(sys.argv[1:]))