blob: 275fc61e0675a0e0402b0696b17de4a27d4e05c6 [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 = {}
57 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',
Stephen Martinis239c35a2019-07-22 19:34:40115 description='Analyze whether changes to a set of '
116 'files will cause a set of binaries to '
117 'be rebuilt.')
dprankefe4602312015-04-08 16:20:35118 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30119 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35120 help='path build was generated into.')
Dirk Prankef24e6b22018-03-27 20:12:30121 subp.add_argument('input_path',
dprankefe4602312015-04-08 16:20:35122 help='path to a file containing the input arguments '
123 'as a JSON object.')
Dirk Prankef24e6b22018-03-27 20:12:30124 subp.add_argument('output_path',
dprankefe4602312015-04-08 16:20:35125 help='path to a file containing the output arguments '
126 'as a JSON object.')
Debrian Figueroaae51d0d2019-07-22 18:04:11127 subp.add_argument('--json-output',
Debrian Figueroaae582232019-07-17 01:54:45128 help='Write errors to json.output')
dprankefe4602312015-04-08 16:20:35129 subp.set_defaults(func=self.CmdAnalyze)
130
dprankef37aebb92016-09-23 01:14:49131 subp = subps.add_parser('export',
Stephen Martinis239c35a2019-07-22 19:34:40132 description='Print out the expanded configuration '
133 'for each builder as a JSON object.')
dprankef37aebb92016-09-23 01:14:49134 subp.add_argument('-f', '--config-file', metavar='PATH',
135 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50136 help='path to config file (default is %(default)s)')
dprankef37aebb92016-09-23 01:14:49137 subp.add_argument('-g', '--goma-dir',
138 help='path to goma directory')
139 subp.set_defaults(func=self.CmdExport)
140
dprankefe4602312015-04-08 16:20:35141 subp = subps.add_parser('gen',
Stephen Martinis239c35a2019-07-22 19:34:40142 description='Generate a new set of build files.')
dprankefe4602312015-04-08 16:20:35143 AddCommonOptions(subp)
dpranke74559b52015-06-10 21:20:39144 subp.add_argument('--swarming-targets-file',
145 help='save runtime dependencies for targets listed '
146 'in file.')
Debrian Figueroaae51d0d2019-07-22 18:04:11147 subp.add_argument('--json-output',
Debrian Figueroaae582232019-07-17 01:54:45148 help='Write errors to json.output')
Dirk Prankef24e6b22018-03-27 20:12:30149 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35150 help='path to generate build into')
151 subp.set_defaults(func=self.CmdGen)
152
Erik Chen42df41d2018-08-21 17:13:31153 subp = subps.add_parser('isolate-everything',
Stephen Martinis239c35a2019-07-22 19:34:40154 description='Generates a .isolate for all targets. '
155 'Requires that mb.py gen has already '
156 'been run.')
Erik Chen42df41d2018-08-21 17:13:31157 AddCommonOptions(subp)
158 subp.set_defaults(func=self.CmdIsolateEverything)
159 subp.add_argument('path',
160 help='path build was generated into')
161
dpranke751516a2015-10-03 01:11:34162 subp = subps.add_parser('isolate',
Stephen Martinis239c35a2019-07-22 19:34:40163 description='Generate the .isolate files for a '
164 'given binary.')
dpranke751516a2015-10-03 01:11:34165 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30166 subp.add_argument('--no-build', dest='build', default=True,
167 action='store_false',
168 help='Do not build, just isolate')
169 subp.add_argument('-j', '--jobs', type=int,
170 help='Number of jobs to pass to ninja')
171 subp.add_argument('path',
dpranke751516a2015-10-03 01:11:34172 help='path build was generated into')
Dirk Prankef24e6b22018-03-27 20:12:30173 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34174 help='ninja target to generate the isolate for')
175 subp.set_defaults(func=self.CmdIsolate)
176
dprankefe4602312015-04-08 16:20:35177 subp = subps.add_parser('lookup',
Stephen Martinis239c35a2019-07-22 19:34:40178 description='Look up the command for a given '
179 'config or builder.')
dprankefe4602312015-04-08 16:20:35180 AddCommonOptions(subp)
Garrett Beatyb6cee042019-04-22 18:42:09181 subp.add_argument('--quiet', default=False, action='store_true',
182 help='Print out just the arguments, '
183 'do not emulate the output of the gen subcommand.')
184 subp.add_argument('--recursive', default=False, action='store_true',
185 help='Lookup arguments from imported files, '
186 'implies --quiet')
dprankefe4602312015-04-08 16:20:35187 subp.set_defaults(func=self.CmdLookup)
188
dpranke030d7a6d2016-03-26 17:23:50189 subp = subps.add_parser(
Stephen Martinis239c35a2019-07-22 19:34:40190 'run', formatter_class=argparse.RawDescriptionHelpFormatter)
dpranke030d7a6d2016-03-26 17:23:50191 subp.description = (
192 'Build, isolate, and run the given binary with the command line\n'
193 'listed in the isolate. You may pass extra arguments after the\n'
194 'target; use "--" if the extra arguments need to include switches.\n'
195 '\n'
196 'Examples:\n'
197 '\n'
198 ' % tools/mb/mb.py run -m chromium.linux -b "Linux Builder" \\\n'
199 ' //out/Default content_browsertests\n'
200 '\n'
201 ' % tools/mb/mb.py run out/Default content_browsertests\n'
202 '\n'
203 ' % tools/mb/mb.py run out/Default content_browsertests -- \\\n'
204 ' --test-launcher-retry-limit=0'
205 '\n'
206 )
dpranke751516a2015-10-03 01:11:34207 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30208 subp.add_argument('-j', '--jobs', type=int,
dpranke751516a2015-10-03 01:11:34209 help='Number of jobs to pass to ninja')
210 subp.add_argument('--no-build', dest='build', default=True,
211 action='store_false',
212 help='Do not build, just isolate and run')
Dirk Prankef24e6b22018-03-27 20:12:30213 subp.add_argument('path',
dpranke030d7a6d2016-03-26 17:23:50214 help=('path to generate build into (or use).'
215 ' This can be either a regular path or a '
216 'GN-style source-relative path like '
217 '//out/Default.'))
Dirk Pranke8cb6aa782017-12-16 02:31:33218 subp.add_argument('-s', '--swarmed', action='store_true',
219 help='Run under swarming with the default dimensions')
220 subp.add_argument('-d', '--dimension', default=[], action='append', nargs=2,
221 dest='dimensions', metavar='FOO bar',
222 help='dimension to filter on')
223 subp.add_argument('--no-default-dimensions', action='store_false',
224 dest='default_dimensions', default=True,
225 help='Do not automatically add dimensions to the task')
Dirk Prankef24e6b22018-03-27 20:12:30226 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34227 help='ninja target to build and run')
dpranke030d7a6d2016-03-26 17:23:50228 subp.add_argument('extra_args', nargs='*',
229 help=('extra args to pass to the isolate to run. Use '
230 '"--" as the first arg if you need to pass '
231 'switches'))
dpranke751516a2015-10-03 01:11:34232 subp.set_defaults(func=self.CmdRun)
233
dprankefe4602312015-04-08 16:20:35234 subp = subps.add_parser('validate',
Stephen Martinis239c35a2019-07-22 19:34:40235 description='Validate the config file.')
dprankea5a77ca2015-07-16 23:24:17236 subp.add_argument('-f', '--config-file', metavar='PATH',
237 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50238 help='path to config file (default is %(default)s)')
dprankefe4602312015-04-08 16:20:35239 subp.set_defaults(func=self.CmdValidate)
240
Dirk Prankef24e6b22018-03-27 20:12:30241 subp = subps.add_parser('zip',
Stephen Martinis239c35a2019-07-22 19:34:40242 description='Generate a .zip containing the files '
243 'needed for a given binary.')
Dirk Prankef24e6b22018-03-27 20:12:30244 AddCommonOptions(subp)
245 subp.add_argument('--no-build', dest='build', default=True,
246 action='store_false',
247 help='Do not build, just isolate')
248 subp.add_argument('-j', '--jobs', type=int,
249 help='Number of jobs to pass to ninja')
250 subp.add_argument('path',
251 help='path build was generated into')
252 subp.add_argument('target',
253 help='ninja target to generate the isolate for')
254 subp.add_argument('zip_path',
255 help='path to zip file to create')
256 subp.set_defaults(func=self.CmdZip)
257
dprankefe4602312015-04-08 16:20:35258 subp = subps.add_parser('help',
259 help='Get help on a subcommand.')
260 subp.add_argument(nargs='?', action='store', dest='subcommand',
261 help='The command to get help for.')
262 subp.set_defaults(func=self.CmdHelp)
263
264 self.args = parser.parse_args(argv)
265
dprankeb2be10a2016-02-22 17:11:00266 def DumpInputFiles(self):
267
dprankef7b7eb7a2016-03-28 22:42:59268 def DumpContentsOfFilePassedTo(arg_name, path):
dprankeb2be10a2016-02-22 17:11:00269 if path and self.Exists(path):
dprankef7b7eb7a2016-03-28 22:42:59270 self.Print("\n# To recreate the file passed to %s:" % arg_name)
dprankecb4a2e242016-09-19 01:13:14271 self.Print("%% cat > %s <<EOF" % path)
dprankeb2be10a2016-02-22 17:11:00272 contents = self.ReadFile(path)
dprankef7b7eb7a2016-03-28 22:42:59273 self.Print(contents)
274 self.Print("EOF\n%\n")
dprankeb2be10a2016-02-22 17:11:00275
dprankef7b7eb7a2016-03-28 22:42:59276 if getattr(self.args, 'input_path', None):
277 DumpContentsOfFilePassedTo(
Dirk Prankef24e6b22018-03-27 20:12:30278 'argv[0] (input_path)', self.args.input_path)
dprankef7b7eb7a2016-03-28 22:42:59279 if getattr(self.args, 'swarming_targets_file', None):
280 DumpContentsOfFilePassedTo(
281 '--swarming-targets-file', self.args.swarming_targets_file)
dprankeb2be10a2016-02-22 17:11:00282
dprankefe4602312015-04-08 16:20:35283 def CmdAnalyze(self):
dpranke751516a2015-10-03 01:11:34284 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11285 return self.RunGNAnalyze(vals)
dprankefe4602312015-04-08 16:20:35286
dprankef37aebb92016-09-23 01:14:49287 def CmdExport(self):
288 self.ReadConfigFile()
289 obj = {}
290 for master, builders in self.masters.items():
291 obj[master] = {}
292 for builder in builders:
293 config = self.masters[master][builder]
294 if not config:
295 continue
296
shenghuazhang804b21542016-10-11 02:06:49297 if isinstance(config, dict):
298 args = {k: self.FlattenConfig(v)['gn_args']
299 for k, v in config.items()}
dprankef37aebb92016-09-23 01:14:49300 elif config.startswith('//'):
301 args = config
302 else:
303 args = self.FlattenConfig(config)['gn_args']
304 if 'error' in args:
305 continue
306
307 obj[master][builder] = args
308
309 # Dump object and trim trailing whitespace.
310 s = '\n'.join(l.rstrip() for l in
311 json.dumps(obj, sort_keys=True, indent=2).splitlines())
312 self.Print(s)
313 return 0
314
dprankefe4602312015-04-08 16:20:35315 def CmdGen(self):
dpranke751516a2015-10-03 01:11:34316 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11317 return self.RunGNGen(vals)
dprankefe4602312015-04-08 16:20:35318
Erik Chen42df41d2018-08-21 17:13:31319 def CmdIsolateEverything(self):
320 vals = self.Lookup()
321 return self.RunGNGenAllIsolates(vals)
322
dprankefe4602312015-04-08 16:20:35323 def CmdHelp(self):
324 if self.args.subcommand:
325 self.ParseArgs([self.args.subcommand, '--help'])
326 else:
327 self.ParseArgs(['--help'])
328
dpranke751516a2015-10-03 01:11:34329 def CmdIsolate(self):
330 vals = self.GetConfig()
331 if not vals:
332 return 1
Dirk Prankef24e6b22018-03-27 20:12:30333 if self.args.build:
334 ret = self.Build(self.args.target)
335 if ret:
336 return ret
Dirk Pranked181a1a2017-12-14 01:47:11337 return self.RunGNIsolate(vals)
dpranke751516a2015-10-03 01:11:34338
339 def CmdLookup(self):
340 vals = self.Lookup()
Garrett Beatyb6cee042019-04-22 18:42:09341 gn_args = self.GNArgs(vals, expand_imports=self.args.recursive)
342 if self.args.quiet or self.args.recursive:
343 self.Print(gn_args, end='')
344 else:
345 cmd = self.GNCmd('gen', '_path_')
346 self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args)
347 env = None
dpranke751516a2015-10-03 01:11:34348
Garrett Beatyb6cee042019-04-22 18:42:09349 self.PrintCmd(cmd, env)
dpranke751516a2015-10-03 01:11:34350 return 0
351
352 def CmdRun(self):
353 vals = self.GetConfig()
354 if not vals:
355 return 1
Dirk Pranked181a1a2017-12-14 01:47:11356 if self.args.build:
Dirk Pranke5f22a822019-05-23 22:55:25357 self.Print('')
Dirk Prankef24e6b22018-03-27 20:12:30358 ret = self.Build(self.args.target)
dpranke751516a2015-10-03 01:11:34359 if ret:
360 return ret
Dirk Pranke5f22a822019-05-23 22:55:25361
362 self.Print('')
Dirk Pranked181a1a2017-12-14 01:47:11363 ret = self.RunGNIsolate(vals)
364 if ret:
365 return ret
dpranke751516a2015-10-03 01:11:34366
Dirk Pranke5f22a822019-05-23 22:55:25367 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33368 if self.args.swarmed:
Dirk Prankef24e6b22018-03-27 20:12:30369 return self._RunUnderSwarming(self.args.path, self.args.target)
Dirk Pranke8cb6aa782017-12-16 02:31:33370 else:
Dirk Prankef24e6b22018-03-27 20:12:30371 return self._RunLocallyIsolated(self.args.path, self.args.target)
372
373 def CmdZip(self):
374 ret = self.CmdIsolate()
375 if ret:
376 return ret
377
378 zip_dir = None
379 try:
380 zip_dir = self.TempDir()
381 remap_cmd = [
382 self.executable,
383 self.PathJoin(self.chromium_src_dir, 'tools', 'swarming_client',
384 'isolate.py'),
385 'remap',
Kenneth Russell2e75e2f2018-11-15 22:37:28386 '--collapse_symlinks',
Dirk Prankef24e6b22018-03-27 20:12:30387 '-s', self.PathJoin(self.args.path, self.args.target + '.isolated'),
388 '-o', zip_dir
389 ]
390 self.Run(remap_cmd)
391
392 zip_path = self.args.zip_path
393 with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as fp:
394 for root, _, files in os.walk(zip_dir):
395 for filename in files:
396 path = self.PathJoin(root, filename)
397 fp.write(path, self.RelPath(path, zip_dir))
398 finally:
399 if zip_dir:
400 self.RemoveDirectory(zip_dir)
Dirk Pranke8cb6aa782017-12-16 02:31:33401
Robert Iannucci5a9d75f62018-03-02 05:28:20402 @staticmethod
403 def _AddBaseSoftware(cmd):
404 # HACK(iannucci): These packages SHOULD NOT BE HERE.
405 # Remove method once Swarming Pool Task Templates are implemented.
406 # crbug.com/812428
407
408 # Add in required base software. This should be kept in sync with the
John Budorick9d9175372019-04-01 19:04:24409 # `chromium_swarming` recipe module in build.git. All references to
410 # `swarming_module` below are purely due to this.
Robert Iannucci5a9d75f62018-03-02 05:28:20411 cipd_packages = [
412 ('infra/python/cpython/${platform}',
smut22dcd68e2019-06-25 23:33:27413 'version:2.7.15.chromium14'),
Robert Iannucci5a9d75f62018-03-02 05:28:20414 ('infra/tools/luci/logdog/butler/${platform}',
415 'git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c'),
416 ('infra/tools/luci/vpython-native/${platform}',
Marc-Antoine Ruele2b07a32019-04-15 17:52:09417 'git_revision:cc09450f1c27c0034ec08b1f6d63bbc298294763'),
Robert Iannucci5a9d75f62018-03-02 05:28:20418 ('infra/tools/luci/vpython/${platform}',
Marc-Antoine Ruele2b07a32019-04-15 17:52:09419 'git_revision:cc09450f1c27c0034ec08b1f6d63bbc298294763'),
Robert Iannucci5a9d75f62018-03-02 05:28:20420 ]
421 for pkg, vers in cipd_packages:
422 cmd.append('--cipd-package=.swarming_module:%s:%s' % (pkg, vers))
423
424 # Add packages to $PATH
425 cmd.extend([
426 '--env-prefix=PATH', '.swarming_module',
427 '--env-prefix=PATH', '.swarming_module/bin',
428 ])
429
430 # Add cache directives for vpython.
431 vpython_cache_path = '.swarming_module_cache/vpython'
432 cmd.extend([
433 '--named-cache=swarming_module_cache_vpython', vpython_cache_path,
434 '--env-prefix=VPYTHON_VIRTUALENV_ROOT', vpython_cache_path,
435 ])
436
Dirk Pranke8cb6aa782017-12-16 02:31:33437 def _RunUnderSwarming(self, build_dir, target):
Marc-Antoine Ruel559cc4732019-03-19 22:20:46438 isolate_server = 'isolateserver.appspot.com'
439 namespace = 'default-gzip'
440 swarming_server = 'chromium-swarm.appspot.com'
Dirk Pranke8cb6aa782017-12-16 02:31:33441 # TODO(dpranke): Look up the information for the target in
442 # the //testing/buildbot.json file, if possible, so that we
443 # can determine the isolate target, command line, and additional
444 # swarming parameters, if possible.
445 #
446 # TODO(dpranke): Also, add support for sharding and merging results.
447 dimensions = []
448 for k, v in self._DefaultDimensions() + self.args.dimensions:
449 dimensions += ['-d', k, v]
450
451 cmd = [
452 self.executable,
453 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
454 'archive',
Marc-Antoine Ruel559cc4732019-03-19 22:20:46455 '-s', self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
456 '-I', isolate_server,
457 '--namespace', namespace,
Dirk Pranke8cb6aa782017-12-16 02:31:33458 ]
Dirk Pranke5f22a822019-05-23 22:55:25459
460 # Talking to the isolateserver may fail because we're not logged in.
461 # We trap the command explicitly and rewrite the error output so that
462 # the error message is actually correct for a Chromium check out.
463 self.PrintCmd(cmd, env=None)
464 ret, out, err = self.Run(cmd, force_verbose=False)
Dirk Pranke8cb6aa782017-12-16 02:31:33465 if ret:
Dirk Pranke5f22a822019-05-23 22:55:25466 self.Print(' -> returned %d' % ret)
467 if out:
468 self.Print(out, end='')
469 if err:
470 # The swarming client will return an exit code of 2 (via
471 # argparse.ArgumentParser.error()) and print a message to indicate
472 # that auth failed, so we have to parse the message to check.
473 if (ret == 2 and 'Please login to' in err):
474 err = err.replace(' auth.py', ' tools/swarming_client/auth.py')
475 self.Print(err, end='', file=sys.stderr)
476
Dirk Pranke8cb6aa782017-12-16 02:31:33477 return ret
478
479 isolated_hash = out.splitlines()[0].split()[0]
480 cmd = [
481 self.executable,
482 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
483 'run',
484 '-s', isolated_hash,
Marc-Antoine Ruel559cc4732019-03-19 22:20:46485 '-I', isolate_server,
486 '--namespace', namespace,
487 '-S', swarming_server,
Dirk Pranke8cb6aa782017-12-16 02:31:33488 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20489 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33490 if self.args.extra_args:
491 cmd += ['--'] + self.args.extra_args
Dirk Pranke5f22a822019-05-23 22:55:25492 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33493 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
494 return ret
495
496 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50497 cmd = [
dpranke751516a2015-10-03 01:11:34498 self.executable,
499 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
500 'run',
501 '-s',
dpranke030d7a6d2016-03-26 17:23:50502 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33503 ]
dpranke030d7a6d2016-03-26 17:23:50504 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33505 cmd += ['--'] + self.args.extra_args
506 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34507 return ret
508
Dirk Pranke8cb6aa782017-12-16 02:31:33509 def _DefaultDimensions(self):
510 if not self.args.default_dimensions:
511 return []
512
513 # This code is naive and just picks reasonable defaults per platform.
514 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40515 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33516 elif self.platform.startswith('linux'):
517 os_dim = ('os', 'Ubuntu-14.04')
518 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40519 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33520 else:
521 raise MBErr('unrecognized platform string "%s"' % self.platform)
522
523 return [('pool', 'Chrome'),
524 ('cpu', 'x86-64'),
525 os_dim]
526
dpranke0cafc162016-03-19 00:41:10527 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35528 errs = []
529
530 # Read the file to make sure it parses.
531 self.ReadConfigFile()
532
dpranke3be00142016-03-17 22:46:04533 # Build a list of all of the configs referenced by builders.
dprankefe4602312015-04-08 16:20:35534 all_configs = {}
dprankefe4602312015-04-08 16:20:35535 for master in self.masters:
dpranke3be00142016-03-17 22:46:04536 for config in self.masters[master].values():
shenghuazhang804b21542016-10-11 02:06:49537 if isinstance(config, dict):
538 for c in config.values():
dprankeb9380a12016-07-21 21:44:09539 all_configs[c] = master
540 else:
541 all_configs[config] = master
dprankefe4602312015-04-08 16:20:35542
dpranke9dd5e252016-04-14 04:23:09543 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35544 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09545 if config.startswith('//'):
546 if not self.Exists(self.ToAbsPath(config)):
547 errs.append('Unknown args file "%s" referenced from "%s".' %
548 (config, loc))
549 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35550 errs.append('Unknown config "%s" referenced from "%s".' %
551 (config, loc))
552
553 # Check that every actual config is actually referenced.
554 for config in self.configs:
555 if not config in all_configs:
556 errs.append('Unused config "%s".' % config)
557
558 # Figure out the whole list of mixins, and check that every mixin
559 # listed by a config or another mixin actually exists.
560 referenced_mixins = set()
561 for config, mixins in self.configs.items():
562 for mixin in mixins:
563 if not mixin in self.mixins:
564 errs.append('Unknown mixin "%s" referenced by config "%s".' %
565 (mixin, config))
566 referenced_mixins.add(mixin)
567
568 for mixin in self.mixins:
569 for sub_mixin in self.mixins[mixin].get('mixins', []):
570 if not sub_mixin in self.mixins:
571 errs.append('Unknown mixin "%s" referenced by mixin "%s".' %
572 (sub_mixin, mixin))
573 referenced_mixins.add(sub_mixin)
574
575 # Check that every mixin defined is actually referenced somewhere.
576 for mixin in self.mixins:
577 if not mixin in referenced_mixins:
578 errs.append('Unreferenced mixin "%s".' % mixin)
579
dpranke255085e2016-03-16 05:23:59580 # If we're checking the Chromium config, check that the 'chromium' bots
581 # which build public artifacts do not include the chrome_with_codecs mixin.
582 if self.args.config_file == self.default_config:
583 if 'chromium' in self.masters:
584 for builder in self.masters['chromium']:
585 config = self.masters['chromium'][builder]
586 def RecurseMixins(current_mixin):
587 if current_mixin == 'chrome_with_codecs':
588 errs.append('Public artifact builder "%s" can not contain the '
589 '"chrome_with_codecs" mixin.' % builder)
590 return
591 if not 'mixins' in self.mixins[current_mixin]:
592 return
593 for mixin in self.mixins[current_mixin]['mixins']:
594 RecurseMixins(mixin)
dalecurtis56fd27e2016-03-09 23:06:41595
dpranke255085e2016-03-16 05:23:59596 for mixin in self.configs[config]:
597 RecurseMixins(mixin)
598 else:
599 errs.append('Missing "chromium" master. Please update this '
600 'proprietary codecs check with the name of the master '
601 'responsible for public build artifacts.')
dalecurtis56fd27e2016-03-09 23:06:41602
dprankefe4602312015-04-08 16:20:35603 if errs:
dpranke4323c80632015-08-10 22:53:54604 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17605 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35606
dpranke0cafc162016-03-19 00:41:10607 if print_ok:
608 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35609 return 0
610
611 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30612 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34613
dprankef37aebb92016-09-23 01:14:49614 vals = self.DefaultVals()
dpranke751516a2015-10-03 01:11:34615 if self.args.builder or self.args.master or self.args.config:
616 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11617 # Re-run gn gen in order to ensure the config is consistent with the
618 # build dir.
619 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34620 return vals
621
Dirk Pranked181a1a2017-12-14 01:47:11622 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
623 'toolchain.ninja')
624 if not self.Exists(toolchain_path):
625 self.Print('Must either specify a path to an existing GN build dir '
626 'or pass in a -m/-b pair or a -c flag to specify the '
627 'configuration')
628 return {}
dpranke751516a2015-10-03 01:11:34629
Dirk Pranked181a1a2017-12-14 01:47:11630 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34631 return vals
632
dprankef37aebb92016-09-23 01:14:49633 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58634 args_contents = ""
635 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
636 if self.Exists(gn_args_path):
637 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34638 gn_args = []
639 for l in args_contents.splitlines():
640 fields = l.split(' ')
641 name = fields[0]
642 val = ' '.join(fields[2:])
643 gn_args.append('%s=%s' % (name, val))
644
dprankef37aebb92016-09-23 01:14:49645 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34646
647 def Lookup(self):
Erik Chen238f4ac2019-04-12 19:02:50648 vals = self.ReadIOSBotConfig()
649 if not vals:
650 self.ReadConfigFile()
651 config = self.ConfigFromArgs()
652 if config.startswith('//'):
653 if not self.Exists(self.ToAbsPath(config)):
654 raise MBErr('args file "%s" not found' % config)
655 vals = self.DefaultVals()
656 vals['args_file'] = config
657 else:
658 if not config in self.configs:
659 raise MBErr('Config "%s" not found in %s' %
660 (config, self.args.config_file))
661 vals = self.FlattenConfig(config)
662 return vals
663
664 def ReadIOSBotConfig(self):
665 if not self.args.master or not self.args.builder:
666 return {}
667 path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
668 self.args.master, self.args.builder + '.json')
669 if not self.Exists(path):
670 return {}
671
672 contents = json.loads(self.ReadFile(path))
673 gn_args = ' '.join(contents.get('gn_args', []))
674
675 vals = self.DefaultVals()
676 vals['gn_args'] = gn_args
dprankef37aebb92016-09-23 01:14:49677 return vals
dprankee0f486f2015-11-19 23:42:00678
dprankefe4602312015-04-08 16:20:35679 def ReadConfigFile(self):
680 if not self.Exists(self.args.config_file):
681 raise MBErr('config file not found at %s' % self.args.config_file)
682
683 try:
684 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
685 except SyntaxError as e:
686 raise MBErr('Failed to parse config file "%s": %s' %
687 (self.args.config_file, e))
688
dprankefe4602312015-04-08 16:20:35689 self.configs = contents['configs']
690 self.masters = contents['masters']
691 self.mixins = contents['mixins']
dprankefe4602312015-04-08 16:20:35692
dprankecb4a2e242016-09-19 01:13:14693 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20694 if not self.args.isolate_map_files:
695 self.args.isolate_map_files = [self.default_isolate_map]
696
697 for f in self.args.isolate_map_files:
698 if not self.Exists(f):
699 raise MBErr('isolate map file not found at %s' % f)
700 isolate_maps = {}
701 for isolate_map in self.args.isolate_map_files:
702 try:
703 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
704 duplicates = set(isolate_map).intersection(isolate_maps)
705 if duplicates:
706 raise MBErr(
707 'Duplicate targets in isolate map files: %s.' %
708 ', '.join(duplicates))
709 isolate_maps.update(isolate_map)
710 except SyntaxError as e:
711 raise MBErr(
712 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
713 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14714
dprankefe4602312015-04-08 16:20:35715 def ConfigFromArgs(self):
716 if self.args.config:
717 if self.args.master or self.args.builder:
718 raise MBErr('Can not specific both -c/--config and -m/--master or '
719 '-b/--builder')
720
721 return self.args.config
722
723 if not self.args.master or not self.args.builder:
724 raise MBErr('Must specify either -c/--config or '
725 '(-m/--master and -b/--builder)')
726
727 if not self.args.master in self.masters:
728 raise MBErr('Master name "%s" not found in "%s"' %
729 (self.args.master, self.args.config_file))
730
731 if not self.args.builder in self.masters[self.args.master]:
732 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
733 (self.args.builder, self.args.master, self.args.config_file))
734
dprankeb9380a12016-07-21 21:44:09735 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49736 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09737 if self.args.phase is None:
738 raise MBErr('Must specify a build --phase for %s on %s' %
739 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49740 phase = str(self.args.phase)
741 if phase not in config:
742 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09743 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49744 return config[phase]
dprankeb9380a12016-07-21 21:44:09745
746 if self.args.phase is not None:
747 raise MBErr('Must not specify a build --phase for %s on %s' %
748 (self.args.builder, self.args.master))
749 return config
dprankefe4602312015-04-08 16:20:35750
751 def FlattenConfig(self, config):
752 mixins = self.configs[config]
dprankef37aebb92016-09-23 01:14:49753 vals = self.DefaultVals()
dprankefe4602312015-04-08 16:20:35754
755 visited = []
756 self.FlattenMixins(mixins, vals, visited)
757 return vals
758
dprankef37aebb92016-09-23 01:14:49759 def DefaultVals(self):
760 return {
761 'args_file': '',
762 'cros_passthrough': False,
763 'gn_args': '',
dprankef37aebb92016-09-23 01:14:49764 }
765
dprankefe4602312015-04-08 16:20:35766 def FlattenMixins(self, mixins, vals, visited):
767 for m in mixins:
768 if m not in self.mixins:
769 raise MBErr('Unknown mixin "%s"' % m)
dprankeee5b51f62015-04-09 00:03:22770
dprankefe4602312015-04-08 16:20:35771 visited.append(m)
772
773 mixin_vals = self.mixins[m]
dpranke73ed0d62016-04-25 19:18:34774
775 if 'cros_passthrough' in mixin_vals:
776 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
Dirk Pranke6b99f072017-04-05 00:58:30777 if 'args_file' in mixin_vals:
778 if vals['args_file']:
779 raise MBErr('args_file specified multiple times in mixins '
780 'for %s on %s' % (self.args.builder, self.args.master))
781 vals['args_file'] = mixin_vals['args_file']
dprankefe4602312015-04-08 16:20:35782 if 'gn_args' in mixin_vals:
783 if vals['gn_args']:
784 vals['gn_args'] += ' ' + mixin_vals['gn_args']
785 else:
786 vals['gn_args'] = mixin_vals['gn_args']
dpranke73ed0d62016-04-25 19:18:34787
dprankefe4602312015-04-08 16:20:35788 if 'mixins' in mixin_vals:
789 self.FlattenMixins(mixin_vals['mixins'], vals, visited)
790 return vals
791
Takuto Ikuta9dffd7e2018-09-05 01:04:00792 def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
Dirk Prankef24e6b22018-03-27 20:12:30793 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50794
Takuto Ikuta9dffd7e2018-09-05 01:04:00795 if check:
796 cmd = self.GNCmd('gen', build_dir, '--check')
797 else:
798 cmd = self.GNCmd('gen', build_dir)
dprankeeca4a782016-04-14 01:42:38799 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:09800 if compute_inputs_for_analyze:
801 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:38802
803 # Since GN hasn't run yet, the build directory may not even exist.
804 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
805
806 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:54807 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:39808
dpranke751516a2015-10-03 01:11:34809 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:39810 # We need GN to generate the list of runtime dependencies for
811 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:14812 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:39813 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:00814 path = self.args.swarming_targets_file
815 if not self.Exists(path):
816 self.WriteFailureAndRaise('"%s" does not exist' % path,
817 output_path=None)
818 contents = self.ReadFile(path)
Erik Chen42df41d2018-08-21 17:13:31819 isolate_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:00820
dprankecb4a2e242016-09-19 01:13:14821 isolate_map = self.ReadIsolateMap()
Dirk Pranke7a7e9b62019-02-17 01:46:25822 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
823 isolate_map, build_dir)
824
Erik Chen42df41d2018-08-21 17:13:31825 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:00826 if err:
Dirk Pranke7a7e9b62019-02-17 01:46:25827 raise MBErr(err)
dpranke74559b52015-06-10 21:20:39828
dpranke751516a2015-10-03 01:11:34829 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:14830 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:39831 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
832
Debrian Figueroaae582232019-07-17 01:54:45833 ret, output, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:40834 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:11835 if self.args.json_output:
Debrian Figueroaae582232019-07-17 01:54:45836 # write errors to json.output
837 self.WriteJSON({'output': output}, self.args.json_output)
Dirk Pranke7a7e9b62019-02-17 01:46:25838 # If `gn gen` failed, we should exit early rather than trying to
839 # generate isolates. Run() will have already logged any error output.
840 self.Print('GN gen failed: %d' % ret)
841 return ret
dpranke74559b52015-06-10 21:20:39842
Erik Chen42df41d2018-08-21 17:13:31843 if getattr(self.args, 'swarming_targets_file', None):
Dirk Pranke7a7e9b62019-02-17 01:46:25844 self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
Erik Chen42df41d2018-08-21 17:13:31845
846 return 0
847
848 def RunGNGenAllIsolates(self, vals):
849 """
850 This command generates all .isolate files.
851
852 This command assumes that "mb.py gen" has already been run, as it relies on
853 "gn ls" to fetch all gn targets. If uses that output, combined with the
854 isolate_map, to determine all isolates that can be generated for the current
855 gn configuration.
856 """
857 build_dir = self.args.path
858 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
859 force_verbose=False)
860 if ret:
861 # If `gn ls` failed, we should exit early rather than trying to
862 # generate isolates.
863 self.Print('GN ls failed: %d' % ret)
864 return ret
865
866 # Create a reverse map from isolate label to isolate dict.
867 isolate_map = self.ReadIsolateMap()
868 isolate_dict_map = {}
869 for key, isolate_dict in isolate_map.iteritems():
870 isolate_dict_map[isolate_dict['label']] = isolate_dict
871 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
872
873 runtime_deps = []
874
875 isolate_targets = []
876 # For every GN target, look up the isolate dict.
877 for line in output.splitlines():
878 target = line.strip()
879 if target in isolate_dict_map:
880 if isolate_dict_map[target]['type'] == 'additional_compile_target':
881 # By definition, additional_compile_targets are not tests, so we
882 # shouldn't generate isolates for them.
883 continue
884
885 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
886 runtime_deps.append(target)
887
Dirk Pranke7a7e9b62019-02-17 01:46:25888 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
889 isolate_map, build_dir)
890
Erik Chen42df41d2018-08-21 17:13:31891 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
892 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
893 cmd = self.GNCmd('gen', build_dir)
894 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
895 self.Run(cmd)
896
897 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
898
Dirk Pranke7a7e9b62019-02-17 01:46:25899 def RemovePossiblyStaleRuntimeDepsFiles(self, vals, targets, isolate_map,
900 build_dir):
901 # TODO(crbug.com/932700): Because `gn gen --runtime-deps-list-file`
902 # puts the runtime_deps file in different locations based on the actual
903 # type of a target, we may end up with multiple possible runtime_deps
904 # files in a given build directory, where some of the entries might be
905 # stale (since we might be reusing an existing build directory).
906 #
907 # We need to be able to get the right one reliably; you might think
908 # we can just pick the newest file, but because GN won't update timestamps
909 # if the contents of the files change, an older runtime_deps
910 # file might actually be the one we should use over a newer one (see
911 # crbug.com/932387 for a more complete explanation and example).
912 #
913 # In order to avoid this, we need to delete any possible runtime_deps
914 # files *prior* to running GN. As long as the files aren't actually
915 # needed during the build, this hopefully will not cause unnecessary
916 # build work, and so it should be safe.
917 #
918 # Ultimately, we should just make sure we get the runtime_deps files
919 # in predictable locations so we don't have this issue at all, and
920 # that's what crbug.com/932700 is for.
921 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, targets, isolate_map)
922 for rpaths in possible_rpaths.values():
923 for rpath in rpaths:
924 path = self.ToAbsPath(build_dir, rpath)
925 if self.Exists(path):
926 self.RemoveFile(path)
927
Erik Chen42df41d2018-08-21 17:13:31928 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
929 """
930 Generates isolates for a list of ninja targets.
931
932 Ninja targets are transformed to GN targets via isolate_map.
933
934 This function assumes that a previous invocation of "mb.py gen" has
935 generated runtime deps for all targets.
936 """
Dirk Pranke7a7e9b62019-02-17 01:46:25937 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, ninja_targets,
938 isolate_map)
939
940 for target, rpaths in possible_rpaths.items():
941 # TODO(crbug.com/932700): We don't know where each .runtime_deps
942 # file might be, but assuming we called
943 # RemovePossiblyStaleRuntimeDepsFiles prior to calling `gn gen`,
944 # there should only be one file.
945 found_one = False
946 path_to_use = None
947 for r in rpaths:
948 path = self.ToAbsPath(build_dir, r)
949 if self.Exists(path):
950 if found_one:
951 raise MBErr('Found more than one of %s' % ', '.join(rpaths))
952 path_to_use = path
953 found_one = True
954
955 if not found_one:
956 raise MBErr('Did not find any of %s' % ', '.join(rpaths))
957
958 command, extra_files = self.GetIsolateCommand(target, vals)
959 runtime_deps = self.ReadFile(path_to_use).splitlines()
960
961 canonical_target = target.replace(':','_').replace('/','_')
962 self.WriteIsolateFiles(build_dir, command, canonical_target, runtime_deps,
963 extra_files)
964
965 def PossibleRuntimeDepsPaths(self, vals, ninja_targets, isolate_map):
966 """Returns a map of targets to possible .runtime_deps paths.
967
968 Each ninja target maps on to a GN label, but depending on the type
969 of the GN target, `gn gen --runtime-deps-list-file` will write
970 the .runtime_deps files into different locations. Unfortunately, in
971 some cases we don't actually know which of multiple locations will
972 actually be used, so we return all plausible candidates.
973
974 The paths that are returned are relative to the build directory.
975 """
976
jbudoricke3c4f95e2016-04-28 23:17:38977 android = 'target_os="android"' in vals['gn_args']
Dirk Pranke26de05aec2019-04-03 19:18:38978 ios = 'target_os="ios"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:42979 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:30980 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Dirk Pranke7a7e9b62019-02-17 01:46:25981 possible_runtime_deps_rpaths = {}
Erik Chen42df41d2018-08-21 17:13:31982 for target in ninja_targets:
John Budorick39f14962019-04-11 23:03:20983 target_type = isolate_map[target]['type']
984 label = isolate_map[target]['label']
985 stamp_runtime_deps = 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')
Erik Chen42df41d2018-08-21 17:13:31986 # TODO(https://crbug.com/876065): 'official_tests' use
987 # type='additional_compile_target' to isolate tests. This is not the
988 # intended use for 'additional_compile_target'.
John Budorick39f14962019-04-11 23:03:20989 if (target_type == 'additional_compile_target' and
Erik Chen42df41d2018-08-21 17:13:31990 target != 'official_tests'):
991 # By definition, additional_compile_targets are not tests, so we
992 # shouldn't generate isolates for them.
Dirk Pranke7a7e9b62019-02-17 01:46:25993 raise MBErr('Cannot generate isolate for %s since it is an '
994 'additional_compile_target.' % target)
John Budorick39f14962019-04-11 23:03:20995 elif fuchsia or ios or target_type == 'generated_script':
996 # iOS and Fuchsia targets end up as groups.
997 # generated_script targets are always actions.
998 rpaths = [stamp_runtime_deps]
Erik Chen42df41d2018-08-21 17:13:31999 elif android:
jbudoricke3c4f95e2016-04-28 23:17:381000 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:021001 # will result in runtime_deps associated with the stamp file, while the
1002 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:441003 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:251004 rpaths = [
dprankecb4a2e242016-09-19 01:13:141005 target + '.runtime_deps',
John Budorick39f14962019-04-11 23:03:201006 stamp_runtime_deps]
1007 elif (target_type == 'script' or
1008 target_type == 'fuzzer' or
dprankecb4a2e242016-09-19 01:13:141009 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:111010 # For script targets, the build target is usually a group,
1011 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:491012 # for the label, which lives under the obj/ directory, but it may
1013 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:441014 label = isolate_map[target]['label']
John Budorick39f14962019-04-11 23:03:201015 rpaths = [stamp_runtime_deps]
Nico Weberd94b71a2018-02-22 22:00:301016 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:251017 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:491018 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251019 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:301020 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:251021 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:521022 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251023 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:021024
Dirk Pranke7a7e9b62019-02-17 01:46:251025 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:411026
Dirk Pranke7a7e9b62019-02-17 01:46:251027 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:341028
1029 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:301030 target = self.args.target
dprankecb4a2e242016-09-19 01:13:141031 isolate_map = self.ReadIsolateMap()
1032 err, labels = self.MapTargetsToLabels(isolate_map, [target])
1033 if err:
1034 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:251035
dprankecb4a2e242016-09-19 01:13:141036 label = labels[0]
dpranke751516a2015-10-03 01:11:341037
Dirk Prankef24e6b22018-03-27 20:12:301038 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:141039 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:341040
dprankeeca4a782016-04-14 01:42:381041 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:201042 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:341043 if ret:
dpranke030d7a6d2016-03-26 17:23:501044 if out:
1045 self.Print(out)
dpranke751516a2015-10-03 01:11:341046 return ret
1047
1048 runtime_deps = out.splitlines()
1049
1050 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
1051 extra_files)
1052
1053 ret, _, _ = self.Run([
1054 self.executable,
1055 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1056 'check',
1057 '-i',
1058 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1059 '-s',
1060 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1061 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301062
dprankefe4602312015-04-08 16:20:351063 return ret
1064
dpranke751516a2015-10-03 01:11:341065 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps,
1066 extra_files):
1067 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
1068 self.WriteFile(isolate_path,
1069 pprint.pformat({
1070 'variables': {
1071 'command': command,
Dirk Pranke8edeb682019-06-11 16:24:051072 'files': sorted(set(runtime_deps + extra_files)),
dpranke751516a2015-10-03 01:11:341073 }
1074 }) + '\n')
1075
1076 self.WriteJSON(
1077 {
1078 'args': [
1079 '--isolated',
1080 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1081 '--isolate',
1082 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1083 ],
1084 'dir': self.chromium_src_dir,
1085 'version': 1,
1086 },
1087 isolate_path + 'd.gen.json',
1088 )
1089
dprankecb4a2e242016-09-19 01:13:141090 def MapTargetsToLabels(self, isolate_map, targets):
1091 labels = []
1092 err = ''
1093
dprankecb4a2e242016-09-19 01:13:141094 for target in targets:
1095 if target == 'all':
1096 labels.append(target)
1097 elif target.startswith('//'):
1098 labels.append(target)
1099 else:
1100 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421101 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141102 err += ('test target "%s" type is unknown\n' % target)
1103 else:
thakis024d6f32017-05-16 23:21:421104 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141105 else:
1106 err += ('target "%s" not found in '
1107 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1108
1109 return err, labels
1110
dprankeeca4a782016-04-14 01:42:381111 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461112 if self.platform == 'linux2':
1113 subdir, exe = 'linux64', 'gn'
1114 elif self.platform == 'darwin':
1115 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251116 elif self.platform == 'aix6':
1117 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461118 else:
1119 subdir, exe = 'win', 'gn.exe'
1120
1121 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081122 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521123
dprankecb4a2e242016-09-19 01:13:141124
Garrett Beatyb6cee042019-04-22 18:42:091125 def GNArgs(self, vals, expand_imports=False):
dpranke73ed0d62016-04-25 19:18:341126 if vals['cros_passthrough']:
1127 if not 'GN_ARGS' in os.environ:
1128 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1129 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161130 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301131 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341132 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351133 if vals['gn_args']:
1134 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341135 else:
1136 gn_args = vals['gn_args']
1137
dpranked0c138b2016-04-13 18:28:471138 if self.args.goma_dir:
1139 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381140
agrieve41d21a72016-04-14 18:02:261141 android_version_code = self.args.android_version_code
1142 if android_version_code:
1143 gn_args += ' android_default_version_code="%s"' % android_version_code
1144
1145 android_version_name = self.args.android_version_name
1146 if android_version_name:
1147 gn_args += ' android_default_version_name="%s"' % android_version_name
1148
Garrett Beatyb6cee042019-04-22 18:42:091149 args_gn_lines = []
1150 parsed_gn_args = {}
dprankeeca4a782016-04-14 01:42:381151
Ben Pastene65ccf6132018-11-08 00:47:591152 # If we're using the Simple Chrome SDK, add a comment at the top that
1153 # points to the doc. This must happen after the gn_helpers.ToGNString()
1154 # call above since gn_helpers strips comments.
1155 if vals['cros_passthrough']:
Garrett Beatyb6cee042019-04-22 18:42:091156 args_gn_lines.extend([
Ben Pastene65ccf6132018-11-08 00:47:591157 '# These args are generated via the Simple Chrome SDK. See the link',
1158 '# below for more details:',
1159 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
Garrett Beatyb6cee042019-04-22 18:42:091160 ])
Ben Pastene65ccf6132018-11-08 00:47:591161
dpranke9dd5e252016-04-14 04:23:091162 args_file = vals.get('args_file', None)
1163 if args_file:
Garrett Beatyb6cee042019-04-22 18:42:091164 if expand_imports:
1165 content = self.ReadFile(self.ToAbsPath(args_file))
1166 parsed_gn_args = gn_helpers.FromGNArgs(content)
1167 else:
1168 args_gn_lines.append('import("%s")' % args_file)
1169
1170 # Canonicalize the arg string into a sorted, newline-separated list
1171 # of key-value pairs, and de-dup the keys if need be so that only
1172 # the last instance of each arg is listed.
1173 parsed_gn_args.update(gn_helpers.FromGNArgs(gn_args))
1174 args_gn_lines.append(gn_helpers.ToGNString(parsed_gn_args))
1175
1176 return '\n'.join(args_gn_lines)
dprankefe4602312015-04-08 16:20:351177
dprankecb4a2e242016-09-19 01:13:141178 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071179 isolate_map = self.ReadIsolateMap()
1180
Scott Graham3be4b4162017-09-12 00:41:411181 is_android = 'target_os="android"' in vals['gn_args']
1182 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Caleb Raittof983d102019-06-21 23:05:021183 is_cros = 'target_os="chromeos"' in vals['gn_args']
Nico Webera7bc1cb2019-06-15 17:42:391184 is_simplechrome = vals.get('cros_passthrough', False)
1185 is_mac = self.platform == 'darwin'
Nico Weberd94b71a2018-02-22 22:00:301186 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061187
kylechar39705682017-01-19 14:37:231188 # This should be true if tests with type='windowed_test_launcher' are
1189 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081190 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1191 # backends. Currently, tests are executed for the headless and X11 backends
1192 # and both can run under Xvfb.
1193 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1194 # backend.
Scott Graham3be4b4162017-09-12 00:41:411195 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251196
1197 asan = 'is_asan=true' in vals['gn_args']
1198 msan = 'is_msan=true' in vals['gn_args']
1199 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411200 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
Yun Liu7cef1072019-06-27 21:22:191201 java_coverage = 'jacoco_coverage=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251202
dprankecb4a2e242016-09-19 01:13:141203 test_type = isolate_map[target]['type']
dprankefe0d35e2016-02-05 02:43:591204
dprankecb4a2e242016-09-19 01:13:141205 executable = isolate_map[target].get('executable', target)
bsheedy9c16ed62019-04-10 20:32:111206 executable_suffix = isolate_map[target].get(
1207 'executable_suffix', '.exe' if is_win else '')
dprankefe0d35e2016-02-05 02:43:591208
dprankea55584f12015-07-22 00:52:471209 cmdline = []
Andrii Shyshkalovc158e0102018-01-10 05:52:001210 extra_files = [
1211 '../../.vpython',
1212 '../../testing/test_env.py',
1213 ]
dpranked8113582015-06-05 20:08:251214
dprankecb4a2e242016-09-19 01:13:141215 if test_type == 'nontest':
1216 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1217 output_path=None)
1218
John Budorick93e88ac82019-04-12 18:39:111219 if test_type == 'generated_script':
Ben Pastenecb0fb412019-06-11 02:31:541220 script = isolate_map[target]['script']
1221 if self.platform == 'win32':
1222 script += '.bat'
John Budorick93e88ac82019-04-12 18:39:111223 cmdline = [
1224 '../../testing/test_env.py',
Ben Pastenecb0fb412019-06-11 02:31:541225 script,
John Budorick93e88ac82019-04-12 18:39:111226 ]
1227 elif test_type == 'fuzzer':
Roberto Carrillo1460da852018-12-14 17:10:391228 cmdline = [
1229 '../../testing/test_env.py',
1230 '../../tools/code_coverage/run_fuzz_target.py',
1231 '--fuzzer', './' + target,
1232 '--output-dir', '${ISOLATED_OUTDIR}',
1233 '--timeout', '3600']
1234 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011235 cmdline = []
1236 if asan:
John Budorick31cdce62019-04-03 20:56:111237 cmdline += [os.path.join('bin', 'run_with_asan'), '--']
John Budorick8c4203042019-03-19 17:22:011238 cmdline += [
John Budorickfb97a852017-12-20 20:10:191239 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041240 '../../build/android/test_wrapper/logdog_wrapper.py',
1241 '--target', target,
hzl9ae14452017-04-04 23:38:021242 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481243 '--store-tombstones']
Yun Liu7cef1072019-06-27 21:22:191244 if java_coverage:
1245 cmdline += ['--coverage-dir', '${ISOLATED_OUTDIR}']
Scott Graham3be4b4162017-09-12 00:41:411246 elif is_fuchsia and test_type != 'script':
John Budorickfb97a852017-12-20 20:10:191247 cmdline = [
1248 '../../testing/test_env.py',
1249 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441250 '--test-launcher-bot-mode',
Sergey Ulanovd851243b2019-06-25 00:33:471251 '--system-log-file', '${ISOLATED_OUTDIR}/system_log'
John Budorickfb97a852017-12-20 20:10:191252 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321253 elif is_simplechrome and test_type != 'script':
1254 cmdline = [
1255 '../../testing/test_env.py',
1256 os.path.join('bin', 'run_%s' % target),
1257 ]
kylechar39705682017-01-19 14:37:231258 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001259 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471260 cmdline = [
Nico Webera7bc1cb2019-06-15 17:42:391261 '../../testing/xvfb.py',
1262 './' + str(executable) + executable_suffix,
1263 '--test-launcher-bot-mode',
1264 '--asan=%d' % asan,
1265 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1266 # supported.
1267 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021268 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1269 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
Nico Webera7bc1cb2019-06-15 17:42:391270 '--msan=%d' % msan,
1271 '--tsan=%d' % tsan,
1272 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471273 ]
1274 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471275 cmdline = [
1276 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591277 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251278 '--test-launcher-bot-mode',
1279 '--asan=%d' % asan,
Caleb Raitto1fb2cc9e2019-06-14 01:04:231280 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1281 # supported.
Nico Webera7bc1cb2019-06-15 17:42:391282 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021283 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1284 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
dpranked8113582015-06-05 20:08:251285 '--msan=%d' % msan,
1286 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411287 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471288 ]
dpranke6abd8652015-08-28 03:21:111289 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241290 cmdline = []
Ben Pastene4534c39e2019-07-08 22:55:341291 # If we're testing a CrOS simplechrome build, assume we need to prepare a
1292 # DUT for testing. So prepend the command to run with the test wrapper.
Ben Pastene8ab6954d2018-05-04 04:08:241293 if is_simplechrome:
Ben Pastene4534c39e2019-07-08 22:55:341294 cmdline = [os.path.join('bin', 'cros_test_wrapper')]
Ben Pastene8ab6954d2018-05-04 04:08:241295 cmdline += [
dpranke6abd8652015-08-28 03:21:111296 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141297 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591298 ]
Dirk Prankef24e6b22018-03-27 20:12:301299 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471300 cmdline = [
1301 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591302 ]
dprankea55584f12015-07-22 00:52:471303 else:
1304 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1305 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251306
Abhishek Arya2f5f7342018-06-13 16:59:441307 if is_win and asan:
Alexander Dunaev384ba482018-03-21 17:56:121308 # Sandbox is not yet supported by ASAN for Windows.
1309 # Perhaps this is only needed for tests that use the sandbox?
1310 cmdline.append('--no-sandbox')
1311
dprankecb4a2e242016-09-19 01:13:141312 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591313
dpranked8113582015-06-05 20:08:251314 return cmdline, extra_files
1315
dpranke74559b52015-06-10 21:20:391316 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331317 return self.PathJoin(self.chromium_src_dir,
1318 self.ToSrcRelPath(build_path),
1319 *comps)
dpranked8113582015-06-05 20:08:251320
dprankeee5b51f62015-04-09 00:03:221321 def ToSrcRelPath(self, path):
1322 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501323 if path.startswith('//'):
1324 return path[2:].replace('/', self.sep)
1325 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351326
Dirk Pranke0fd41bcd2015-06-19 00:05:501327 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141328 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501329 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001330 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501331 if ret:
1332 return ret
1333
Dirk Prankef24e6b22018-03-27 20:12:301334 build_path = self.args.path
1335 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141336 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301337 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141338 gn_output_path = output_path + '.gn'
1339
dpranke7837fc362015-11-19 03:54:161340 inp = self.ReadInputJSON(['files', 'test_targets',
1341 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321342 if self.args.verbose:
1343 self.Print()
1344 self.Print('analyze input:')
1345 self.PrintJSON(inp)
1346 self.Print()
1347
dpranke76734662015-04-16 02:17:501348
dpranke7c5f614d2015-07-22 23:43:391349 # This shouldn't normally happen, but could due to unusual race conditions,
1350 # like a try job that gets scheduled before a patch lands but runs after
1351 # the patch has landed.
1352 if not inp['files']:
1353 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161354 self.WriteJSON({
1355 'status': 'No dependency',
1356 'compile_targets': [],
1357 'test_targets': [],
1358 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391359 return 0
1360
dprankecb4a2e242016-09-19 01:13:141361 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161362 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561363
dprankecb4a2e242016-09-19 01:13:141364 isolate_map = self.ReadIsolateMap()
1365 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1366 isolate_map, inp['additional_compile_targets'])
1367 if err:
1368 raise MBErr(err)
1369
1370 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1371 isolate_map, inp['test_targets'])
1372 if err:
1373 raise MBErr(err)
1374 labels_to_targets = {}
1375 for i, label in enumerate(gn_inp['test_targets']):
1376 labels_to_targets[label] = inp['test_targets'][i]
1377
dprankef61de2f2015-05-14 04:09:561378 try:
dprankecb4a2e242016-09-19 01:13:141379 self.WriteJSON(gn_inp, gn_input_path)
1380 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
Debrian Figueroaae51d0d2019-07-22 18:04:111381 ret, output, _ = self.Run(cmd, force_verbose=True)
dprankecb4a2e242016-09-19 01:13:141382 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:111383 if self.args.json_output:
1384 # write errors to json.output
1385 self.WriteJSON({'output': output}, self.args.json_output)
dprankecb4a2e242016-09-19 01:13:141386 return ret
dpranke067d0142015-05-14 22:52:451387
dprankecb4a2e242016-09-19 01:13:141388 gn_outp_str = self.ReadFile(gn_output_path)
1389 try:
1390 gn_outp = json.loads(gn_outp_str)
1391 except Exception as e:
1392 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1393 % (repr(gn_outp_str), str(e)))
1394 raise
1395
1396 outp = {}
1397 if 'status' in gn_outp:
1398 outp['status'] = gn_outp['status']
1399 if 'error' in gn_outp:
1400 outp['error'] = gn_outp['error']
1401 if 'invalid_targets' in gn_outp:
1402 outp['invalid_targets'] = gn_outp['invalid_targets']
1403 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491404 all_input_compile_targets = sorted(
1405 set(inp['test_targets'] + inp['additional_compile_targets']))
1406
1407 # If we're building 'all', we can throw away the rest of the targets
1408 # since they're redundant.
dpranke385a3102016-09-20 22:04:081409 if 'all' in gn_outp['compile_targets']:
1410 outp['compile_targets'] = ['all']
1411 else:
Dirk Pranke45165072017-11-08 04:57:491412 outp['compile_targets'] = gn_outp['compile_targets']
1413
1414 # crbug.com/736215: When GN returns targets back, for targets in
1415 # the default toolchain, GN will have generated a phony ninja
1416 # target matching the label, and so we can safely (and easily)
1417 # transform any GN label into the matching ninja target. For
1418 # targets in other toolchains, though, GN doesn't generate the
1419 # phony targets, and we don't know how to turn the labels into
1420 # compile targets. In this case, we also conservatively give up
1421 # and build everything. Probably the right thing to do here is
1422 # to have GN return the compile targets directly.
1423 if any("(" in target for target in outp['compile_targets']):
1424 self.Print('WARNING: targets with non-default toolchains were '
1425 'found, building everything instead.')
1426 outp['compile_targets'] = all_input_compile_targets
1427 else:
dpranke385a3102016-09-20 22:04:081428 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491429 label.replace('//', '') for label in outp['compile_targets']]
1430
1431 # Windows has a maximum command line length of 8k; even Linux
1432 # maxes out at 128k; if analyze returns a *really long* list of
1433 # targets, we just give up and conservatively build everything instead.
1434 # Probably the right thing here is for ninja to support response
1435 # files as input on the command line
1436 # (see https://github.com/ninja-build/ninja/issues/1355).
1437 if len(' '.join(outp['compile_targets'])) > 7*1024:
1438 self.Print('WARNING: Too many compile targets were affected.')
1439 self.Print('WARNING: Building everything instead to avoid '
1440 'command-line length issues.')
1441 outp['compile_targets'] = all_input_compile_targets
1442
1443
dprankecb4a2e242016-09-19 01:13:141444 if 'test_targets' in gn_outp:
1445 outp['test_targets'] = [
1446 labels_to_targets[label] for label in gn_outp['test_targets']]
1447
1448 if self.args.verbose:
1449 self.Print()
1450 self.Print('analyze output:')
1451 self.PrintJSON(outp)
1452 self.Print()
1453
1454 self.WriteJSON(outp, output_path)
1455
dprankef61de2f2015-05-14 04:09:561456 finally:
dprankecb4a2e242016-09-19 01:13:141457 if self.Exists(gn_input_path):
1458 self.RemoveFile(gn_input_path)
1459 if self.Exists(gn_output_path):
1460 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351461
1462 return 0
1463
dpranked8113582015-06-05 20:08:251464 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301465 path = self.args.input_path
1466 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351467 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321468 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351469
1470 try:
1471 inp = json.loads(self.ReadFile(path))
1472 except Exception as e:
1473 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321474 (path, e), output_path)
dpranked8113582015-06-05 20:08:251475
1476 for k in required_keys:
1477 if not k in inp:
1478 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1479 output_path)
dprankefe4602312015-04-08 16:20:351480
1481 return inp
1482
dpranked5b2b9432015-06-23 16:55:301483 def WriteFailureAndRaise(self, msg, output_path):
1484 if output_path:
dprankee0547cd2015-09-15 01:27:401485 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351486 raise MBErr(msg)
1487
dprankee0547cd2015-09-15 01:27:401488 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321489 try:
dprankee0547cd2015-09-15 01:27:401490 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1491 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321492 except Exception as e:
1493 raise MBErr('Error %s writing to the output path "%s"' %
1494 (e, path))
dprankefe4602312015-04-08 16:20:351495
aneeshmde50f472016-04-01 01:13:101496 def CheckCompile(self, master, builder):
1497 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1498 url = urllib2.quote(url_template.format(master=master, builder=builder),
1499 safe=':/()?=')
1500 try:
1501 builds = json.loads(self.Fetch(url))
1502 except Exception as e:
1503 return str(e)
1504 successes = sorted(
1505 [int(x) for x in builds.keys() if "text" in builds[x] and
1506 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1507 reverse=True)
1508 if not successes:
1509 return "no successful builds"
1510 build = builds[str(successes[0])]
1511 step_names = set([step["name"] for step in build["steps"]])
1512 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1513 if compile_indicators & step_names:
1514 return "compiles"
1515 return "does not compile"
1516
dpranke3cec199c2015-09-22 23:29:021517 def PrintCmd(self, cmd, env):
1518 if self.platform == 'win32':
1519 env_prefix = 'set '
1520 env_quoter = QuoteForSet
1521 shell_quoter = QuoteForCmd
1522 else:
1523 env_prefix = ''
1524 env_quoter = pipes.quote
1525 shell_quoter = pipes.quote
1526
1527 def print_env(var):
1528 if env and var in env:
1529 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1530
dprankeec079262016-06-07 02:21:201531 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021532
dpranke8c2cfd32015-09-17 20:12:331533 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351534 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021535 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351536
dprankecda00332015-04-11 04:18:321537 def PrintJSON(self, obj):
1538 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1539
dpranke751516a2015-10-03 01:11:341540 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301541 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381542 if self.platform == 'win32':
1543 # On Windows use the batch script since there is no exe
1544 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1545 else:
1546 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341547 if self.args.jobs:
1548 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1549 ninja_cmd.append(target)
Dirk Pranke5f22a822019-05-23 22:55:251550 ret, _, _ = self.Run(ninja_cmd, buffer_output=False)
dpranke751516a2015-10-03 01:11:341551 return ret
1552
1553 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351554 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401555 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021556 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351557 if self.args.dryrun:
1558 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401559
dpranke751516a2015-10-03 01:11:341560 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401561 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341562 if ret:
1563 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351564 if out:
Debrian Figueroaae582232019-07-17 01:54:451565 # This is the error seen on the logs
dprankeee5b51f62015-04-09 00:03:221566 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351567 if err:
dprankeee5b51f62015-04-09 00:03:221568 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351569 return ret, out, err
1570
dpranke751516a2015-10-03 01:11:341571 def Call(self, cmd, env=None, buffer_output=True):
1572 if buffer_output:
1573 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1574 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1575 env=env)
1576 out, err = p.communicate()
1577 else:
1578 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1579 env=env)
1580 p.wait()
1581 out = err = ''
dprankefe4602312015-04-08 16:20:351582 return p.returncode, out, err
1583
1584 def ExpandUser(self, path):
1585 # This function largely exists so it can be overridden for testing.
1586 return os.path.expanduser(path)
1587
1588 def Exists(self, path):
1589 # This function largely exists so it can be overridden for testing.
1590 return os.path.exists(path)
1591
dpranke867bcf4a2016-03-14 22:28:321592 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501593 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321594 f = urllib2.urlopen(url)
1595 contents = f.read()
1596 f.close()
1597 return contents
1598
dprankec3441d12015-06-23 23:01:351599 def MaybeMakeDirectory(self, path):
1600 try:
1601 os.makedirs(path)
1602 except OSError, e:
1603 if e.errno != errno.EEXIST:
1604 raise
1605
dpranke8c2cfd32015-09-17 20:12:331606 def PathJoin(self, *comps):
1607 # This function largely exists so it can be overriden for testing.
1608 return os.path.join(*comps)
1609
dpranke030d7a6d2016-03-26 17:23:501610 def Print(self, *args, **kwargs):
1611 # This function largely exists so it can be overridden for testing.
1612 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101613 if kwargs.get('stream', sys.stdout) == sys.stdout:
1614 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501615
dprankefe4602312015-04-08 16:20:351616 def ReadFile(self, path):
1617 # This function largely exists so it can be overriden for testing.
1618 with open(path) as fp:
1619 return fp.read()
1620
dpranke030d7a6d2016-03-26 17:23:501621 def RelPath(self, path, start='.'):
1622 # This function largely exists so it can be overriden for testing.
1623 return os.path.relpath(path, start)
1624
dprankef61de2f2015-05-14 04:09:561625 def RemoveFile(self, path):
1626 # This function largely exists so it can be overriden for testing.
1627 os.remove(path)
1628
dprankec161aa92015-09-14 20:21:131629 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331630 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131631 # In other places in chromium, we often have to retry this command
1632 # because we're worried about other processes still holding on to
1633 # file handles, but when MB is invoked, it will be early enough in the
1634 # build that their should be no other processes to interfere. We
1635 # can change this if need be.
1636 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1637 else:
1638 shutil.rmtree(abs_path, ignore_errors=True)
1639
Dirk Prankef24e6b22018-03-27 20:12:301640 def TempDir(self):
1641 # This function largely exists so it can be overriden for testing.
1642 return tempfile.mkdtemp(prefix='mb_')
1643
dprankef61de2f2015-05-14 04:09:561644 def TempFile(self, mode='w'):
1645 # This function largely exists so it can be overriden for testing.
1646 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1647
dprankee0547cd2015-09-15 01:27:401648 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351649 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401650 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301651 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351652 with open(path, 'w') as fp:
1653 return fp.write(contents)
1654
dprankef61de2f2015-05-14 04:09:561655
dprankefe4602312015-04-08 16:20:351656class MBErr(Exception):
1657 pass
1658
1659
dpranke3cec199c2015-09-22 23:29:021660# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1661# details of this next section, which handles escaping command lines
1662# so that they can be copied and pasted into a cmd window.
1663UNSAFE_FOR_SET = set('^<>&|')
1664UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1665ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1666
1667
1668def QuoteForSet(arg):
1669 if any(a in UNSAFE_FOR_SET for a in arg):
1670 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1671 return arg
1672
1673
1674def QuoteForCmd(arg):
1675 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021676 if arg == '' or ' ' in arg or '"' in arg:
1677 quote_re = re.compile(r'(\\*)"')
1678 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1679
1680 # Then check to see if the arg contains any metacharacters other than
1681 # double quotes; if it does, quote everything (including the double
1682 # quotes) for safety.
1683 if any(a in UNSAFE_FOR_CMD for a in arg):
1684 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1685 return arg
1686
1687
dprankefe4602312015-04-08 16:20:351688if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591689 sys.exit(main(sys.argv[1:]))