blob: df48080e80b7bdf29639a17be8fed73247862960 [file] [log] [blame]
dprankefe4602312015-04-08 16:20:351#!/usr/bin/env python
2# Copyright 2015 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Dirk Pranke8cb6aa782017-12-16 02:31:336"""MB - the Meta-Build wrapper around GN.
dprankefe4602312015-04-08 16:20:357
Dirk Pranked181a1a2017-12-14 01:47:118MB is a wrapper script for GN that can be used to generate build files
dprankefe4602312015-04-08 16:20:359for sets of canned configurations and analyze them.
10"""
11
12from __future__ import print_function
13
14import argparse
15import ast
dprankec3441d12015-06-23 23:01:3516import errno
dprankefe4602312015-04-08 16:20:3517import json
18import os
dpranke68d1cb182015-09-17 23:30:0019import pipes
Dirk Pranke8cb6aa782017-12-16 02:31:3320import platform
dpranked8113582015-06-05 20:08:2521import pprint
dpranke3cec199c2015-09-22 23:29:0222import re
dprankefe4602312015-04-08 16:20:3523import shutil
24import sys
25import subprocess
dprankef61de2f2015-05-14 04:09:5626import tempfile
dprankebbe6d4672016-04-19 06:56:5727import traceback
dpranke867bcf4a2016-03-14 22:28:3228import urllib2
Dirk Prankef24e6b22018-03-27 20:12:3029import zipfile
dpranke867bcf4a2016-03-14 22:28:3230
31from collections import OrderedDict
dprankefe4602312015-04-08 16:20:3532
dprankeeca4a782016-04-14 01:42:3833CHROMIUM_SRC_DIR = os.path.dirname(os.path.dirname(os.path.dirname(
34 os.path.abspath(__file__))))
35sys.path = [os.path.join(CHROMIUM_SRC_DIR, 'build')] + sys.path
36
37import gn_helpers
38
39
dprankefe4602312015-04-08 16:20:3540def main(args):
dprankeee5b51f62015-04-09 00:03:2241 mbw = MetaBuildWrapper()
dpranke255085e2016-03-16 05:23:5942 return mbw.Main(args)
dprankefe4602312015-04-08 16:20:3543
44
45class MetaBuildWrapper(object):
46 def __init__(self):
dprankeeca4a782016-04-14 01:42:3847 self.chromium_src_dir = CHROMIUM_SRC_DIR
48 self.default_config = os.path.join(self.chromium_src_dir, 'tools', 'mb',
49 'mb_config.pyl')
kjellander902bcb62016-10-26 06:20:5050 self.default_isolate_map = os.path.join(self.chromium_src_dir, 'testing',
51 'buildbot', 'gn_isolate_map.pyl')
dpranke8c2cfd32015-09-17 20:12:3352 self.executable = sys.executable
dpranked1fba482015-04-14 20:54:5153 self.platform = sys.platform
dpranke8c2cfd32015-09-17 20:12:3354 self.sep = os.sep
dprankefe4602312015-04-08 16:20:3555 self.args = argparse.Namespace()
56 self.configs = {}
dprankefe4602312015-04-08 16:20:3557 self.masters = {}
58 self.mixins = {}
dprankefe4602312015-04-08 16:20:3559
dpranke255085e2016-03-16 05:23:5960 def Main(self, args):
61 self.ParseArgs(args)
62 try:
63 ret = self.args.func()
64 if ret:
65 self.DumpInputFiles()
66 return ret
67 except KeyboardInterrupt:
dprankecb4a2e242016-09-19 01:13:1468 self.Print('interrupted, exiting')
dpranke255085e2016-03-16 05:23:5969 return 130
dprankebbe6d4672016-04-19 06:56:5770 except Exception:
dpranke255085e2016-03-16 05:23:5971 self.DumpInputFiles()
dprankebbe6d4672016-04-19 06:56:5772 s = traceback.format_exc()
73 for l in s.splitlines():
74 self.Print(l)
dpranke255085e2016-03-16 05:23:5975 return 1
76
dprankefe4602312015-04-08 16:20:3577 def ParseArgs(self, argv):
78 def AddCommonOptions(subp):
79 subp.add_argument('-b', '--builder',
80 help='builder name to look up config from')
81 subp.add_argument('-m', '--master',
82 help='master name to look up config from')
83 subp.add_argument('-c', '--config',
84 help='configuration to analyze')
shenghuazhang804b21542016-10-11 02:06:4985 subp.add_argument('--phase',
86 help='optional phase name (used when builders '
87 'do multiple compiles with different '
88 'arguments in a single build)')
dprankefe4602312015-04-08 16:20:3589 subp.add_argument('-f', '--config-file', metavar='PATH',
90 default=self.default_config,
91 help='path to config file '
kjellander902bcb62016-10-26 06:20:5092 '(default is %(default)s)')
93 subp.add_argument('-i', '--isolate-map-file', metavar='PATH',
kjellander902bcb62016-10-26 06:20:5094 help='path to isolate map file '
Zhiling Huang66958462018-02-03 00:28:2095 '(default is %(default)s)',
96 default=[],
97 action='append',
98 dest='isolate_map_files')
dpranked0c138b2016-04-13 18:28:4799 subp.add_argument('-g', '--goma-dir',
100 help='path to goma directory')
agrieve41d21a72016-04-14 18:02:26101 subp.add_argument('--android-version-code',
Dirk Pranked181a1a2017-12-14 01:47:11102 help='Sets GN arg android_default_version_code')
agrieve41d21a72016-04-14 18:02:26103 subp.add_argument('--android-version-name',
Dirk Pranked181a1a2017-12-14 01:47:11104 help='Sets GN arg android_default_version_name')
dprankefe4602312015-04-08 16:20:35105 subp.add_argument('-n', '--dryrun', action='store_true',
106 help='Do a dry run (i.e., do nothing, just print '
107 'the commands that will run)')
dprankee0547cd2015-09-15 01:27:40108 subp.add_argument('-v', '--verbose', action='store_true',
109 help='verbose logging')
dprankefe4602312015-04-08 16:20:35110
111 parser = argparse.ArgumentParser(prog='mb')
112 subps = parser.add_subparsers()
113
114 subp = subps.add_parser('analyze',
115 help='analyze whether changes to a set of files '
116 'will cause a set of binaries to be rebuilt.')
117 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30118 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35119 help='path build was generated into.')
Dirk Prankef24e6b22018-03-27 20:12:30120 subp.add_argument('input_path',
dprankefe4602312015-04-08 16:20:35121 help='path to a file containing the input arguments '
122 'as a JSON object.')
Dirk Prankef24e6b22018-03-27 20:12:30123 subp.add_argument('output_path',
dprankefe4602312015-04-08 16:20:35124 help='path to a file containing the output arguments '
125 'as a JSON object.')
126 subp.set_defaults(func=self.CmdAnalyze)
127
dprankef37aebb92016-09-23 01:14:49128 subp = subps.add_parser('export',
129 help='print out the expanded configuration for'
130 'each builder as a JSON object')
131 subp.add_argument('-f', '--config-file', metavar='PATH',
132 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50133 help='path to config file (default is %(default)s)')
dprankef37aebb92016-09-23 01:14:49134 subp.add_argument('-g', '--goma-dir',
135 help='path to goma directory')
136 subp.set_defaults(func=self.CmdExport)
137
dprankefe4602312015-04-08 16:20:35138 subp = subps.add_parser('gen',
139 help='generate a new set of build files')
140 AddCommonOptions(subp)
dpranke74559b52015-06-10 21:20:39141 subp.add_argument('--swarming-targets-file',
142 help='save runtime dependencies for targets listed '
143 'in file.')
Dirk Prankef24e6b22018-03-27 20:12:30144 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35145 help='path to generate build into')
146 subp.set_defaults(func=self.CmdGen)
147
dpranke751516a2015-10-03 01:11:34148 subp = subps.add_parser('isolate',
149 help='generate the .isolate files for a given'
150 'binary')
151 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30152 subp.add_argument('--no-build', dest='build', default=True,
153 action='store_false',
154 help='Do not build, just isolate')
155 subp.add_argument('-j', '--jobs', type=int,
156 help='Number of jobs to pass to ninja')
157 subp.add_argument('path',
dpranke751516a2015-10-03 01:11:34158 help='path build was generated into')
Dirk Prankef24e6b22018-03-27 20:12:30159 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34160 help='ninja target to generate the isolate for')
161 subp.set_defaults(func=self.CmdIsolate)
162
dprankefe4602312015-04-08 16:20:35163 subp = subps.add_parser('lookup',
164 help='look up the command for a given config or '
165 'builder')
166 AddCommonOptions(subp)
167 subp.set_defaults(func=self.CmdLookup)
168
dpranke030d7a6d2016-03-26 17:23:50169 subp = subps.add_parser(
170 'run',
171 help='build and run the isolated version of a '
172 'binary',
173 formatter_class=argparse.RawDescriptionHelpFormatter)
174 subp.description = (
175 'Build, isolate, and run the given binary with the command line\n'
176 'listed in the isolate. You may pass extra arguments after the\n'
177 'target; use "--" if the extra arguments need to include switches.\n'
178 '\n'
179 'Examples:\n'
180 '\n'
181 ' % tools/mb/mb.py run -m chromium.linux -b "Linux Builder" \\\n'
182 ' //out/Default content_browsertests\n'
183 '\n'
184 ' % tools/mb/mb.py run out/Default content_browsertests\n'
185 '\n'
186 ' % tools/mb/mb.py run out/Default content_browsertests -- \\\n'
187 ' --test-launcher-retry-limit=0'
188 '\n'
189 )
dpranke751516a2015-10-03 01:11:34190 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30191 subp.add_argument('-j', '--jobs', type=int,
dpranke751516a2015-10-03 01:11:34192 help='Number of jobs to pass to ninja')
193 subp.add_argument('--no-build', dest='build', default=True,
194 action='store_false',
195 help='Do not build, just isolate and run')
Dirk Prankef24e6b22018-03-27 20:12:30196 subp.add_argument('path',
dpranke030d7a6d2016-03-26 17:23:50197 help=('path to generate build into (or use).'
198 ' This can be either a regular path or a '
199 'GN-style source-relative path like '
200 '//out/Default.'))
Dirk Pranke8cb6aa782017-12-16 02:31:33201 subp.add_argument('-s', '--swarmed', action='store_true',
202 help='Run under swarming with the default dimensions')
203 subp.add_argument('-d', '--dimension', default=[], action='append', nargs=2,
204 dest='dimensions', metavar='FOO bar',
205 help='dimension to filter on')
206 subp.add_argument('--no-default-dimensions', action='store_false',
207 dest='default_dimensions', default=True,
208 help='Do not automatically add dimensions to the task')
Dirk Prankef24e6b22018-03-27 20:12:30209 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34210 help='ninja target to build and run')
dpranke030d7a6d2016-03-26 17:23:50211 subp.add_argument('extra_args', nargs='*',
212 help=('extra args to pass to the isolate to run. Use '
213 '"--" as the first arg if you need to pass '
214 'switches'))
dpranke751516a2015-10-03 01:11:34215 subp.set_defaults(func=self.CmdRun)
216
dprankefe4602312015-04-08 16:20:35217 subp = subps.add_parser('validate',
218 help='validate the config file')
dprankea5a77ca2015-07-16 23:24:17219 subp.add_argument('-f', '--config-file', metavar='PATH',
220 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50221 help='path to config file (default is %(default)s)')
dprankefe4602312015-04-08 16:20:35222 subp.set_defaults(func=self.CmdValidate)
223
Dirk Prankef24e6b22018-03-27 20:12:30224 subp = subps.add_parser('zip',
225 help='generate a .zip containing the files needed '
226 'for a given binary')
227 AddCommonOptions(subp)
228 subp.add_argument('--no-build', dest='build', default=True,
229 action='store_false',
230 help='Do not build, just isolate')
231 subp.add_argument('-j', '--jobs', type=int,
232 help='Number of jobs to pass to ninja')
233 subp.add_argument('path',
234 help='path build was generated into')
235 subp.add_argument('target',
236 help='ninja target to generate the isolate for')
237 subp.add_argument('zip_path',
238 help='path to zip file to create')
239 subp.set_defaults(func=self.CmdZip)
240
dprankefe4602312015-04-08 16:20:35241 subp = subps.add_parser('help',
242 help='Get help on a subcommand.')
243 subp.add_argument(nargs='?', action='store', dest='subcommand',
244 help='The command to get help for.')
245 subp.set_defaults(func=self.CmdHelp)
246
247 self.args = parser.parse_args(argv)
248
dprankeb2be10a2016-02-22 17:11:00249 def DumpInputFiles(self):
250
dprankef7b7eb7a2016-03-28 22:42:59251 def DumpContentsOfFilePassedTo(arg_name, path):
dprankeb2be10a2016-02-22 17:11:00252 if path and self.Exists(path):
dprankef7b7eb7a2016-03-28 22:42:59253 self.Print("\n# To recreate the file passed to %s:" % arg_name)
dprankecb4a2e242016-09-19 01:13:14254 self.Print("%% cat > %s <<EOF" % path)
dprankeb2be10a2016-02-22 17:11:00255 contents = self.ReadFile(path)
dprankef7b7eb7a2016-03-28 22:42:59256 self.Print(contents)
257 self.Print("EOF\n%\n")
dprankeb2be10a2016-02-22 17:11:00258
dprankef7b7eb7a2016-03-28 22:42:59259 if getattr(self.args, 'input_path', None):
260 DumpContentsOfFilePassedTo(
Dirk Prankef24e6b22018-03-27 20:12:30261 'argv[0] (input_path)', self.args.input_path)
dprankef7b7eb7a2016-03-28 22:42:59262 if getattr(self.args, 'swarming_targets_file', None):
263 DumpContentsOfFilePassedTo(
264 '--swarming-targets-file', self.args.swarming_targets_file)
dprankeb2be10a2016-02-22 17:11:00265
dprankefe4602312015-04-08 16:20:35266 def CmdAnalyze(self):
dpranke751516a2015-10-03 01:11:34267 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11268 return self.RunGNAnalyze(vals)
dprankefe4602312015-04-08 16:20:35269
dprankef37aebb92016-09-23 01:14:49270 def CmdExport(self):
271 self.ReadConfigFile()
272 obj = {}
273 for master, builders in self.masters.items():
274 obj[master] = {}
275 for builder in builders:
276 config = self.masters[master][builder]
277 if not config:
278 continue
279
shenghuazhang804b21542016-10-11 02:06:49280 if isinstance(config, dict):
281 args = {k: self.FlattenConfig(v)['gn_args']
282 for k, v in config.items()}
dprankef37aebb92016-09-23 01:14:49283 elif config.startswith('//'):
284 args = config
285 else:
286 args = self.FlattenConfig(config)['gn_args']
287 if 'error' in args:
288 continue
289
290 obj[master][builder] = args
291
292 # Dump object and trim trailing whitespace.
293 s = '\n'.join(l.rstrip() for l in
294 json.dumps(obj, sort_keys=True, indent=2).splitlines())
295 self.Print(s)
296 return 0
297
dprankefe4602312015-04-08 16:20:35298 def CmdGen(self):
dpranke751516a2015-10-03 01:11:34299 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11300 return self.RunGNGen(vals)
dprankefe4602312015-04-08 16:20:35301
302 def CmdHelp(self):
303 if self.args.subcommand:
304 self.ParseArgs([self.args.subcommand, '--help'])
305 else:
306 self.ParseArgs(['--help'])
307
dpranke751516a2015-10-03 01:11:34308 def CmdIsolate(self):
309 vals = self.GetConfig()
310 if not vals:
311 return 1
Dirk Prankef24e6b22018-03-27 20:12:30312 if self.args.build:
313 ret = self.Build(self.args.target)
314 if ret:
315 return ret
Dirk Pranked181a1a2017-12-14 01:47:11316 return self.RunGNIsolate(vals)
dpranke751516a2015-10-03 01:11:34317
318 def CmdLookup(self):
319 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11320 cmd = self.GNCmd('gen', '_path_')
321 gn_args = self.GNArgs(vals)
322 self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args)
323 env = None
dpranke751516a2015-10-03 01:11:34324
325 self.PrintCmd(cmd, env)
326 return 0
327
328 def CmdRun(self):
329 vals = self.GetConfig()
330 if not vals:
331 return 1
Dirk Pranked181a1a2017-12-14 01:47:11332 if self.args.build:
Dirk Prankef24e6b22018-03-27 20:12:30333 ret = self.Build(self.args.target)
dpranke751516a2015-10-03 01:11:34334 if ret:
335 return ret
Dirk Pranked181a1a2017-12-14 01:47:11336 ret = self.RunGNIsolate(vals)
337 if ret:
338 return ret
dpranke751516a2015-10-03 01:11:34339
Dirk Pranke8cb6aa782017-12-16 02:31:33340 if self.args.swarmed:
Dirk Prankef24e6b22018-03-27 20:12:30341 return self._RunUnderSwarming(self.args.path, self.args.target)
Dirk Pranke8cb6aa782017-12-16 02:31:33342 else:
Dirk Prankef24e6b22018-03-27 20:12:30343 return self._RunLocallyIsolated(self.args.path, self.args.target)
344
345 def CmdZip(self):
346 ret = self.CmdIsolate()
347 if ret:
348 return ret
349
350 zip_dir = None
351 try:
352 zip_dir = self.TempDir()
353 remap_cmd = [
354 self.executable,
355 self.PathJoin(self.chromium_src_dir, 'tools', 'swarming_client',
356 'isolate.py'),
357 'remap',
358 '-s', self.PathJoin(self.args.path, self.args.target + '.isolated'),
359 '-o', zip_dir
360 ]
361 self.Run(remap_cmd)
362
363 zip_path = self.args.zip_path
364 with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as fp:
365 for root, _, files in os.walk(zip_dir):
366 for filename in files:
367 path = self.PathJoin(root, filename)
368 fp.write(path, self.RelPath(path, zip_dir))
369 finally:
370 if zip_dir:
371 self.RemoveDirectory(zip_dir)
Dirk Pranke8cb6aa782017-12-16 02:31:33372
Robert Iannucci5a9d75f62018-03-02 05:28:20373 @staticmethod
374 def _AddBaseSoftware(cmd):
375 # HACK(iannucci): These packages SHOULD NOT BE HERE.
376 # Remove method once Swarming Pool Task Templates are implemented.
377 # crbug.com/812428
378
379 # Add in required base software. This should be kept in sync with the
380 # `swarming` recipe module in build.git. All references to `swarming_module`
381 # below are purely due to this.
382 cipd_packages = [
383 ('infra/python/cpython/${platform}',
384 'version:2.7.14.chromium14'),
385 ('infra/tools/luci/logdog/butler/${platform}',
386 'git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c'),
387 ('infra/tools/luci/vpython-native/${platform}',
Vadim Shtayura97df7752018-06-26 22:38:18388 'git_revision:aacec5af4d569cee51437a89425db0b3c3d39a0a'),
Robert Iannucci5a9d75f62018-03-02 05:28:20389 ('infra/tools/luci/vpython/${platform}',
Vadim Shtayura97df7752018-06-26 22:38:18390 'git_revision:aacec5af4d569cee51437a89425db0b3c3d39a0a'),
Robert Iannucci5a9d75f62018-03-02 05:28:20391 ]
392 for pkg, vers in cipd_packages:
393 cmd.append('--cipd-package=.swarming_module:%s:%s' % (pkg, vers))
394
395 # Add packages to $PATH
396 cmd.extend([
397 '--env-prefix=PATH', '.swarming_module',
398 '--env-prefix=PATH', '.swarming_module/bin',
399 ])
400
401 # Add cache directives for vpython.
402 vpython_cache_path = '.swarming_module_cache/vpython'
403 cmd.extend([
404 '--named-cache=swarming_module_cache_vpython', vpython_cache_path,
405 '--env-prefix=VPYTHON_VIRTUALENV_ROOT', vpython_cache_path,
406 ])
407
Dirk Pranke8cb6aa782017-12-16 02:31:33408 def _RunUnderSwarming(self, build_dir, target):
409 # TODO(dpranke): Look up the information for the target in
410 # the //testing/buildbot.json file, if possible, so that we
411 # can determine the isolate target, command line, and additional
412 # swarming parameters, if possible.
413 #
414 # TODO(dpranke): Also, add support for sharding and merging results.
415 dimensions = []
416 for k, v in self._DefaultDimensions() + self.args.dimensions:
417 dimensions += ['-d', k, v]
418
419 cmd = [
420 self.executable,
421 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
422 'archive',
423 '-s',
424 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
425 '-I', 'isolateserver.appspot.com',
426 ]
427 ret, out, _ = self.Run(cmd, force_verbose=False)
428 if ret:
429 return ret
430
431 isolated_hash = out.splitlines()[0].split()[0]
432 cmd = [
433 self.executable,
434 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
435 'run',
436 '-s', isolated_hash,
437 '-I', 'isolateserver.appspot.com',
438 '-S', 'chromium-swarm.appspot.com',
439 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20440 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33441 if self.args.extra_args:
442 cmd += ['--'] + self.args.extra_args
443 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
444 return ret
445
446 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50447 cmd = [
dpranke751516a2015-10-03 01:11:34448 self.executable,
449 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
450 'run',
451 '-s',
dpranke030d7a6d2016-03-26 17:23:50452 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33453 ]
dpranke030d7a6d2016-03-26 17:23:50454 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33455 cmd += ['--'] + self.args.extra_args
456 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34457 return ret
458
Dirk Pranke8cb6aa782017-12-16 02:31:33459 def _DefaultDimensions(self):
460 if not self.args.default_dimensions:
461 return []
462
463 # This code is naive and just picks reasonable defaults per platform.
464 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40465 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33466 elif self.platform.startswith('linux'):
467 os_dim = ('os', 'Ubuntu-14.04')
468 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40469 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33470 else:
471 raise MBErr('unrecognized platform string "%s"' % self.platform)
472
473 return [('pool', 'Chrome'),
474 ('cpu', 'x86-64'),
475 os_dim]
476
dpranke0cafc162016-03-19 00:41:10477 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35478 errs = []
479
480 # Read the file to make sure it parses.
481 self.ReadConfigFile()
482
dpranke3be00142016-03-17 22:46:04483 # Build a list of all of the configs referenced by builders.
dprankefe4602312015-04-08 16:20:35484 all_configs = {}
dprankefe4602312015-04-08 16:20:35485 for master in self.masters:
dpranke3be00142016-03-17 22:46:04486 for config in self.masters[master].values():
shenghuazhang804b21542016-10-11 02:06:49487 if isinstance(config, dict):
488 for c in config.values():
dprankeb9380a12016-07-21 21:44:09489 all_configs[c] = master
490 else:
491 all_configs[config] = master
dprankefe4602312015-04-08 16:20:35492
dpranke9dd5e252016-04-14 04:23:09493 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35494 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09495 if config.startswith('//'):
496 if not self.Exists(self.ToAbsPath(config)):
497 errs.append('Unknown args file "%s" referenced from "%s".' %
498 (config, loc))
499 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35500 errs.append('Unknown config "%s" referenced from "%s".' %
501 (config, loc))
502
503 # Check that every actual config is actually referenced.
504 for config in self.configs:
505 if not config in all_configs:
506 errs.append('Unused config "%s".' % config)
507
508 # Figure out the whole list of mixins, and check that every mixin
509 # listed by a config or another mixin actually exists.
510 referenced_mixins = set()
511 for config, mixins in self.configs.items():
512 for mixin in mixins:
513 if not mixin in self.mixins:
514 errs.append('Unknown mixin "%s" referenced by config "%s".' %
515 (mixin, config))
516 referenced_mixins.add(mixin)
517
518 for mixin in self.mixins:
519 for sub_mixin in self.mixins[mixin].get('mixins', []):
520 if not sub_mixin in self.mixins:
521 errs.append('Unknown mixin "%s" referenced by mixin "%s".' %
522 (sub_mixin, mixin))
523 referenced_mixins.add(sub_mixin)
524
525 # Check that every mixin defined is actually referenced somewhere.
526 for mixin in self.mixins:
527 if not mixin in referenced_mixins:
528 errs.append('Unreferenced mixin "%s".' % mixin)
529
dpranke255085e2016-03-16 05:23:59530 # If we're checking the Chromium config, check that the 'chromium' bots
531 # which build public artifacts do not include the chrome_with_codecs mixin.
532 if self.args.config_file == self.default_config:
533 if 'chromium' in self.masters:
534 for builder in self.masters['chromium']:
535 config = self.masters['chromium'][builder]
536 def RecurseMixins(current_mixin):
537 if current_mixin == 'chrome_with_codecs':
538 errs.append('Public artifact builder "%s" can not contain the '
539 '"chrome_with_codecs" mixin.' % builder)
540 return
541 if not 'mixins' in self.mixins[current_mixin]:
542 return
543 for mixin in self.mixins[current_mixin]['mixins']:
544 RecurseMixins(mixin)
dalecurtis56fd27e2016-03-09 23:06:41545
dpranke255085e2016-03-16 05:23:59546 for mixin in self.configs[config]:
547 RecurseMixins(mixin)
548 else:
549 errs.append('Missing "chromium" master. Please update this '
550 'proprietary codecs check with the name of the master '
551 'responsible for public build artifacts.')
dalecurtis56fd27e2016-03-09 23:06:41552
dprankefe4602312015-04-08 16:20:35553 if errs:
dpranke4323c80632015-08-10 22:53:54554 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17555 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35556
dpranke0cafc162016-03-19 00:41:10557 if print_ok:
558 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35559 return 0
560
561 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30562 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34563
dprankef37aebb92016-09-23 01:14:49564 vals = self.DefaultVals()
dpranke751516a2015-10-03 01:11:34565 if self.args.builder or self.args.master or self.args.config:
566 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11567 # Re-run gn gen in order to ensure the config is consistent with the
568 # build dir.
569 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34570 return vals
571
Dirk Pranked181a1a2017-12-14 01:47:11572 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
573 'toolchain.ninja')
574 if not self.Exists(toolchain_path):
575 self.Print('Must either specify a path to an existing GN build dir '
576 'or pass in a -m/-b pair or a -c flag to specify the '
577 'configuration')
578 return {}
dpranke751516a2015-10-03 01:11:34579
Dirk Pranked181a1a2017-12-14 01:47:11580 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34581 return vals
582
dprankef37aebb92016-09-23 01:14:49583 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58584 args_contents = ""
585 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
586 if self.Exists(gn_args_path):
587 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34588 gn_args = []
589 for l in args_contents.splitlines():
590 fields = l.split(' ')
591 name = fields[0]
592 val = ' '.join(fields[2:])
593 gn_args.append('%s=%s' % (name, val))
594
dprankef37aebb92016-09-23 01:14:49595 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34596
597 def Lookup(self):
dprankef37aebb92016-09-23 01:14:49598 vals = self.ReadIOSBotConfig()
dprankee0f486f2015-11-19 23:42:00599 if not vals:
600 self.ReadConfigFile()
601 config = self.ConfigFromArgs()
dpranke9dd5e252016-04-14 04:23:09602 if config.startswith('//'):
603 if not self.Exists(self.ToAbsPath(config)):
604 raise MBErr('args file "%s" not found' % config)
dprankef37aebb92016-09-23 01:14:49605 vals = self.DefaultVals()
606 vals['args_file'] = config
dpranke9dd5e252016-04-14 04:23:09607 else:
608 if not config in self.configs:
609 raise MBErr('Config "%s" not found in %s' %
610 (config, self.args.config_file))
611 vals = self.FlattenConfig(config)
dpranke751516a2015-10-03 01:11:34612 return vals
dprankefe4602312015-04-08 16:20:35613
dprankef37aebb92016-09-23 01:14:49614 def ReadIOSBotConfig(self):
dprankee0f486f2015-11-19 23:42:00615 if not self.args.master or not self.args.builder:
616 return {}
617 path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
618 self.args.master, self.args.builder + '.json')
619 if not self.Exists(path):
620 return {}
621
622 contents = json.loads(self.ReadFile(path))
dprankee0f486f2015-11-19 23:42:00623 gn_args = ' '.join(contents.get('gn_args', []))
624
dprankef37aebb92016-09-23 01:14:49625 vals = self.DefaultVals()
626 vals['gn_args'] = gn_args
dprankef37aebb92016-09-23 01:14:49627 return vals
dprankee0f486f2015-11-19 23:42:00628
dprankefe4602312015-04-08 16:20:35629 def ReadConfigFile(self):
630 if not self.Exists(self.args.config_file):
631 raise MBErr('config file not found at %s' % self.args.config_file)
632
633 try:
634 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
635 except SyntaxError as e:
636 raise MBErr('Failed to parse config file "%s": %s' %
637 (self.args.config_file, e))
638
dprankefe4602312015-04-08 16:20:35639 self.configs = contents['configs']
dprankefe4602312015-04-08 16:20:35640 self.masters = contents['masters']
641 self.mixins = contents['mixins']
dprankefe4602312015-04-08 16:20:35642
dprankecb4a2e242016-09-19 01:13:14643 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20644 if not self.args.isolate_map_files:
645 self.args.isolate_map_files = [self.default_isolate_map]
646
647 for f in self.args.isolate_map_files:
648 if not self.Exists(f):
649 raise MBErr('isolate map file not found at %s' % f)
650 isolate_maps = {}
651 for isolate_map in self.args.isolate_map_files:
652 try:
653 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
654 duplicates = set(isolate_map).intersection(isolate_maps)
655 if duplicates:
656 raise MBErr(
657 'Duplicate targets in isolate map files: %s.' %
658 ', '.join(duplicates))
659 isolate_maps.update(isolate_map)
660 except SyntaxError as e:
661 raise MBErr(
662 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
663 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14664
dprankefe4602312015-04-08 16:20:35665 def ConfigFromArgs(self):
666 if self.args.config:
667 if self.args.master or self.args.builder:
668 raise MBErr('Can not specific both -c/--config and -m/--master or '
669 '-b/--builder')
670
671 return self.args.config
672
673 if not self.args.master or not self.args.builder:
674 raise MBErr('Must specify either -c/--config or '
675 '(-m/--master and -b/--builder)')
676
677 if not self.args.master in self.masters:
678 raise MBErr('Master name "%s" not found in "%s"' %
679 (self.args.master, self.args.config_file))
680
681 if not self.args.builder in self.masters[self.args.master]:
682 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
683 (self.args.builder, self.args.master, self.args.config_file))
684
dprankeb9380a12016-07-21 21:44:09685 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49686 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09687 if self.args.phase is None:
688 raise MBErr('Must specify a build --phase for %s on %s' %
689 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49690 phase = str(self.args.phase)
691 if phase not in config:
692 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09693 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49694 return config[phase]
dprankeb9380a12016-07-21 21:44:09695
696 if self.args.phase is not None:
697 raise MBErr('Must not specify a build --phase for %s on %s' %
698 (self.args.builder, self.args.master))
699 return config
dprankefe4602312015-04-08 16:20:35700
701 def FlattenConfig(self, config):
702 mixins = self.configs[config]
dprankef37aebb92016-09-23 01:14:49703 vals = self.DefaultVals()
dprankefe4602312015-04-08 16:20:35704
705 visited = []
706 self.FlattenMixins(mixins, vals, visited)
707 return vals
708
dprankef37aebb92016-09-23 01:14:49709 def DefaultVals(self):
710 return {
711 'args_file': '',
712 'cros_passthrough': False,
713 'gn_args': '',
dprankef37aebb92016-09-23 01:14:49714 }
715
dprankefe4602312015-04-08 16:20:35716 def FlattenMixins(self, mixins, vals, visited):
717 for m in mixins:
718 if m not in self.mixins:
719 raise MBErr('Unknown mixin "%s"' % m)
dprankeee5b51f62015-04-09 00:03:22720
dprankefe4602312015-04-08 16:20:35721 visited.append(m)
722
723 mixin_vals = self.mixins[m]
dpranke73ed0d62016-04-25 19:18:34724
725 if 'cros_passthrough' in mixin_vals:
726 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
Dirk Pranke6b99f072017-04-05 00:58:30727 if 'args_file' in mixin_vals:
728 if vals['args_file']:
729 raise MBErr('args_file specified multiple times in mixins '
730 'for %s on %s' % (self.args.builder, self.args.master))
731 vals['args_file'] = mixin_vals['args_file']
dprankefe4602312015-04-08 16:20:35732 if 'gn_args' in mixin_vals:
733 if vals['gn_args']:
734 vals['gn_args'] += ' ' + mixin_vals['gn_args']
735 else:
736 vals['gn_args'] = mixin_vals['gn_args']
dpranke73ed0d62016-04-25 19:18:34737
dprankefe4602312015-04-08 16:20:35738 if 'mixins' in mixin_vals:
739 self.FlattenMixins(mixin_vals['mixins'], vals, visited)
740 return vals
741
Andrew Grieve0bb79bb2018-06-27 03:14:09742 def RunGNGen(self, vals, compute_inputs_for_analyze=False):
Dirk Prankef24e6b22018-03-27 20:12:30743 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50744
dprankeeca4a782016-04-14 01:42:38745 cmd = self.GNCmd('gen', build_dir, '--check')
746 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:09747 if compute_inputs_for_analyze:
748 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:38749
750 # Since GN hasn't run yet, the build directory may not even exist.
751 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
752
753 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:54754 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:39755
756 swarming_targets = []
dpranke751516a2015-10-03 01:11:34757 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:39758 # We need GN to generate the list of runtime dependencies for
759 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:14760 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:39761 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:00762 path = self.args.swarming_targets_file
763 if not self.Exists(path):
764 self.WriteFailureAndRaise('"%s" does not exist' % path,
765 output_path=None)
766 contents = self.ReadFile(path)
767 swarming_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:00768
dprankecb4a2e242016-09-19 01:13:14769 isolate_map = self.ReadIsolateMap()
770 err, labels = self.MapTargetsToLabels(isolate_map, swarming_targets)
dprankeb2be10a2016-02-22 17:11:00771 if err:
dprankecb4a2e242016-09-19 01:13:14772 raise MBErr(err)
dpranke74559b52015-06-10 21:20:39773
dpranke751516a2015-10-03 01:11:34774 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:14775 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:39776 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
777
dprankefe4602312015-04-08 16:20:35778 ret, _, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:40779 if ret:
780 # If `gn gen` failed, we should exit early rather than trying to
781 # generate isolates. Run() will have already logged any error output.
782 self.Print('GN gen failed: %d' % ret)
783 return ret
dpranke74559b52015-06-10 21:20:39784
jbudoricke3c4f95e2016-04-28 23:17:38785 android = 'target_os="android"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:42786 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:30787 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Abhishek Arya2f5f7342018-06-13 16:59:44788 for target in swarming_targets:
jbudoricke3c4f95e2016-04-28 23:17:38789 if android:
790 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:02791 # will result in runtime_deps associated with the stamp file, while the
792 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:44793 label = isolate_map[target]['label']
jbudorick91c8a6012016-01-29 23:20:02794 runtime_deps_targets = [
dprankecb4a2e242016-09-19 01:13:14795 target + '.runtime_deps',
dpranke48ccf8f2016-03-28 23:58:28796 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
Kevin Marshallf35fa5f2018-01-29 19:24:42797 elif fuchsia:
798 # Only emit a runtime deps file for the group() target on Fuchsia.
Abhishek Arya2f5f7342018-06-13 16:59:44799 label = isolate_map[target]['label']
Kevin Marshallf35fa5f2018-01-29 19:24:42800 runtime_deps_targets = [
801 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
dprankecb4a2e242016-09-19 01:13:14802 elif (isolate_map[target]['type'] == 'script' or
803 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:11804 # For script targets, the build target is usually a group,
805 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:49806 # for the label, which lives under the obj/ directory, but it may
807 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:44808 label = isolate_map[target]['label']
dpranke48ccf8f2016-03-28 23:58:28809 runtime_deps_targets = [
810 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
Nico Weberd94b71a2018-02-22 22:00:30811 if win:
eyaich82d5ac942016-11-03 12:13:49812 runtime_deps_targets += [ target + '.exe.runtime_deps' ]
813 else:
814 runtime_deps_targets += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:30815 elif win:
dpranke48ccf8f2016-03-28 23:58:28816 runtime_deps_targets = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:52817 else:
dpranke48ccf8f2016-03-28 23:58:28818 runtime_deps_targets = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:02819
dpranke48ccf8f2016-03-28 23:58:28820 for r in runtime_deps_targets:
821 runtime_deps_path = self.ToAbsPath(build_dir, r)
822 if self.Exists(runtime_deps_path):
jbudorick91c8a6012016-01-29 23:20:02823 break
824 else:
dpranke48ccf8f2016-03-28 23:58:28825 raise MBErr('did not generate any of %s' %
826 ', '.join(runtime_deps_targets))
dpranke74559b52015-06-10 21:20:39827
dprankecb4a2e242016-09-19 01:13:14828 command, extra_files = self.GetIsolateCommand(target, vals)
dpranked5b2b9432015-06-23 16:55:30829
dpranke48ccf8f2016-03-28 23:58:28830 runtime_deps = self.ReadFile(runtime_deps_path).splitlines()
dpranked5b2b9432015-06-23 16:55:30831
dpranke751516a2015-10-03 01:11:34832 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
833 extra_files)
dpranked5b2b9432015-06-23 16:55:30834
dpranke751516a2015-10-03 01:11:34835 return 0
836
837 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:30838 target = self.args.target
dprankecb4a2e242016-09-19 01:13:14839 isolate_map = self.ReadIsolateMap()
840 err, labels = self.MapTargetsToLabels(isolate_map, [target])
841 if err:
842 raise MBErr(err)
843 label = labels[0]
dpranke751516a2015-10-03 01:11:34844
Dirk Prankef24e6b22018-03-27 20:12:30845 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:14846 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:34847
dprankeeca4a782016-04-14 01:42:38848 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:20849 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:34850 if ret:
dpranke030d7a6d2016-03-26 17:23:50851 if out:
852 self.Print(out)
dpranke751516a2015-10-03 01:11:34853 return ret
854
855 runtime_deps = out.splitlines()
856
857 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
858 extra_files)
859
860 ret, _, _ = self.Run([
861 self.executable,
862 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
863 'check',
864 '-i',
865 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
866 '-s',
867 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
868 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:30869
dprankefe4602312015-04-08 16:20:35870 return ret
871
dpranke751516a2015-10-03 01:11:34872 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps,
873 extra_files):
874 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
875 self.WriteFile(isolate_path,
876 pprint.pformat({
877 'variables': {
878 'command': command,
879 'files': sorted(runtime_deps + extra_files),
880 }
881 }) + '\n')
882
883 self.WriteJSON(
884 {
885 'args': [
886 '--isolated',
887 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
888 '--isolate',
889 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
890 ],
891 'dir': self.chromium_src_dir,
892 'version': 1,
893 },
894 isolate_path + 'd.gen.json',
895 )
896
dprankecb4a2e242016-09-19 01:13:14897 def MapTargetsToLabels(self, isolate_map, targets):
898 labels = []
899 err = ''
900
dprankecb4a2e242016-09-19 01:13:14901 for target in targets:
902 if target == 'all':
903 labels.append(target)
904 elif target.startswith('//'):
905 labels.append(target)
906 else:
907 if target in isolate_map:
thakis024d6f32017-05-16 23:21:42908 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:14909 err += ('test target "%s" type is unknown\n' % target)
910 else:
thakis024d6f32017-05-16 23:21:42911 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:14912 else:
913 err += ('target "%s" not found in '
914 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
915
916 return err, labels
917
dprankeeca4a782016-04-14 01:42:38918 def GNCmd(self, subcommand, path, *args):
dpranked1fba482015-04-14 20:54:51919 if self.platform == 'linux2':
dpranke40da0202016-02-13 05:05:20920 subdir, exe = 'linux64', 'gn'
dpranked1fba482015-04-14 20:54:51921 elif self.platform == 'darwin':
dpranke40da0202016-02-13 05:05:20922 subdir, exe = 'mac', 'gn'
dpranked1fba482015-04-14 20:54:51923 else:
dpranke40da0202016-02-13 05:05:20924 subdir, exe = 'win', 'gn.exe'
dprankeeca4a782016-04-14 01:42:38925
dpranke40da0202016-02-13 05:05:20926 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:08927 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:52928
dprankecb4a2e242016-09-19 01:13:14929
dprankeeca4a782016-04-14 01:42:38930 def GNArgs(self, vals):
dpranke73ed0d62016-04-25 19:18:34931 if vals['cros_passthrough']:
932 if not 'GN_ARGS' in os.environ:
933 raise MBErr('MB is expecting GN_ARGS to be in the environment')
934 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:16935 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:30936 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:34937 gn_args)
938 else:
939 gn_args = vals['gn_args']
940
dpranked0c138b2016-04-13 18:28:47941 if self.args.goma_dir:
942 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:38943
agrieve41d21a72016-04-14 18:02:26944 android_version_code = self.args.android_version_code
945 if android_version_code:
946 gn_args += ' android_default_version_code="%s"' % android_version_code
947
948 android_version_name = self.args.android_version_name
949 if android_version_name:
950 gn_args += ' android_default_version_name="%s"' % android_version_name
951
dprankeeca4a782016-04-14 01:42:38952 # Canonicalize the arg string into a sorted, newline-separated list
953 # of key-value pairs, and de-dup the keys if need be so that only
954 # the last instance of each arg is listed.
955 gn_args = gn_helpers.ToGNString(gn_helpers.FromGNArgs(gn_args))
956
dpranke9dd5e252016-04-14 04:23:09957 args_file = vals.get('args_file', None)
958 if args_file:
959 gn_args = ('import("%s")\n' % vals['args_file']) + gn_args
dprankeeca4a782016-04-14 01:42:38960 return gn_args
dprankefe4602312015-04-08 16:20:35961
dprankecb4a2e242016-09-19 01:13:14962 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:07963 isolate_map = self.ReadIsolateMap()
964
Scott Graham3be4b4162017-09-12 00:41:41965 is_android = 'target_os="android"' in vals['gn_args']
Benjamin Pastene3bce864e2018-04-14 01:16:32966 is_simplechrome = vals.get('cros_passthrough', False)
Scott Graham3be4b4162017-09-12 00:41:41967 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:30968 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:06969
kylechar39705682017-01-19 14:37:23970 # This should be true if tests with type='windowed_test_launcher' are
971 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:08972 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
973 # backends. Currently, tests are executed for the headless and X11 backends
974 # and both can run under Xvfb.
975 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
976 # backend.
Scott Graham3be4b4162017-09-12 00:41:41977 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:25978
979 asan = 'is_asan=true' in vals['gn_args']
980 msan = 'is_msan=true' in vals['gn_args']
981 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:41982 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:25983
dprankecb4a2e242016-09-19 01:13:14984 test_type = isolate_map[target]['type']
dprankefe0d35e2016-02-05 02:43:59985
dprankecb4a2e242016-09-19 01:13:14986 executable = isolate_map[target].get('executable', target)
Nico Weberd94b71a2018-02-22 22:00:30987 executable_suffix = '.exe' if is_win else ''
dprankefe0d35e2016-02-05 02:43:59988
dprankea55584f12015-07-22 00:52:47989 cmdline = []
Andrii Shyshkalovc158e0102018-01-10 05:52:00990 extra_files = [
991 '../../.vpython',
992 '../../testing/test_env.py',
993 ]
dpranked8113582015-06-05 20:08:25994
dprankecb4a2e242016-09-19 01:13:14995 if test_type == 'nontest':
996 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
997 output_path=None)
998
Scott Graham3be4b4162017-09-12 00:41:41999 if is_android and test_type != "script":
bpastenee428ea92017-02-17 02:20:321000 cmdline = [
John Budorickfb97a852017-12-20 20:10:191001 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041002 '../../build/android/test_wrapper/logdog_wrapper.py',
1003 '--target', target,
hzl9ae14452017-04-04 23:38:021004 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481005 '--store-tombstones']
Scott Graham3be4b4162017-09-12 00:41:411006 elif is_fuchsia and test_type != 'script':
John Budorickfb97a852017-12-20 20:10:191007 cmdline = [
1008 '../../testing/test_env.py',
1009 os.path.join('bin', 'run_%s' % target),
1010 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321011 elif is_simplechrome and test_type != 'script':
1012 cmdline = [
1013 '../../testing/test_env.py',
1014 os.path.join('bin', 'run_%s' % target),
1015 ]
kylechar39705682017-01-19 14:37:231016 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001017 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471018 cmdline = [
dprankefe0d35e2016-02-05 02:43:591019 '../../testing/xvfb.py',
dprankefe0d35e2016-02-05 02:43:591020 './' + str(executable) + executable_suffix,
1021 '--brave-new-test-launcher',
1022 '--test-launcher-bot-mode',
1023 '--asan=%d' % asan,
1024 '--msan=%d' % msan,
1025 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411026 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471027 ]
1028 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471029 cmdline = [
1030 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591031 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251032 '--brave-new-test-launcher',
1033 '--test-launcher-bot-mode',
1034 '--asan=%d' % asan,
1035 '--msan=%d' % msan,
1036 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411037 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471038 ]
dpranke6abd8652015-08-28 03:21:111039 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241040 cmdline = []
1041 # If we're testing a CrOS simplechrome build, assume we need to launch a
1042 # VM first. So prepend the command to run with the VM launcher.
1043 # TODO(bpastene): Differentiate between CrOS VM and hardware tests.
1044 if is_simplechrome:
1045 cmdline = [os.path.join('bin', 'launch_cros_vm')]
1046 cmdline += [
dpranke6abd8652015-08-28 03:21:111047 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141048 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591049 ]
Dirk Prankef24e6b22018-03-27 20:12:301050 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471051 cmdline = [
1052 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591053 ]
dprankea55584f12015-07-22 00:52:471054 else:
1055 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1056 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251057
Abhishek Arya2f5f7342018-06-13 16:59:441058 if is_win and asan:
Alexander Dunaev384ba482018-03-21 17:56:121059 # Sandbox is not yet supported by ASAN for Windows.
1060 # Perhaps this is only needed for tests that use the sandbox?
1061 cmdline.append('--no-sandbox')
1062
dprankecb4a2e242016-09-19 01:13:141063 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591064
dpranked8113582015-06-05 20:08:251065 return cmdline, extra_files
1066
dpranke74559b52015-06-10 21:20:391067 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331068 return self.PathJoin(self.chromium_src_dir,
1069 self.ToSrcRelPath(build_path),
1070 *comps)
dpranked8113582015-06-05 20:08:251071
dprankeee5b51f62015-04-09 00:03:221072 def ToSrcRelPath(self, path):
1073 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501074 if path.startswith('//'):
1075 return path[2:].replace('/', self.sep)
1076 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351077
Dirk Pranke0fd41bcd2015-06-19 00:05:501078 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141079 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501080 # in order to ensure that we have a build directory.
Andrew Grieve0bb79bb2018-06-27 03:14:091081 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True)
Dirk Pranke0fd41bcd2015-06-19 00:05:501082 if ret:
1083 return ret
1084
Dirk Prankef24e6b22018-03-27 20:12:301085 build_path = self.args.path
1086 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141087 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301088 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141089 gn_output_path = output_path + '.gn'
1090
dpranke7837fc362015-11-19 03:54:161091 inp = self.ReadInputJSON(['files', 'test_targets',
1092 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321093 if self.args.verbose:
1094 self.Print()
1095 self.Print('analyze input:')
1096 self.PrintJSON(inp)
1097 self.Print()
1098
dpranke76734662015-04-16 02:17:501099
dpranke7c5f614d2015-07-22 23:43:391100 # This shouldn't normally happen, but could due to unusual race conditions,
1101 # like a try job that gets scheduled before a patch lands but runs after
1102 # the patch has landed.
1103 if not inp['files']:
1104 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161105 self.WriteJSON({
1106 'status': 'No dependency',
1107 'compile_targets': [],
1108 'test_targets': [],
1109 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391110 return 0
1111
dprankecb4a2e242016-09-19 01:13:141112 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161113 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561114
dprankecb4a2e242016-09-19 01:13:141115 isolate_map = self.ReadIsolateMap()
1116 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1117 isolate_map, inp['additional_compile_targets'])
1118 if err:
1119 raise MBErr(err)
1120
1121 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1122 isolate_map, inp['test_targets'])
1123 if err:
1124 raise MBErr(err)
1125 labels_to_targets = {}
1126 for i, label in enumerate(gn_inp['test_targets']):
1127 labels_to_targets[label] = inp['test_targets'][i]
1128
dprankef61de2f2015-05-14 04:09:561129 try:
dprankecb4a2e242016-09-19 01:13:141130 self.WriteJSON(gn_inp, gn_input_path)
1131 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
1132 ret, _, _ = self.Run(cmd, force_verbose=True)
1133 if ret:
1134 return ret
dpranke067d0142015-05-14 22:52:451135
dprankecb4a2e242016-09-19 01:13:141136 gn_outp_str = self.ReadFile(gn_output_path)
1137 try:
1138 gn_outp = json.loads(gn_outp_str)
1139 except Exception as e:
1140 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1141 % (repr(gn_outp_str), str(e)))
1142 raise
1143
1144 outp = {}
1145 if 'status' in gn_outp:
1146 outp['status'] = gn_outp['status']
1147 if 'error' in gn_outp:
1148 outp['error'] = gn_outp['error']
1149 if 'invalid_targets' in gn_outp:
1150 outp['invalid_targets'] = gn_outp['invalid_targets']
1151 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491152 all_input_compile_targets = sorted(
1153 set(inp['test_targets'] + inp['additional_compile_targets']))
1154
1155 # If we're building 'all', we can throw away the rest of the targets
1156 # since they're redundant.
dpranke385a3102016-09-20 22:04:081157 if 'all' in gn_outp['compile_targets']:
1158 outp['compile_targets'] = ['all']
1159 else:
Dirk Pranke45165072017-11-08 04:57:491160 outp['compile_targets'] = gn_outp['compile_targets']
1161
1162 # crbug.com/736215: When GN returns targets back, for targets in
1163 # the default toolchain, GN will have generated a phony ninja
1164 # target matching the label, and so we can safely (and easily)
1165 # transform any GN label into the matching ninja target. For
1166 # targets in other toolchains, though, GN doesn't generate the
1167 # phony targets, and we don't know how to turn the labels into
1168 # compile targets. In this case, we also conservatively give up
1169 # and build everything. Probably the right thing to do here is
1170 # to have GN return the compile targets directly.
1171 if any("(" in target for target in outp['compile_targets']):
1172 self.Print('WARNING: targets with non-default toolchains were '
1173 'found, building everything instead.')
1174 outp['compile_targets'] = all_input_compile_targets
1175 else:
dpranke385a3102016-09-20 22:04:081176 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491177 label.replace('//', '') for label in outp['compile_targets']]
1178
1179 # Windows has a maximum command line length of 8k; even Linux
1180 # maxes out at 128k; if analyze returns a *really long* list of
1181 # targets, we just give up and conservatively build everything instead.
1182 # Probably the right thing here is for ninja to support response
1183 # files as input on the command line
1184 # (see https://github.com/ninja-build/ninja/issues/1355).
1185 if len(' '.join(outp['compile_targets'])) > 7*1024:
1186 self.Print('WARNING: Too many compile targets were affected.')
1187 self.Print('WARNING: Building everything instead to avoid '
1188 'command-line length issues.')
1189 outp['compile_targets'] = all_input_compile_targets
1190
1191
dprankecb4a2e242016-09-19 01:13:141192 if 'test_targets' in gn_outp:
1193 outp['test_targets'] = [
1194 labels_to_targets[label] for label in gn_outp['test_targets']]
1195
1196 if self.args.verbose:
1197 self.Print()
1198 self.Print('analyze output:')
1199 self.PrintJSON(outp)
1200 self.Print()
1201
1202 self.WriteJSON(outp, output_path)
1203
dprankef61de2f2015-05-14 04:09:561204 finally:
dprankecb4a2e242016-09-19 01:13:141205 if self.Exists(gn_input_path):
1206 self.RemoveFile(gn_input_path)
1207 if self.Exists(gn_output_path):
1208 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351209
1210 return 0
1211
dpranked8113582015-06-05 20:08:251212 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301213 path = self.args.input_path
1214 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351215 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321216 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351217
1218 try:
1219 inp = json.loads(self.ReadFile(path))
1220 except Exception as e:
1221 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321222 (path, e), output_path)
dpranked8113582015-06-05 20:08:251223
1224 for k in required_keys:
1225 if not k in inp:
1226 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1227 output_path)
dprankefe4602312015-04-08 16:20:351228
1229 return inp
1230
dpranked5b2b9432015-06-23 16:55:301231 def WriteFailureAndRaise(self, msg, output_path):
1232 if output_path:
dprankee0547cd2015-09-15 01:27:401233 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351234 raise MBErr(msg)
1235
dprankee0547cd2015-09-15 01:27:401236 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321237 try:
dprankee0547cd2015-09-15 01:27:401238 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1239 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321240 except Exception as e:
1241 raise MBErr('Error %s writing to the output path "%s"' %
1242 (e, path))
dprankefe4602312015-04-08 16:20:351243
aneeshmde50f472016-04-01 01:13:101244 def CheckCompile(self, master, builder):
1245 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1246 url = urllib2.quote(url_template.format(master=master, builder=builder),
1247 safe=':/()?=')
1248 try:
1249 builds = json.loads(self.Fetch(url))
1250 except Exception as e:
1251 return str(e)
1252 successes = sorted(
1253 [int(x) for x in builds.keys() if "text" in builds[x] and
1254 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1255 reverse=True)
1256 if not successes:
1257 return "no successful builds"
1258 build = builds[str(successes[0])]
1259 step_names = set([step["name"] for step in build["steps"]])
1260 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1261 if compile_indicators & step_names:
1262 return "compiles"
1263 return "does not compile"
1264
dpranke3cec199c2015-09-22 23:29:021265 def PrintCmd(self, cmd, env):
1266 if self.platform == 'win32':
1267 env_prefix = 'set '
1268 env_quoter = QuoteForSet
1269 shell_quoter = QuoteForCmd
1270 else:
1271 env_prefix = ''
1272 env_quoter = pipes.quote
1273 shell_quoter = pipes.quote
1274
1275 def print_env(var):
1276 if env and var in env:
1277 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1278
dprankeec079262016-06-07 02:21:201279 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021280
dpranke8c2cfd32015-09-17 20:12:331281 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351282 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021283 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351284
dprankecda00332015-04-11 04:18:321285 def PrintJSON(self, obj):
1286 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1287
dpranke751516a2015-10-03 01:11:341288 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301289 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381290 if self.platform == 'win32':
1291 # On Windows use the batch script since there is no exe
1292 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1293 else:
1294 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341295 if self.args.jobs:
1296 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1297 ninja_cmd.append(target)
1298 ret, _, _ = self.Run(ninja_cmd, force_verbose=False, buffer_output=False)
1299 return ret
1300
1301 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351302 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401303 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021304 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351305 if self.args.dryrun:
1306 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401307
dpranke751516a2015-10-03 01:11:341308 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401309 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341310 if ret:
1311 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351312 if out:
dprankeee5b51f62015-04-09 00:03:221313 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351314 if err:
dprankeee5b51f62015-04-09 00:03:221315 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351316 return ret, out, err
1317
dpranke751516a2015-10-03 01:11:341318 def Call(self, cmd, env=None, buffer_output=True):
1319 if buffer_output:
1320 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1321 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1322 env=env)
1323 out, err = p.communicate()
1324 else:
1325 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1326 env=env)
1327 p.wait()
1328 out = err = ''
dprankefe4602312015-04-08 16:20:351329 return p.returncode, out, err
1330
1331 def ExpandUser(self, path):
1332 # This function largely exists so it can be overridden for testing.
1333 return os.path.expanduser(path)
1334
1335 def Exists(self, path):
1336 # This function largely exists so it can be overridden for testing.
1337 return os.path.exists(path)
1338
dpranke867bcf4a2016-03-14 22:28:321339 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501340 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321341 f = urllib2.urlopen(url)
1342 contents = f.read()
1343 f.close()
1344 return contents
1345
dprankec3441d12015-06-23 23:01:351346 def MaybeMakeDirectory(self, path):
1347 try:
1348 os.makedirs(path)
1349 except OSError, e:
1350 if e.errno != errno.EEXIST:
1351 raise
1352
dpranke8c2cfd32015-09-17 20:12:331353 def PathJoin(self, *comps):
1354 # This function largely exists so it can be overriden for testing.
1355 return os.path.join(*comps)
1356
dpranke030d7a6d2016-03-26 17:23:501357 def Print(self, *args, **kwargs):
1358 # This function largely exists so it can be overridden for testing.
1359 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101360 if kwargs.get('stream', sys.stdout) == sys.stdout:
1361 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501362
dprankefe4602312015-04-08 16:20:351363 def ReadFile(self, path):
1364 # This function largely exists so it can be overriden for testing.
1365 with open(path) as fp:
1366 return fp.read()
1367
dpranke030d7a6d2016-03-26 17:23:501368 def RelPath(self, path, start='.'):
1369 # This function largely exists so it can be overriden for testing.
1370 return os.path.relpath(path, start)
1371
dprankef61de2f2015-05-14 04:09:561372 def RemoveFile(self, path):
1373 # This function largely exists so it can be overriden for testing.
1374 os.remove(path)
1375
dprankec161aa92015-09-14 20:21:131376 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331377 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131378 # In other places in chromium, we often have to retry this command
1379 # because we're worried about other processes still holding on to
1380 # file handles, but when MB is invoked, it will be early enough in the
1381 # build that their should be no other processes to interfere. We
1382 # can change this if need be.
1383 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1384 else:
1385 shutil.rmtree(abs_path, ignore_errors=True)
1386
Dirk Prankef24e6b22018-03-27 20:12:301387 def TempDir(self):
1388 # This function largely exists so it can be overriden for testing.
1389 return tempfile.mkdtemp(prefix='mb_')
1390
dprankef61de2f2015-05-14 04:09:561391 def TempFile(self, mode='w'):
1392 # This function largely exists so it can be overriden for testing.
1393 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1394
dprankee0547cd2015-09-15 01:27:401395 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351396 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401397 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301398 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351399 with open(path, 'w') as fp:
1400 return fp.write(contents)
1401
dprankef61de2f2015-05-14 04:09:561402
dprankefe4602312015-04-08 16:20:351403class MBErr(Exception):
1404 pass
1405
1406
dpranke3cec199c2015-09-22 23:29:021407# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1408# details of this next section, which handles escaping command lines
1409# so that they can be copied and pasted into a cmd window.
1410UNSAFE_FOR_SET = set('^<>&|')
1411UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1412ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1413
1414
1415def QuoteForSet(arg):
1416 if any(a in UNSAFE_FOR_SET for a in arg):
1417 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1418 return arg
1419
1420
1421def QuoteForCmd(arg):
1422 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021423 if arg == '' or ' ' in arg or '"' in arg:
1424 quote_re = re.compile(r'(\\*)"')
1425 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1426
1427 # Then check to see if the arg contains any metacharacters other than
1428 # double quotes; if it does, quote everything (including the double
1429 # quotes) for safety.
1430 if any(a in UNSAFE_FOR_CMD for a in arg):
1431 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1432 return arg
1433
1434
dprankefe4602312015-04-08 16:20:351435if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591436 sys.exit(main(sys.argv[1:]))