blob: 1d1c2e12b015ff535bd08ee8db454ea9dc7215fe [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
Stephen Martinisb40a6852019-07-23 01:48:30111 parser = argparse.ArgumentParser(
112 prog='mb', description='mb (meta-build) is a python wrapper around GN. '
113 'See the user guide in '
114 '//tools/mb/docs/user_guide.md for detailed usage '
115 'instructions.')
116
dprankefe4602312015-04-08 16:20:35117 subps = parser.add_subparsers()
118
119 subp = subps.add_parser('analyze',
Stephen Martinis239c35a2019-07-22 19:34:40120 description='Analyze whether changes to a set of '
121 'files will cause a set of binaries to '
122 'be rebuilt.')
dprankefe4602312015-04-08 16:20:35123 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30124 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35125 help='path build was generated into.')
Dirk Prankef24e6b22018-03-27 20:12:30126 subp.add_argument('input_path',
dprankefe4602312015-04-08 16:20:35127 help='path to a file containing the input arguments '
128 'as a JSON object.')
Dirk Prankef24e6b22018-03-27 20:12:30129 subp.add_argument('output_path',
dprankefe4602312015-04-08 16:20:35130 help='path to a file containing the output arguments '
131 'as a JSON object.')
Debrian Figueroaae51d0d2019-07-22 18:04:11132 subp.add_argument('--json-output',
Debrian Figueroaae582232019-07-17 01:54:45133 help='Write errors to json.output')
dprankefe4602312015-04-08 16:20:35134 subp.set_defaults(func=self.CmdAnalyze)
135
dprankef37aebb92016-09-23 01:14:49136 subp = subps.add_parser('export',
Stephen Martinis239c35a2019-07-22 19:34:40137 description='Print out the expanded configuration '
138 'for each builder as a JSON object.')
dprankef37aebb92016-09-23 01:14:49139 subp.add_argument('-f', '--config-file', metavar='PATH',
140 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50141 help='path to config file (default is %(default)s)')
dprankef37aebb92016-09-23 01:14:49142 subp.add_argument('-g', '--goma-dir',
143 help='path to goma directory')
144 subp.set_defaults(func=self.CmdExport)
145
dprankefe4602312015-04-08 16:20:35146 subp = subps.add_parser('gen',
Stephen Martinis239c35a2019-07-22 19:34:40147 description='Generate a new set of build files.')
dprankefe4602312015-04-08 16:20:35148 AddCommonOptions(subp)
dpranke74559b52015-06-10 21:20:39149 subp.add_argument('--swarming-targets-file',
150 help='save runtime dependencies for targets listed '
151 'in file.')
Debrian Figueroaae51d0d2019-07-22 18:04:11152 subp.add_argument('--json-output',
Debrian Figueroaae582232019-07-17 01:54:45153 help='Write errors to json.output')
Dirk Prankef24e6b22018-03-27 20:12:30154 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35155 help='path to generate build into')
156 subp.set_defaults(func=self.CmdGen)
157
Erik Chen42df41d2018-08-21 17:13:31158 subp = subps.add_parser('isolate-everything',
Stephen Martinis239c35a2019-07-22 19:34:40159 description='Generates a .isolate for all targets. '
160 'Requires that mb.py gen has already '
161 'been run.')
Erik Chen42df41d2018-08-21 17:13:31162 AddCommonOptions(subp)
163 subp.set_defaults(func=self.CmdIsolateEverything)
164 subp.add_argument('path',
165 help='path build was generated into')
166
dpranke751516a2015-10-03 01:11:34167 subp = subps.add_parser('isolate',
Stephen Martinis239c35a2019-07-22 19:34:40168 description='Generate the .isolate files for a '
169 'given binary.')
dpranke751516a2015-10-03 01:11:34170 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30171 subp.add_argument('--no-build', dest='build', default=True,
172 action='store_false',
173 help='Do not build, just isolate')
174 subp.add_argument('-j', '--jobs', type=int,
175 help='Number of jobs to pass to ninja')
176 subp.add_argument('path',
dpranke751516a2015-10-03 01:11:34177 help='path build was generated into')
Dirk Prankef24e6b22018-03-27 20:12:30178 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34179 help='ninja target to generate the isolate for')
180 subp.set_defaults(func=self.CmdIsolate)
181
dprankefe4602312015-04-08 16:20:35182 subp = subps.add_parser('lookup',
Stephen Martinis239c35a2019-07-22 19:34:40183 description='Look up the command for a given '
184 'config or builder.')
dprankefe4602312015-04-08 16:20:35185 AddCommonOptions(subp)
Garrett Beatyb6cee042019-04-22 18:42:09186 subp.add_argument('--quiet', default=False, action='store_true',
187 help='Print out just the arguments, '
188 'do not emulate the output of the gen subcommand.')
189 subp.add_argument('--recursive', default=False, action='store_true',
190 help='Lookup arguments from imported files, '
191 'implies --quiet')
dprankefe4602312015-04-08 16:20:35192 subp.set_defaults(func=self.CmdLookup)
193
dpranke030d7a6d2016-03-26 17:23:50194 subp = subps.add_parser(
Stephen Martinis239c35a2019-07-22 19:34:40195 'run', formatter_class=argparse.RawDescriptionHelpFormatter)
dpranke030d7a6d2016-03-26 17:23:50196 subp.description = (
197 'Build, isolate, and run the given binary with the command line\n'
198 'listed in the isolate. You may pass extra arguments after the\n'
199 'target; use "--" if the extra arguments need to include switches.\n'
200 '\n'
201 'Examples:\n'
202 '\n'
203 ' % tools/mb/mb.py run -m chromium.linux -b "Linux Builder" \\\n'
204 ' //out/Default content_browsertests\n'
205 '\n'
206 ' % tools/mb/mb.py run out/Default content_browsertests\n'
207 '\n'
208 ' % tools/mb/mb.py run out/Default content_browsertests -- \\\n'
209 ' --test-launcher-retry-limit=0'
210 '\n'
211 )
dpranke751516a2015-10-03 01:11:34212 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30213 subp.add_argument('-j', '--jobs', type=int,
dpranke751516a2015-10-03 01:11:34214 help='Number of jobs to pass to ninja')
215 subp.add_argument('--no-build', dest='build', default=True,
216 action='store_false',
217 help='Do not build, just isolate and run')
Dirk Prankef24e6b22018-03-27 20:12:30218 subp.add_argument('path',
dpranke030d7a6d2016-03-26 17:23:50219 help=('path to generate build into (or use).'
220 ' This can be either a regular path or a '
221 'GN-style source-relative path like '
222 '//out/Default.'))
Dirk Pranke8cb6aa782017-12-16 02:31:33223 subp.add_argument('-s', '--swarmed', action='store_true',
224 help='Run under swarming with the default dimensions')
225 subp.add_argument('-d', '--dimension', default=[], action='append', nargs=2,
226 dest='dimensions', metavar='FOO bar',
227 help='dimension to filter on')
228 subp.add_argument('--no-default-dimensions', action='store_false',
229 dest='default_dimensions', default=True,
230 help='Do not automatically add dimensions to the task')
Dirk Prankef24e6b22018-03-27 20:12:30231 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34232 help='ninja target to build and run')
dpranke030d7a6d2016-03-26 17:23:50233 subp.add_argument('extra_args', nargs='*',
234 help=('extra args to pass to the isolate to run. Use '
235 '"--" as the first arg if you need to pass '
236 'switches'))
dpranke751516a2015-10-03 01:11:34237 subp.set_defaults(func=self.CmdRun)
238
dprankefe4602312015-04-08 16:20:35239 subp = subps.add_parser('validate',
Stephen Martinis239c35a2019-07-22 19:34:40240 description='Validate the config file.')
dprankea5a77ca2015-07-16 23:24:17241 subp.add_argument('-f', '--config-file', metavar='PATH',
242 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50243 help='path to config file (default is %(default)s)')
dprankefe4602312015-04-08 16:20:35244 subp.set_defaults(func=self.CmdValidate)
245
Dirk Prankef24e6b22018-03-27 20:12:30246 subp = subps.add_parser('zip',
Stephen Martinis239c35a2019-07-22 19:34:40247 description='Generate a .zip containing the files '
248 'needed for a given binary.')
Dirk Prankef24e6b22018-03-27 20:12:30249 AddCommonOptions(subp)
250 subp.add_argument('--no-build', dest='build', default=True,
251 action='store_false',
252 help='Do not build, just isolate')
253 subp.add_argument('-j', '--jobs', type=int,
254 help='Number of jobs to pass to ninja')
255 subp.add_argument('path',
256 help='path build was generated into')
257 subp.add_argument('target',
258 help='ninja target to generate the isolate for')
259 subp.add_argument('zip_path',
260 help='path to zip file to create')
261 subp.set_defaults(func=self.CmdZip)
262
dprankefe4602312015-04-08 16:20:35263 subp = subps.add_parser('help',
264 help='Get help on a subcommand.')
265 subp.add_argument(nargs='?', action='store', dest='subcommand',
266 help='The command to get help for.')
267 subp.set_defaults(func=self.CmdHelp)
268
269 self.args = parser.parse_args(argv)
270
dprankeb2be10a2016-02-22 17:11:00271 def DumpInputFiles(self):
272
dprankef7b7eb7a2016-03-28 22:42:59273 def DumpContentsOfFilePassedTo(arg_name, path):
dprankeb2be10a2016-02-22 17:11:00274 if path and self.Exists(path):
dprankef7b7eb7a2016-03-28 22:42:59275 self.Print("\n# To recreate the file passed to %s:" % arg_name)
dprankecb4a2e242016-09-19 01:13:14276 self.Print("%% cat > %s <<EOF" % path)
dprankeb2be10a2016-02-22 17:11:00277 contents = self.ReadFile(path)
dprankef7b7eb7a2016-03-28 22:42:59278 self.Print(contents)
279 self.Print("EOF\n%\n")
dprankeb2be10a2016-02-22 17:11:00280
dprankef7b7eb7a2016-03-28 22:42:59281 if getattr(self.args, 'input_path', None):
282 DumpContentsOfFilePassedTo(
Dirk Prankef24e6b22018-03-27 20:12:30283 'argv[0] (input_path)', self.args.input_path)
dprankef7b7eb7a2016-03-28 22:42:59284 if getattr(self.args, 'swarming_targets_file', None):
285 DumpContentsOfFilePassedTo(
286 '--swarming-targets-file', self.args.swarming_targets_file)
dprankeb2be10a2016-02-22 17:11:00287
dprankefe4602312015-04-08 16:20:35288 def CmdAnalyze(self):
dpranke751516a2015-10-03 01:11:34289 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11290 return self.RunGNAnalyze(vals)
dprankefe4602312015-04-08 16:20:35291
dprankef37aebb92016-09-23 01:14:49292 def CmdExport(self):
293 self.ReadConfigFile()
294 obj = {}
295 for master, builders in self.masters.items():
296 obj[master] = {}
297 for builder in builders:
298 config = self.masters[master][builder]
299 if not config:
300 continue
301
shenghuazhang804b21542016-10-11 02:06:49302 if isinstance(config, dict):
303 args = {k: self.FlattenConfig(v)['gn_args']
304 for k, v in config.items()}
dprankef37aebb92016-09-23 01:14:49305 elif config.startswith('//'):
306 args = config
307 else:
308 args = self.FlattenConfig(config)['gn_args']
309 if 'error' in args:
310 continue
311
312 obj[master][builder] = args
313
314 # Dump object and trim trailing whitespace.
315 s = '\n'.join(l.rstrip() for l in
316 json.dumps(obj, sort_keys=True, indent=2).splitlines())
317 self.Print(s)
318 return 0
319
dprankefe4602312015-04-08 16:20:35320 def CmdGen(self):
dpranke751516a2015-10-03 01:11:34321 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11322 return self.RunGNGen(vals)
dprankefe4602312015-04-08 16:20:35323
Erik Chen42df41d2018-08-21 17:13:31324 def CmdIsolateEverything(self):
325 vals = self.Lookup()
326 return self.RunGNGenAllIsolates(vals)
327
dprankefe4602312015-04-08 16:20:35328 def CmdHelp(self):
329 if self.args.subcommand:
330 self.ParseArgs([self.args.subcommand, '--help'])
331 else:
332 self.ParseArgs(['--help'])
333
dpranke751516a2015-10-03 01:11:34334 def CmdIsolate(self):
335 vals = self.GetConfig()
336 if not vals:
337 return 1
Dirk Prankef24e6b22018-03-27 20:12:30338 if self.args.build:
339 ret = self.Build(self.args.target)
340 if ret:
341 return ret
Dirk Pranked181a1a2017-12-14 01:47:11342 return self.RunGNIsolate(vals)
dpranke751516a2015-10-03 01:11:34343
344 def CmdLookup(self):
345 vals = self.Lookup()
Garrett Beatyb6cee042019-04-22 18:42:09346 gn_args = self.GNArgs(vals, expand_imports=self.args.recursive)
347 if self.args.quiet or self.args.recursive:
348 self.Print(gn_args, end='')
349 else:
350 cmd = self.GNCmd('gen', '_path_')
351 self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args)
352 env = None
dpranke751516a2015-10-03 01:11:34353
Garrett Beatyb6cee042019-04-22 18:42:09354 self.PrintCmd(cmd, env)
dpranke751516a2015-10-03 01:11:34355 return 0
356
357 def CmdRun(self):
358 vals = self.GetConfig()
359 if not vals:
360 return 1
Dirk Pranked181a1a2017-12-14 01:47:11361 if self.args.build:
Dirk Pranke5f22a822019-05-23 22:55:25362 self.Print('')
Dirk Prankef24e6b22018-03-27 20:12:30363 ret = self.Build(self.args.target)
dpranke751516a2015-10-03 01:11:34364 if ret:
365 return ret
Dirk Pranke5f22a822019-05-23 22:55:25366
367 self.Print('')
Dirk Pranked181a1a2017-12-14 01:47:11368 ret = self.RunGNIsolate(vals)
369 if ret:
370 return ret
dpranke751516a2015-10-03 01:11:34371
Dirk Pranke5f22a822019-05-23 22:55:25372 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33373 if self.args.swarmed:
Dirk Prankef24e6b22018-03-27 20:12:30374 return self._RunUnderSwarming(self.args.path, self.args.target)
Dirk Pranke8cb6aa782017-12-16 02:31:33375 else:
Dirk Prankef24e6b22018-03-27 20:12:30376 return self._RunLocallyIsolated(self.args.path, self.args.target)
377
378 def CmdZip(self):
379 ret = self.CmdIsolate()
380 if ret:
381 return ret
382
383 zip_dir = None
384 try:
385 zip_dir = self.TempDir()
386 remap_cmd = [
387 self.executable,
388 self.PathJoin(self.chromium_src_dir, 'tools', 'swarming_client',
389 'isolate.py'),
390 'remap',
Kenneth Russell2e75e2f2018-11-15 22:37:28391 '--collapse_symlinks',
Dirk Prankef24e6b22018-03-27 20:12:30392 '-s', self.PathJoin(self.args.path, self.args.target + '.isolated'),
393 '-o', zip_dir
394 ]
395 self.Run(remap_cmd)
396
397 zip_path = self.args.zip_path
398 with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as fp:
399 for root, _, files in os.walk(zip_dir):
400 for filename in files:
401 path = self.PathJoin(root, filename)
402 fp.write(path, self.RelPath(path, zip_dir))
403 finally:
404 if zip_dir:
405 self.RemoveDirectory(zip_dir)
Dirk Pranke8cb6aa782017-12-16 02:31:33406
Robert Iannucci5a9d75f62018-03-02 05:28:20407 @staticmethod
408 def _AddBaseSoftware(cmd):
409 # HACK(iannucci): These packages SHOULD NOT BE HERE.
410 # Remove method once Swarming Pool Task Templates are implemented.
411 # crbug.com/812428
412
413 # Add in required base software. This should be kept in sync with the
John Budorick9d9175372019-04-01 19:04:24414 # `chromium_swarming` recipe module in build.git. All references to
415 # `swarming_module` below are purely due to this.
Robert Iannucci5a9d75f62018-03-02 05:28:20416 cipd_packages = [
417 ('infra/python/cpython/${platform}',
smut22dcd68e2019-06-25 23:33:27418 'version:2.7.15.chromium14'),
Robert Iannucci5a9d75f62018-03-02 05:28:20419 ('infra/tools/luci/logdog/butler/${platform}',
420 'git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c'),
421 ('infra/tools/luci/vpython-native/${platform}',
Marc-Antoine Ruele2b07a32019-04-15 17:52:09422 'git_revision:cc09450f1c27c0034ec08b1f6d63bbc298294763'),
Robert Iannucci5a9d75f62018-03-02 05:28:20423 ('infra/tools/luci/vpython/${platform}',
Marc-Antoine Ruele2b07a32019-04-15 17:52:09424 'git_revision:cc09450f1c27c0034ec08b1f6d63bbc298294763'),
Robert Iannucci5a9d75f62018-03-02 05:28:20425 ]
426 for pkg, vers in cipd_packages:
427 cmd.append('--cipd-package=.swarming_module:%s:%s' % (pkg, vers))
428
429 # Add packages to $PATH
430 cmd.extend([
431 '--env-prefix=PATH', '.swarming_module',
432 '--env-prefix=PATH', '.swarming_module/bin',
433 ])
434
435 # Add cache directives for vpython.
436 vpython_cache_path = '.swarming_module_cache/vpython'
437 cmd.extend([
438 '--named-cache=swarming_module_cache_vpython', vpython_cache_path,
439 '--env-prefix=VPYTHON_VIRTUALENV_ROOT', vpython_cache_path,
440 ])
441
Dirk Pranke8cb6aa782017-12-16 02:31:33442 def _RunUnderSwarming(self, build_dir, target):
Marc-Antoine Ruel559cc4732019-03-19 22:20:46443 isolate_server = 'isolateserver.appspot.com'
444 namespace = 'default-gzip'
445 swarming_server = 'chromium-swarm.appspot.com'
Dirk Pranke8cb6aa782017-12-16 02:31:33446 # TODO(dpranke): Look up the information for the target in
447 # the //testing/buildbot.json file, if possible, so that we
448 # can determine the isolate target, command line, and additional
449 # swarming parameters, if possible.
450 #
451 # TODO(dpranke): Also, add support for sharding and merging results.
452 dimensions = []
453 for k, v in self._DefaultDimensions() + self.args.dimensions:
454 dimensions += ['-d', k, v]
455
456 cmd = [
457 self.executable,
458 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
459 'archive',
Marc-Antoine Ruel559cc4732019-03-19 22:20:46460 '-s', self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
461 '-I', isolate_server,
462 '--namespace', namespace,
Dirk Pranke8cb6aa782017-12-16 02:31:33463 ]
Dirk Pranke5f22a822019-05-23 22:55:25464
465 # Talking to the isolateserver may fail because we're not logged in.
466 # We trap the command explicitly and rewrite the error output so that
467 # the error message is actually correct for a Chromium check out.
468 self.PrintCmd(cmd, env=None)
469 ret, out, err = self.Run(cmd, force_verbose=False)
Dirk Pranke8cb6aa782017-12-16 02:31:33470 if ret:
Dirk Pranke5f22a822019-05-23 22:55:25471 self.Print(' -> returned %d' % ret)
472 if out:
473 self.Print(out, end='')
474 if err:
475 # The swarming client will return an exit code of 2 (via
476 # argparse.ArgumentParser.error()) and print a message to indicate
477 # that auth failed, so we have to parse the message to check.
478 if (ret == 2 and 'Please login to' in err):
479 err = err.replace(' auth.py', ' tools/swarming_client/auth.py')
480 self.Print(err, end='', file=sys.stderr)
481
Dirk Pranke8cb6aa782017-12-16 02:31:33482 return ret
483
484 isolated_hash = out.splitlines()[0].split()[0]
485 cmd = [
486 self.executable,
487 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
488 'run',
489 '-s', isolated_hash,
Marc-Antoine Ruel559cc4732019-03-19 22:20:46490 '-I', isolate_server,
491 '--namespace', namespace,
492 '-S', swarming_server,
Dirk Pranke8cb6aa782017-12-16 02:31:33493 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20494 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33495 if self.args.extra_args:
496 cmd += ['--'] + self.args.extra_args
Dirk Pranke5f22a822019-05-23 22:55:25497 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33498 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
499 return ret
500
501 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50502 cmd = [
dpranke751516a2015-10-03 01:11:34503 self.executable,
504 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
505 'run',
506 '-s',
dpranke030d7a6d2016-03-26 17:23:50507 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33508 ]
dpranke030d7a6d2016-03-26 17:23:50509 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33510 cmd += ['--'] + self.args.extra_args
511 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34512 return ret
513
Dirk Pranke8cb6aa782017-12-16 02:31:33514 def _DefaultDimensions(self):
515 if not self.args.default_dimensions:
516 return []
517
518 # This code is naive and just picks reasonable defaults per platform.
519 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40520 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33521 elif self.platform.startswith('linux'):
522 os_dim = ('os', 'Ubuntu-14.04')
523 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40524 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33525 else:
526 raise MBErr('unrecognized platform string "%s"' % self.platform)
527
528 return [('pool', 'Chrome'),
529 ('cpu', 'x86-64'),
530 os_dim]
531
dpranke0cafc162016-03-19 00:41:10532 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35533 errs = []
534
535 # Read the file to make sure it parses.
536 self.ReadConfigFile()
537
dpranke3be00142016-03-17 22:46:04538 # Build a list of all of the configs referenced by builders.
dprankefe4602312015-04-08 16:20:35539 all_configs = {}
dprankefe4602312015-04-08 16:20:35540 for master in self.masters:
dpranke3be00142016-03-17 22:46:04541 for config in self.masters[master].values():
shenghuazhang804b21542016-10-11 02:06:49542 if isinstance(config, dict):
543 for c in config.values():
dprankeb9380a12016-07-21 21:44:09544 all_configs[c] = master
545 else:
546 all_configs[config] = master
dprankefe4602312015-04-08 16:20:35547
dpranke9dd5e252016-04-14 04:23:09548 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35549 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09550 if config.startswith('//'):
551 if not self.Exists(self.ToAbsPath(config)):
552 errs.append('Unknown args file "%s" referenced from "%s".' %
553 (config, loc))
554 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35555 errs.append('Unknown config "%s" referenced from "%s".' %
556 (config, loc))
557
558 # Check that every actual config is actually referenced.
559 for config in self.configs:
560 if not config in all_configs:
561 errs.append('Unused config "%s".' % config)
562
563 # Figure out the whole list of mixins, and check that every mixin
564 # listed by a config or another mixin actually exists.
565 referenced_mixins = set()
566 for config, mixins in self.configs.items():
567 for mixin in mixins:
568 if not mixin in self.mixins:
569 errs.append('Unknown mixin "%s" referenced by config "%s".' %
570 (mixin, config))
571 referenced_mixins.add(mixin)
572
573 for mixin in self.mixins:
574 for sub_mixin in self.mixins[mixin].get('mixins', []):
575 if not sub_mixin in self.mixins:
576 errs.append('Unknown mixin "%s" referenced by mixin "%s".' %
577 (sub_mixin, mixin))
578 referenced_mixins.add(sub_mixin)
579
580 # Check that every mixin defined is actually referenced somewhere.
581 for mixin in self.mixins:
582 if not mixin in referenced_mixins:
583 errs.append('Unreferenced mixin "%s".' % mixin)
584
dpranke255085e2016-03-16 05:23:59585 # If we're checking the Chromium config, check that the 'chromium' bots
586 # which build public artifacts do not include the chrome_with_codecs mixin.
587 if self.args.config_file == self.default_config:
588 if 'chromium' in self.masters:
589 for builder in self.masters['chromium']:
590 config = self.masters['chromium'][builder]
591 def RecurseMixins(current_mixin):
592 if current_mixin == 'chrome_with_codecs':
593 errs.append('Public artifact builder "%s" can not contain the '
594 '"chrome_with_codecs" mixin.' % builder)
595 return
596 if not 'mixins' in self.mixins[current_mixin]:
597 return
598 for mixin in self.mixins[current_mixin]['mixins']:
599 RecurseMixins(mixin)
dalecurtis56fd27e2016-03-09 23:06:41600
dpranke255085e2016-03-16 05:23:59601 for mixin in self.configs[config]:
602 RecurseMixins(mixin)
603 else:
604 errs.append('Missing "chromium" master. Please update this '
605 'proprietary codecs check with the name of the master '
606 'responsible for public build artifacts.')
dalecurtis56fd27e2016-03-09 23:06:41607
dprankefe4602312015-04-08 16:20:35608 if errs:
dpranke4323c80632015-08-10 22:53:54609 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17610 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35611
dpranke0cafc162016-03-19 00:41:10612 if print_ok:
613 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35614 return 0
615
616 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30617 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34618
dprankef37aebb92016-09-23 01:14:49619 vals = self.DefaultVals()
dpranke751516a2015-10-03 01:11:34620 if self.args.builder or self.args.master or self.args.config:
621 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11622 # Re-run gn gen in order to ensure the config is consistent with the
623 # build dir.
624 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34625 return vals
626
Dirk Pranked181a1a2017-12-14 01:47:11627 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
628 'toolchain.ninja')
629 if not self.Exists(toolchain_path):
630 self.Print('Must either specify a path to an existing GN build dir '
631 'or pass in a -m/-b pair or a -c flag to specify the '
632 'configuration')
633 return {}
dpranke751516a2015-10-03 01:11:34634
Dirk Pranked181a1a2017-12-14 01:47:11635 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34636 return vals
637
dprankef37aebb92016-09-23 01:14:49638 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58639 args_contents = ""
640 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
641 if self.Exists(gn_args_path):
642 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34643 gn_args = []
644 for l in args_contents.splitlines():
645 fields = l.split(' ')
646 name = fields[0]
647 val = ' '.join(fields[2:])
648 gn_args.append('%s=%s' % (name, val))
649
dprankef37aebb92016-09-23 01:14:49650 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34651
652 def Lookup(self):
Erik Chen238f4ac2019-04-12 19:02:50653 vals = self.ReadIOSBotConfig()
654 if not vals:
655 self.ReadConfigFile()
656 config = self.ConfigFromArgs()
657 if config.startswith('//'):
658 if not self.Exists(self.ToAbsPath(config)):
659 raise MBErr('args file "%s" not found' % config)
660 vals = self.DefaultVals()
661 vals['args_file'] = config
662 else:
663 if not config in self.configs:
664 raise MBErr('Config "%s" not found in %s' %
665 (config, self.args.config_file))
666 vals = self.FlattenConfig(config)
667 return vals
668
669 def ReadIOSBotConfig(self):
670 if not self.args.master or not self.args.builder:
671 return {}
672 path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
673 self.args.master, self.args.builder + '.json')
674 if not self.Exists(path):
675 return {}
676
677 contents = json.loads(self.ReadFile(path))
678 gn_args = ' '.join(contents.get('gn_args', []))
679
680 vals = self.DefaultVals()
681 vals['gn_args'] = gn_args
dprankef37aebb92016-09-23 01:14:49682 return vals
dprankee0f486f2015-11-19 23:42:00683
dprankefe4602312015-04-08 16:20:35684 def ReadConfigFile(self):
685 if not self.Exists(self.args.config_file):
686 raise MBErr('config file not found at %s' % self.args.config_file)
687
688 try:
689 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
690 except SyntaxError as e:
691 raise MBErr('Failed to parse config file "%s": %s' %
692 (self.args.config_file, e))
693
dprankefe4602312015-04-08 16:20:35694 self.configs = contents['configs']
695 self.masters = contents['masters']
696 self.mixins = contents['mixins']
dprankefe4602312015-04-08 16:20:35697
dprankecb4a2e242016-09-19 01:13:14698 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20699 if not self.args.isolate_map_files:
700 self.args.isolate_map_files = [self.default_isolate_map]
701
702 for f in self.args.isolate_map_files:
703 if not self.Exists(f):
704 raise MBErr('isolate map file not found at %s' % f)
705 isolate_maps = {}
706 for isolate_map in self.args.isolate_map_files:
707 try:
708 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
709 duplicates = set(isolate_map).intersection(isolate_maps)
710 if duplicates:
711 raise MBErr(
712 'Duplicate targets in isolate map files: %s.' %
713 ', '.join(duplicates))
714 isolate_maps.update(isolate_map)
715 except SyntaxError as e:
716 raise MBErr(
717 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
718 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14719
dprankefe4602312015-04-08 16:20:35720 def ConfigFromArgs(self):
721 if self.args.config:
722 if self.args.master or self.args.builder:
723 raise MBErr('Can not specific both -c/--config and -m/--master or '
724 '-b/--builder')
725
726 return self.args.config
727
728 if not self.args.master or not self.args.builder:
729 raise MBErr('Must specify either -c/--config or '
730 '(-m/--master and -b/--builder)')
731
732 if not self.args.master in self.masters:
733 raise MBErr('Master name "%s" not found in "%s"' %
734 (self.args.master, self.args.config_file))
735
736 if not self.args.builder in self.masters[self.args.master]:
737 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
738 (self.args.builder, self.args.master, self.args.config_file))
739
dprankeb9380a12016-07-21 21:44:09740 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49741 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09742 if self.args.phase is None:
743 raise MBErr('Must specify a build --phase for %s on %s' %
744 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49745 phase = str(self.args.phase)
746 if phase not in config:
747 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09748 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49749 return config[phase]
dprankeb9380a12016-07-21 21:44:09750
751 if self.args.phase is not None:
752 raise MBErr('Must not specify a build --phase for %s on %s' %
753 (self.args.builder, self.args.master))
754 return config
dprankefe4602312015-04-08 16:20:35755
756 def FlattenConfig(self, config):
757 mixins = self.configs[config]
dprankef37aebb92016-09-23 01:14:49758 vals = self.DefaultVals()
dprankefe4602312015-04-08 16:20:35759
760 visited = []
761 self.FlattenMixins(mixins, vals, visited)
762 return vals
763
dprankef37aebb92016-09-23 01:14:49764 def DefaultVals(self):
765 return {
766 'args_file': '',
767 'cros_passthrough': False,
768 'gn_args': '',
dprankef37aebb92016-09-23 01:14:49769 }
770
dprankefe4602312015-04-08 16:20:35771 def FlattenMixins(self, mixins, vals, visited):
772 for m in mixins:
773 if m not in self.mixins:
774 raise MBErr('Unknown mixin "%s"' % m)
dprankeee5b51f62015-04-09 00:03:22775
dprankefe4602312015-04-08 16:20:35776 visited.append(m)
777
778 mixin_vals = self.mixins[m]
dpranke73ed0d62016-04-25 19:18:34779
780 if 'cros_passthrough' in mixin_vals:
781 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
Dirk Pranke6b99f072017-04-05 00:58:30782 if 'args_file' in mixin_vals:
783 if vals['args_file']:
784 raise MBErr('args_file specified multiple times in mixins '
785 'for %s on %s' % (self.args.builder, self.args.master))
786 vals['args_file'] = mixin_vals['args_file']
dprankefe4602312015-04-08 16:20:35787 if 'gn_args' in mixin_vals:
788 if vals['gn_args']:
789 vals['gn_args'] += ' ' + mixin_vals['gn_args']
790 else:
791 vals['gn_args'] = mixin_vals['gn_args']
dpranke73ed0d62016-04-25 19:18:34792
dprankefe4602312015-04-08 16:20:35793 if 'mixins' in mixin_vals:
794 self.FlattenMixins(mixin_vals['mixins'], vals, visited)
795 return vals
796
Takuto Ikuta9dffd7e2018-09-05 01:04:00797 def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
Dirk Prankef24e6b22018-03-27 20:12:30798 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50799
Takuto Ikuta9dffd7e2018-09-05 01:04:00800 if check:
801 cmd = self.GNCmd('gen', build_dir, '--check')
802 else:
803 cmd = self.GNCmd('gen', build_dir)
dprankeeca4a782016-04-14 01:42:38804 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:09805 if compute_inputs_for_analyze:
806 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:38807
808 # Since GN hasn't run yet, the build directory may not even exist.
809 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
810
811 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:54812 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:39813
dpranke751516a2015-10-03 01:11:34814 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:39815 # We need GN to generate the list of runtime dependencies for
816 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:14817 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:39818 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:00819 path = self.args.swarming_targets_file
820 if not self.Exists(path):
821 self.WriteFailureAndRaise('"%s" does not exist' % path,
822 output_path=None)
823 contents = self.ReadFile(path)
Erik Chen42df41d2018-08-21 17:13:31824 isolate_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:00825
dprankecb4a2e242016-09-19 01:13:14826 isolate_map = self.ReadIsolateMap()
Dirk Pranke7a7e9b62019-02-17 01:46:25827 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
828 isolate_map, build_dir)
829
Erik Chen42df41d2018-08-21 17:13:31830 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:00831 if err:
Dirk Pranke7a7e9b62019-02-17 01:46:25832 raise MBErr(err)
dpranke74559b52015-06-10 21:20:39833
dpranke751516a2015-10-03 01:11:34834 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:14835 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:39836 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
837
Debrian Figueroaae582232019-07-17 01:54:45838 ret, output, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:40839 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:11840 if self.args.json_output:
Debrian Figueroaae582232019-07-17 01:54:45841 # write errors to json.output
842 self.WriteJSON({'output': output}, self.args.json_output)
Dirk Pranke7a7e9b62019-02-17 01:46:25843 # If `gn gen` failed, we should exit early rather than trying to
844 # generate isolates. Run() will have already logged any error output.
845 self.Print('GN gen failed: %d' % ret)
846 return ret
dpranke74559b52015-06-10 21:20:39847
Erik Chen42df41d2018-08-21 17:13:31848 if getattr(self.args, 'swarming_targets_file', None):
Dirk Pranke7a7e9b62019-02-17 01:46:25849 self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
Erik Chen42df41d2018-08-21 17:13:31850
851 return 0
852
853 def RunGNGenAllIsolates(self, vals):
854 """
855 This command generates all .isolate files.
856
857 This command assumes that "mb.py gen" has already been run, as it relies on
858 "gn ls" to fetch all gn targets. If uses that output, combined with the
859 isolate_map, to determine all isolates that can be generated for the current
860 gn configuration.
861 """
862 build_dir = self.args.path
863 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
864 force_verbose=False)
865 if ret:
866 # If `gn ls` failed, we should exit early rather than trying to
867 # generate isolates.
868 self.Print('GN ls failed: %d' % ret)
869 return ret
870
871 # Create a reverse map from isolate label to isolate dict.
872 isolate_map = self.ReadIsolateMap()
873 isolate_dict_map = {}
874 for key, isolate_dict in isolate_map.iteritems():
875 isolate_dict_map[isolate_dict['label']] = isolate_dict
876 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
877
878 runtime_deps = []
879
880 isolate_targets = []
881 # For every GN target, look up the isolate dict.
882 for line in output.splitlines():
883 target = line.strip()
884 if target in isolate_dict_map:
885 if isolate_dict_map[target]['type'] == 'additional_compile_target':
886 # By definition, additional_compile_targets are not tests, so we
887 # shouldn't generate isolates for them.
888 continue
889
890 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
891 runtime_deps.append(target)
892
Dirk Pranke7a7e9b62019-02-17 01:46:25893 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
894 isolate_map, build_dir)
895
Erik Chen42df41d2018-08-21 17:13:31896 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
897 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
898 cmd = self.GNCmd('gen', build_dir)
899 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
900 self.Run(cmd)
901
902 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
903
Dirk Pranke7a7e9b62019-02-17 01:46:25904 def RemovePossiblyStaleRuntimeDepsFiles(self, vals, targets, isolate_map,
905 build_dir):
906 # TODO(crbug.com/932700): Because `gn gen --runtime-deps-list-file`
907 # puts the runtime_deps file in different locations based on the actual
908 # type of a target, we may end up with multiple possible runtime_deps
909 # files in a given build directory, where some of the entries might be
910 # stale (since we might be reusing an existing build directory).
911 #
912 # We need to be able to get the right one reliably; you might think
913 # we can just pick the newest file, but because GN won't update timestamps
914 # if the contents of the files change, an older runtime_deps
915 # file might actually be the one we should use over a newer one (see
916 # crbug.com/932387 for a more complete explanation and example).
917 #
918 # In order to avoid this, we need to delete any possible runtime_deps
919 # files *prior* to running GN. As long as the files aren't actually
920 # needed during the build, this hopefully will not cause unnecessary
921 # build work, and so it should be safe.
922 #
923 # Ultimately, we should just make sure we get the runtime_deps files
924 # in predictable locations so we don't have this issue at all, and
925 # that's what crbug.com/932700 is for.
926 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, targets, isolate_map)
927 for rpaths in possible_rpaths.values():
928 for rpath in rpaths:
929 path = self.ToAbsPath(build_dir, rpath)
930 if self.Exists(path):
931 self.RemoveFile(path)
932
Erik Chen42df41d2018-08-21 17:13:31933 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
934 """
935 Generates isolates for a list of ninja targets.
936
937 Ninja targets are transformed to GN targets via isolate_map.
938
939 This function assumes that a previous invocation of "mb.py gen" has
940 generated runtime deps for all targets.
941 """
Dirk Pranke7a7e9b62019-02-17 01:46:25942 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, ninja_targets,
943 isolate_map)
944
945 for target, rpaths in possible_rpaths.items():
946 # TODO(crbug.com/932700): We don't know where each .runtime_deps
947 # file might be, but assuming we called
948 # RemovePossiblyStaleRuntimeDepsFiles prior to calling `gn gen`,
949 # there should only be one file.
950 found_one = False
951 path_to_use = None
952 for r in rpaths:
953 path = self.ToAbsPath(build_dir, r)
954 if self.Exists(path):
955 if found_one:
956 raise MBErr('Found more than one of %s' % ', '.join(rpaths))
957 path_to_use = path
958 found_one = True
959
960 if not found_one:
961 raise MBErr('Did not find any of %s' % ', '.join(rpaths))
962
963 command, extra_files = self.GetIsolateCommand(target, vals)
964 runtime_deps = self.ReadFile(path_to_use).splitlines()
965
966 canonical_target = target.replace(':','_').replace('/','_')
967 self.WriteIsolateFiles(build_dir, command, canonical_target, runtime_deps,
968 extra_files)
969
970 def PossibleRuntimeDepsPaths(self, vals, ninja_targets, isolate_map):
971 """Returns a map of targets to possible .runtime_deps paths.
972
973 Each ninja target maps on to a GN label, but depending on the type
974 of the GN target, `gn gen --runtime-deps-list-file` will write
975 the .runtime_deps files into different locations. Unfortunately, in
976 some cases we don't actually know which of multiple locations will
977 actually be used, so we return all plausible candidates.
978
979 The paths that are returned are relative to the build directory.
980 """
981
jbudoricke3c4f95e2016-04-28 23:17:38982 android = 'target_os="android"' in vals['gn_args']
Dirk Pranke26de05aec2019-04-03 19:18:38983 ios = 'target_os="ios"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:42984 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:30985 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Dirk Pranke7a7e9b62019-02-17 01:46:25986 possible_runtime_deps_rpaths = {}
Erik Chen42df41d2018-08-21 17:13:31987 for target in ninja_targets:
John Budorick39f14962019-04-11 23:03:20988 target_type = isolate_map[target]['type']
989 label = isolate_map[target]['label']
990 stamp_runtime_deps = 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')
Erik Chen42df41d2018-08-21 17:13:31991 # TODO(https://crbug.com/876065): 'official_tests' use
992 # type='additional_compile_target' to isolate tests. This is not the
993 # intended use for 'additional_compile_target'.
John Budorick39f14962019-04-11 23:03:20994 if (target_type == 'additional_compile_target' and
Erik Chen42df41d2018-08-21 17:13:31995 target != 'official_tests'):
996 # By definition, additional_compile_targets are not tests, so we
997 # shouldn't generate isolates for them.
Dirk Pranke7a7e9b62019-02-17 01:46:25998 raise MBErr('Cannot generate isolate for %s since it is an '
999 'additional_compile_target.' % target)
John Budorick39f14962019-04-11 23:03:201000 elif fuchsia or ios or target_type == 'generated_script':
1001 # iOS and Fuchsia targets end up as groups.
1002 # generated_script targets are always actions.
1003 rpaths = [stamp_runtime_deps]
Erik Chen42df41d2018-08-21 17:13:311004 elif android:
jbudoricke3c4f95e2016-04-28 23:17:381005 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:021006 # will result in runtime_deps associated with the stamp file, while the
1007 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:441008 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:251009 rpaths = [
dprankecb4a2e242016-09-19 01:13:141010 target + '.runtime_deps',
John Budorick39f14962019-04-11 23:03:201011 stamp_runtime_deps]
1012 elif (target_type == 'script' or
1013 target_type == 'fuzzer' or
dprankecb4a2e242016-09-19 01:13:141014 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:111015 # For script targets, the build target is usually a group,
1016 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:491017 # for the label, which lives under the obj/ directory, but it may
1018 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:441019 label = isolate_map[target]['label']
John Budorick39f14962019-04-11 23:03:201020 rpaths = [stamp_runtime_deps]
Nico Weberd94b71a2018-02-22 22:00:301021 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:251022 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:491023 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251024 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:301025 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:251026 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:521027 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251028 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:021029
Dirk Pranke7a7e9b62019-02-17 01:46:251030 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:411031
Dirk Pranke7a7e9b62019-02-17 01:46:251032 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:341033
1034 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:301035 target = self.args.target
dprankecb4a2e242016-09-19 01:13:141036 isolate_map = self.ReadIsolateMap()
1037 err, labels = self.MapTargetsToLabels(isolate_map, [target])
1038 if err:
1039 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:251040
dprankecb4a2e242016-09-19 01:13:141041 label = labels[0]
dpranke751516a2015-10-03 01:11:341042
Dirk Prankef24e6b22018-03-27 20:12:301043 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:141044 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:341045
dprankeeca4a782016-04-14 01:42:381046 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:201047 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:341048 if ret:
dpranke030d7a6d2016-03-26 17:23:501049 if out:
1050 self.Print(out)
dpranke751516a2015-10-03 01:11:341051 return ret
1052
1053 runtime_deps = out.splitlines()
1054
1055 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
1056 extra_files)
1057
1058 ret, _, _ = self.Run([
1059 self.executable,
1060 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1061 'check',
1062 '-i',
1063 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1064 '-s',
1065 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1066 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301067
dprankefe4602312015-04-08 16:20:351068 return ret
1069
dpranke751516a2015-10-03 01:11:341070 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps,
1071 extra_files):
1072 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
1073 self.WriteFile(isolate_path,
1074 pprint.pformat({
1075 'variables': {
1076 'command': command,
Dirk Pranke8edeb682019-06-11 16:24:051077 'files': sorted(set(runtime_deps + extra_files)),
dpranke751516a2015-10-03 01:11:341078 }
1079 }) + '\n')
1080
1081 self.WriteJSON(
1082 {
1083 'args': [
1084 '--isolated',
1085 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1086 '--isolate',
1087 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1088 ],
1089 'dir': self.chromium_src_dir,
1090 'version': 1,
1091 },
1092 isolate_path + 'd.gen.json',
1093 )
1094
dprankecb4a2e242016-09-19 01:13:141095 def MapTargetsToLabels(self, isolate_map, targets):
1096 labels = []
1097 err = ''
1098
dprankecb4a2e242016-09-19 01:13:141099 for target in targets:
1100 if target == 'all':
1101 labels.append(target)
1102 elif target.startswith('//'):
1103 labels.append(target)
1104 else:
1105 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421106 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141107 err += ('test target "%s" type is unknown\n' % target)
1108 else:
thakis024d6f32017-05-16 23:21:421109 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141110 else:
1111 err += ('target "%s" not found in '
1112 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1113
1114 return err, labels
1115
dprankeeca4a782016-04-14 01:42:381116 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461117 if self.platform == 'linux2':
1118 subdir, exe = 'linux64', 'gn'
1119 elif self.platform == 'darwin':
1120 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251121 elif self.platform == 'aix6':
1122 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461123 else:
1124 subdir, exe = 'win', 'gn.exe'
1125
1126 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081127 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521128
dprankecb4a2e242016-09-19 01:13:141129
Garrett Beatyb6cee042019-04-22 18:42:091130 def GNArgs(self, vals, expand_imports=False):
dpranke73ed0d62016-04-25 19:18:341131 if vals['cros_passthrough']:
1132 if not 'GN_ARGS' in os.environ:
1133 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1134 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161135 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301136 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341137 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351138 if vals['gn_args']:
1139 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341140 else:
1141 gn_args = vals['gn_args']
1142
dpranked0c138b2016-04-13 18:28:471143 if self.args.goma_dir:
1144 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381145
agrieve41d21a72016-04-14 18:02:261146 android_version_code = self.args.android_version_code
1147 if android_version_code:
1148 gn_args += ' android_default_version_code="%s"' % android_version_code
1149
1150 android_version_name = self.args.android_version_name
1151 if android_version_name:
1152 gn_args += ' android_default_version_name="%s"' % android_version_name
1153
Garrett Beatyb6cee042019-04-22 18:42:091154 args_gn_lines = []
1155 parsed_gn_args = {}
dprankeeca4a782016-04-14 01:42:381156
Ben Pastene65ccf6132018-11-08 00:47:591157 # If we're using the Simple Chrome SDK, add a comment at the top that
1158 # points to the doc. This must happen after the gn_helpers.ToGNString()
1159 # call above since gn_helpers strips comments.
1160 if vals['cros_passthrough']:
Garrett Beatyb6cee042019-04-22 18:42:091161 args_gn_lines.extend([
Ben Pastene65ccf6132018-11-08 00:47:591162 '# These args are generated via the Simple Chrome SDK. See the link',
1163 '# below for more details:',
1164 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
Garrett Beatyb6cee042019-04-22 18:42:091165 ])
Ben Pastene65ccf6132018-11-08 00:47:591166
dpranke9dd5e252016-04-14 04:23:091167 args_file = vals.get('args_file', None)
1168 if args_file:
Garrett Beatyb6cee042019-04-22 18:42:091169 if expand_imports:
1170 content = self.ReadFile(self.ToAbsPath(args_file))
1171 parsed_gn_args = gn_helpers.FromGNArgs(content)
1172 else:
1173 args_gn_lines.append('import("%s")' % args_file)
1174
1175 # Canonicalize the arg string into a sorted, newline-separated list
1176 # of key-value pairs, and de-dup the keys if need be so that only
1177 # the last instance of each arg is listed.
1178 parsed_gn_args.update(gn_helpers.FromGNArgs(gn_args))
1179 args_gn_lines.append(gn_helpers.ToGNString(parsed_gn_args))
1180
1181 return '\n'.join(args_gn_lines)
dprankefe4602312015-04-08 16:20:351182
dprankecb4a2e242016-09-19 01:13:141183 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071184 isolate_map = self.ReadIsolateMap()
1185
Scott Graham3be4b4162017-09-12 00:41:411186 is_android = 'target_os="android"' in vals['gn_args']
1187 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Caleb Raittof983d102019-06-21 23:05:021188 is_cros = 'target_os="chromeos"' in vals['gn_args']
Nico Webera7bc1cb2019-06-15 17:42:391189 is_simplechrome = vals.get('cros_passthrough', False)
1190 is_mac = self.platform == 'darwin'
Nico Weberd94b71a2018-02-22 22:00:301191 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061192
kylechar39705682017-01-19 14:37:231193 # This should be true if tests with type='windowed_test_launcher' are
1194 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081195 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1196 # backends. Currently, tests are executed for the headless and X11 backends
1197 # and both can run under Xvfb.
1198 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1199 # backend.
Scott Graham3be4b4162017-09-12 00:41:411200 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251201
1202 asan = 'is_asan=true' in vals['gn_args']
1203 msan = 'is_msan=true' in vals['gn_args']
1204 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411205 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
Yun Liu7cef1072019-06-27 21:22:191206 java_coverage = 'jacoco_coverage=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251207
dprankecb4a2e242016-09-19 01:13:141208 test_type = isolate_map[target]['type']
dprankefe0d35e2016-02-05 02:43:591209
dprankecb4a2e242016-09-19 01:13:141210 executable = isolate_map[target].get('executable', target)
bsheedy9c16ed62019-04-10 20:32:111211 executable_suffix = isolate_map[target].get(
1212 'executable_suffix', '.exe' if is_win else '')
dprankefe0d35e2016-02-05 02:43:591213
dprankea55584f12015-07-22 00:52:471214 cmdline = []
Andrii Shyshkalovc158e0102018-01-10 05:52:001215 extra_files = [
1216 '../../.vpython',
1217 '../../testing/test_env.py',
1218 ]
dpranked8113582015-06-05 20:08:251219
dprankecb4a2e242016-09-19 01:13:141220 if test_type == 'nontest':
1221 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1222 output_path=None)
1223
John Budorick93e88ac82019-04-12 18:39:111224 if test_type == 'generated_script':
Ben Pastenecb0fb412019-06-11 02:31:541225 script = isolate_map[target]['script']
1226 if self.platform == 'win32':
1227 script += '.bat'
John Budorick93e88ac82019-04-12 18:39:111228 cmdline = [
1229 '../../testing/test_env.py',
Ben Pastenecb0fb412019-06-11 02:31:541230 script,
John Budorick93e88ac82019-04-12 18:39:111231 ]
1232 elif test_type == 'fuzzer':
Roberto Carrillo1460da852018-12-14 17:10:391233 cmdline = [
1234 '../../testing/test_env.py',
1235 '../../tools/code_coverage/run_fuzz_target.py',
1236 '--fuzzer', './' + target,
1237 '--output-dir', '${ISOLATED_OUTDIR}',
1238 '--timeout', '3600']
1239 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011240 cmdline = []
1241 if asan:
John Budorick31cdce62019-04-03 20:56:111242 cmdline += [os.path.join('bin', 'run_with_asan'), '--']
John Budorick8c4203042019-03-19 17:22:011243 cmdline += [
John Budorickfb97a852017-12-20 20:10:191244 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041245 '../../build/android/test_wrapper/logdog_wrapper.py',
1246 '--target', target,
hzl9ae14452017-04-04 23:38:021247 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481248 '--store-tombstones']
Yun Liu7cef1072019-06-27 21:22:191249 if java_coverage:
1250 cmdline += ['--coverage-dir', '${ISOLATED_OUTDIR}']
Scott Graham3be4b4162017-09-12 00:41:411251 elif is_fuchsia and test_type != 'script':
John Budorickfb97a852017-12-20 20:10:191252 cmdline = [
1253 '../../testing/test_env.py',
1254 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441255 '--test-launcher-bot-mode',
Sergey Ulanovd851243b2019-06-25 00:33:471256 '--system-log-file', '${ISOLATED_OUTDIR}/system_log'
John Budorickfb97a852017-12-20 20:10:191257 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321258 elif is_simplechrome and test_type != 'script':
1259 cmdline = [
1260 '../../testing/test_env.py',
1261 os.path.join('bin', 'run_%s' % target),
1262 ]
kylechar39705682017-01-19 14:37:231263 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001264 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471265 cmdline = [
Nico Webera7bc1cb2019-06-15 17:42:391266 '../../testing/xvfb.py',
1267 './' + str(executable) + executable_suffix,
1268 '--test-launcher-bot-mode',
1269 '--asan=%d' % asan,
1270 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1271 # supported.
1272 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021273 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1274 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
Nico Webera7bc1cb2019-06-15 17:42:391275 '--msan=%d' % msan,
1276 '--tsan=%d' % tsan,
1277 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471278 ]
1279 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471280 cmdline = [
1281 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591282 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251283 '--test-launcher-bot-mode',
1284 '--asan=%d' % asan,
Caleb Raitto1fb2cc9e2019-06-14 01:04:231285 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1286 # supported.
Nico Webera7bc1cb2019-06-15 17:42:391287 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021288 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1289 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
dpranked8113582015-06-05 20:08:251290 '--msan=%d' % msan,
1291 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411292 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471293 ]
dpranke6abd8652015-08-28 03:21:111294 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241295 cmdline = []
Ben Pastene4534c39e2019-07-08 22:55:341296 # If we're testing a CrOS simplechrome build, assume we need to prepare a
1297 # DUT for testing. So prepend the command to run with the test wrapper.
Ben Pastene8ab6954d2018-05-04 04:08:241298 if is_simplechrome:
Ben Pastene4534c39e2019-07-08 22:55:341299 cmdline = [os.path.join('bin', 'cros_test_wrapper')]
Ben Pastene8ab6954d2018-05-04 04:08:241300 cmdline += [
dpranke6abd8652015-08-28 03:21:111301 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141302 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591303 ]
Dirk Prankef24e6b22018-03-27 20:12:301304 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471305 cmdline = [
1306 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591307 ]
dprankea55584f12015-07-22 00:52:471308 else:
1309 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1310 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251311
Abhishek Arya2f5f7342018-06-13 16:59:441312 if is_win and asan:
Alexander Dunaev384ba482018-03-21 17:56:121313 # Sandbox is not yet supported by ASAN for Windows.
1314 # Perhaps this is only needed for tests that use the sandbox?
1315 cmdline.append('--no-sandbox')
1316
dprankecb4a2e242016-09-19 01:13:141317 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591318
dpranked8113582015-06-05 20:08:251319 return cmdline, extra_files
1320
dpranke74559b52015-06-10 21:20:391321 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331322 return self.PathJoin(self.chromium_src_dir,
1323 self.ToSrcRelPath(build_path),
1324 *comps)
dpranked8113582015-06-05 20:08:251325
dprankeee5b51f62015-04-09 00:03:221326 def ToSrcRelPath(self, path):
1327 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501328 if path.startswith('//'):
1329 return path[2:].replace('/', self.sep)
1330 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351331
Dirk Pranke0fd41bcd2015-06-19 00:05:501332 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141333 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501334 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001335 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501336 if ret:
1337 return ret
1338
Dirk Prankef24e6b22018-03-27 20:12:301339 build_path = self.args.path
1340 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141341 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301342 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141343 gn_output_path = output_path + '.gn'
1344
dpranke7837fc362015-11-19 03:54:161345 inp = self.ReadInputJSON(['files', 'test_targets',
1346 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321347 if self.args.verbose:
1348 self.Print()
1349 self.Print('analyze input:')
1350 self.PrintJSON(inp)
1351 self.Print()
1352
dpranke76734662015-04-16 02:17:501353
dpranke7c5f614d2015-07-22 23:43:391354 # This shouldn't normally happen, but could due to unusual race conditions,
1355 # like a try job that gets scheduled before a patch lands but runs after
1356 # the patch has landed.
1357 if not inp['files']:
1358 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161359 self.WriteJSON({
1360 'status': 'No dependency',
1361 'compile_targets': [],
1362 'test_targets': [],
1363 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391364 return 0
1365
dprankecb4a2e242016-09-19 01:13:141366 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161367 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561368
dprankecb4a2e242016-09-19 01:13:141369 isolate_map = self.ReadIsolateMap()
1370 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1371 isolate_map, inp['additional_compile_targets'])
1372 if err:
1373 raise MBErr(err)
1374
1375 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1376 isolate_map, inp['test_targets'])
1377 if err:
1378 raise MBErr(err)
1379 labels_to_targets = {}
1380 for i, label in enumerate(gn_inp['test_targets']):
1381 labels_to_targets[label] = inp['test_targets'][i]
1382
dprankef61de2f2015-05-14 04:09:561383 try:
dprankecb4a2e242016-09-19 01:13:141384 self.WriteJSON(gn_inp, gn_input_path)
1385 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
Debrian Figueroaae51d0d2019-07-22 18:04:111386 ret, output, _ = self.Run(cmd, force_verbose=True)
dprankecb4a2e242016-09-19 01:13:141387 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:111388 if self.args.json_output:
1389 # write errors to json.output
1390 self.WriteJSON({'output': output}, self.args.json_output)
dprankecb4a2e242016-09-19 01:13:141391 return ret
dpranke067d0142015-05-14 22:52:451392
dprankecb4a2e242016-09-19 01:13:141393 gn_outp_str = self.ReadFile(gn_output_path)
1394 try:
1395 gn_outp = json.loads(gn_outp_str)
1396 except Exception as e:
1397 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1398 % (repr(gn_outp_str), str(e)))
1399 raise
1400
1401 outp = {}
1402 if 'status' in gn_outp:
1403 outp['status'] = gn_outp['status']
1404 if 'error' in gn_outp:
1405 outp['error'] = gn_outp['error']
1406 if 'invalid_targets' in gn_outp:
1407 outp['invalid_targets'] = gn_outp['invalid_targets']
1408 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491409 all_input_compile_targets = sorted(
1410 set(inp['test_targets'] + inp['additional_compile_targets']))
1411
1412 # If we're building 'all', we can throw away the rest of the targets
1413 # since they're redundant.
dpranke385a3102016-09-20 22:04:081414 if 'all' in gn_outp['compile_targets']:
1415 outp['compile_targets'] = ['all']
1416 else:
Dirk Pranke45165072017-11-08 04:57:491417 outp['compile_targets'] = gn_outp['compile_targets']
1418
1419 # crbug.com/736215: When GN returns targets back, for targets in
1420 # the default toolchain, GN will have generated a phony ninja
1421 # target matching the label, and so we can safely (and easily)
1422 # transform any GN label into the matching ninja target. For
1423 # targets in other toolchains, though, GN doesn't generate the
1424 # phony targets, and we don't know how to turn the labels into
1425 # compile targets. In this case, we also conservatively give up
1426 # and build everything. Probably the right thing to do here is
1427 # to have GN return the compile targets directly.
1428 if any("(" in target for target in outp['compile_targets']):
1429 self.Print('WARNING: targets with non-default toolchains were '
1430 'found, building everything instead.')
1431 outp['compile_targets'] = all_input_compile_targets
1432 else:
dpranke385a3102016-09-20 22:04:081433 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491434 label.replace('//', '') for label in outp['compile_targets']]
1435
1436 # Windows has a maximum command line length of 8k; even Linux
1437 # maxes out at 128k; if analyze returns a *really long* list of
1438 # targets, we just give up and conservatively build everything instead.
1439 # Probably the right thing here is for ninja to support response
1440 # files as input on the command line
1441 # (see https://github.com/ninja-build/ninja/issues/1355).
1442 if len(' '.join(outp['compile_targets'])) > 7*1024:
1443 self.Print('WARNING: Too many compile targets were affected.')
1444 self.Print('WARNING: Building everything instead to avoid '
1445 'command-line length issues.')
1446 outp['compile_targets'] = all_input_compile_targets
1447
1448
dprankecb4a2e242016-09-19 01:13:141449 if 'test_targets' in gn_outp:
1450 outp['test_targets'] = [
1451 labels_to_targets[label] for label in gn_outp['test_targets']]
1452
1453 if self.args.verbose:
1454 self.Print()
1455 self.Print('analyze output:')
1456 self.PrintJSON(outp)
1457 self.Print()
1458
1459 self.WriteJSON(outp, output_path)
1460
dprankef61de2f2015-05-14 04:09:561461 finally:
dprankecb4a2e242016-09-19 01:13:141462 if self.Exists(gn_input_path):
1463 self.RemoveFile(gn_input_path)
1464 if self.Exists(gn_output_path):
1465 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351466
1467 return 0
1468
dpranked8113582015-06-05 20:08:251469 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301470 path = self.args.input_path
1471 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351472 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321473 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351474
1475 try:
1476 inp = json.loads(self.ReadFile(path))
1477 except Exception as e:
1478 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321479 (path, e), output_path)
dpranked8113582015-06-05 20:08:251480
1481 for k in required_keys:
1482 if not k in inp:
1483 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1484 output_path)
dprankefe4602312015-04-08 16:20:351485
1486 return inp
1487
dpranked5b2b9432015-06-23 16:55:301488 def WriteFailureAndRaise(self, msg, output_path):
1489 if output_path:
dprankee0547cd2015-09-15 01:27:401490 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351491 raise MBErr(msg)
1492
dprankee0547cd2015-09-15 01:27:401493 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321494 try:
dprankee0547cd2015-09-15 01:27:401495 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1496 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321497 except Exception as e:
1498 raise MBErr('Error %s writing to the output path "%s"' %
1499 (e, path))
dprankefe4602312015-04-08 16:20:351500
aneeshmde50f472016-04-01 01:13:101501 def CheckCompile(self, master, builder):
1502 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1503 url = urllib2.quote(url_template.format(master=master, builder=builder),
1504 safe=':/()?=')
1505 try:
1506 builds = json.loads(self.Fetch(url))
1507 except Exception as e:
1508 return str(e)
1509 successes = sorted(
1510 [int(x) for x in builds.keys() if "text" in builds[x] and
1511 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1512 reverse=True)
1513 if not successes:
1514 return "no successful builds"
1515 build = builds[str(successes[0])]
1516 step_names = set([step["name"] for step in build["steps"]])
1517 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1518 if compile_indicators & step_names:
1519 return "compiles"
1520 return "does not compile"
1521
dpranke3cec199c2015-09-22 23:29:021522 def PrintCmd(self, cmd, env):
1523 if self.platform == 'win32':
1524 env_prefix = 'set '
1525 env_quoter = QuoteForSet
1526 shell_quoter = QuoteForCmd
1527 else:
1528 env_prefix = ''
1529 env_quoter = pipes.quote
1530 shell_quoter = pipes.quote
1531
1532 def print_env(var):
1533 if env and var in env:
1534 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1535
dprankeec079262016-06-07 02:21:201536 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021537
dpranke8c2cfd32015-09-17 20:12:331538 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351539 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021540 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351541
dprankecda00332015-04-11 04:18:321542 def PrintJSON(self, obj):
1543 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1544
dpranke751516a2015-10-03 01:11:341545 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301546 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381547 if self.platform == 'win32':
1548 # On Windows use the batch script since there is no exe
1549 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1550 else:
1551 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341552 if self.args.jobs:
1553 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1554 ninja_cmd.append(target)
Dirk Pranke5f22a822019-05-23 22:55:251555 ret, _, _ = self.Run(ninja_cmd, buffer_output=False)
dpranke751516a2015-10-03 01:11:341556 return ret
1557
1558 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351559 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401560 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021561 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351562 if self.args.dryrun:
1563 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401564
dpranke751516a2015-10-03 01:11:341565 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401566 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341567 if ret:
1568 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351569 if out:
Debrian Figueroaae582232019-07-17 01:54:451570 # This is the error seen on the logs
dprankeee5b51f62015-04-09 00:03:221571 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351572 if err:
dprankeee5b51f62015-04-09 00:03:221573 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351574 return ret, out, err
1575
dpranke751516a2015-10-03 01:11:341576 def Call(self, cmd, env=None, buffer_output=True):
1577 if buffer_output:
1578 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1579 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1580 env=env)
1581 out, err = p.communicate()
1582 else:
1583 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1584 env=env)
1585 p.wait()
1586 out = err = ''
dprankefe4602312015-04-08 16:20:351587 return p.returncode, out, err
1588
1589 def ExpandUser(self, path):
1590 # This function largely exists so it can be overridden for testing.
1591 return os.path.expanduser(path)
1592
1593 def Exists(self, path):
1594 # This function largely exists so it can be overridden for testing.
1595 return os.path.exists(path)
1596
dpranke867bcf4a2016-03-14 22:28:321597 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501598 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321599 f = urllib2.urlopen(url)
1600 contents = f.read()
1601 f.close()
1602 return contents
1603
dprankec3441d12015-06-23 23:01:351604 def MaybeMakeDirectory(self, path):
1605 try:
1606 os.makedirs(path)
1607 except OSError, e:
1608 if e.errno != errno.EEXIST:
1609 raise
1610
dpranke8c2cfd32015-09-17 20:12:331611 def PathJoin(self, *comps):
1612 # This function largely exists so it can be overriden for testing.
1613 return os.path.join(*comps)
1614
dpranke030d7a6d2016-03-26 17:23:501615 def Print(self, *args, **kwargs):
1616 # This function largely exists so it can be overridden for testing.
1617 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101618 if kwargs.get('stream', sys.stdout) == sys.stdout:
1619 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501620
dprankefe4602312015-04-08 16:20:351621 def ReadFile(self, path):
1622 # This function largely exists so it can be overriden for testing.
1623 with open(path) as fp:
1624 return fp.read()
1625
dpranke030d7a6d2016-03-26 17:23:501626 def RelPath(self, path, start='.'):
1627 # This function largely exists so it can be overriden for testing.
1628 return os.path.relpath(path, start)
1629
dprankef61de2f2015-05-14 04:09:561630 def RemoveFile(self, path):
1631 # This function largely exists so it can be overriden for testing.
1632 os.remove(path)
1633
dprankec161aa92015-09-14 20:21:131634 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331635 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131636 # In other places in chromium, we often have to retry this command
1637 # because we're worried about other processes still holding on to
1638 # file handles, but when MB is invoked, it will be early enough in the
1639 # build that their should be no other processes to interfere. We
1640 # can change this if need be.
1641 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1642 else:
1643 shutil.rmtree(abs_path, ignore_errors=True)
1644
Dirk Prankef24e6b22018-03-27 20:12:301645 def TempDir(self):
1646 # This function largely exists so it can be overriden for testing.
1647 return tempfile.mkdtemp(prefix='mb_')
1648
dprankef61de2f2015-05-14 04:09:561649 def TempFile(self, mode='w'):
1650 # This function largely exists so it can be overriden for testing.
1651 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1652
dprankee0547cd2015-09-15 01:27:401653 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351654 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401655 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301656 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351657 with open(path, 'w') as fp:
1658 return fp.write(contents)
1659
dprankef61de2f2015-05-14 04:09:561660
dprankefe4602312015-04-08 16:20:351661class MBErr(Exception):
1662 pass
1663
1664
dpranke3cec199c2015-09-22 23:29:021665# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1666# details of this next section, which handles escaping command lines
1667# so that they can be copied and pasted into a cmd window.
1668UNSAFE_FOR_SET = set('^<>&|')
1669UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1670ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1671
1672
1673def QuoteForSet(arg):
1674 if any(a in UNSAFE_FOR_SET for a in arg):
1675 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1676 return arg
1677
1678
1679def QuoteForCmd(arg):
1680 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021681 if arg == '' or ' ' in arg or '"' in arg:
1682 quote_re = re.compile(r'(\\*)"')
1683 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1684
1685 # Then check to see if the arg contains any metacharacters other than
1686 # double quotes; if it does, quote everything (including the double
1687 # quotes) for safety.
1688 if any(a in UNSAFE_FOR_CMD for a in arg):
1689 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1690 return arg
1691
1692
dprankefe4602312015-04-08 16:20:351693if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591694 sys.exit(main(sys.argv[1:]))