blob: 169ebb007950c8044aad3547d88e0c6e4028ae5e [file] [log] [blame]
dprankefe4602312015-04-08 16:20:351#!/usr/bin/env python
2# Copyright 2015 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Dirk Pranke8cb6aa782017-12-16 02:31:336"""MB - the Meta-Build wrapper around GN.
dprankefe4602312015-04-08 16:20:357
Dirk Pranked181a1a2017-12-14 01:47:118MB is a wrapper script for GN that can be used to generate build files
dprankefe4602312015-04-08 16:20:359for sets of canned configurations and analyze them.
10"""
11
12from __future__ import print_function
13
14import argparse
15import ast
dprankec3441d12015-06-23 23:01:3516import errno
dprankefe4602312015-04-08 16:20:3517import json
18import os
dpranke68d1cb182015-09-17 23:30:0019import pipes
Dirk Pranke8cb6aa782017-12-16 02:31:3320import platform
dpranked8113582015-06-05 20:08:2521import pprint
dpranke3cec199c2015-09-22 23:29:0222import re
dprankefe4602312015-04-08 16:20:3523import shutil
24import sys
25import subprocess
dprankef61de2f2015-05-14 04:09:5626import tempfile
dprankebbe6d4672016-04-19 06:56:5727import traceback
dpranke867bcf4a2016-03-14 22:28:3228import urllib2
Dirk Prankef24e6b22018-03-27 20:12:3029import zipfile
dpranke867bcf4a2016-03-14 22:28:3230
31from collections import OrderedDict
dprankefe4602312015-04-08 16:20:3532
dprankeeca4a782016-04-14 01:42:3833CHROMIUM_SRC_DIR = os.path.dirname(os.path.dirname(os.path.dirname(
34 os.path.abspath(__file__))))
35sys.path = [os.path.join(CHROMIUM_SRC_DIR, 'build')] + sys.path
36
37import gn_helpers
38
39
dprankefe4602312015-04-08 16:20:3540def main(args):
dprankeee5b51f62015-04-09 00:03:2241 mbw = MetaBuildWrapper()
dpranke255085e2016-03-16 05:23:5942 return mbw.Main(args)
dprankefe4602312015-04-08 16:20:3543
44
45class MetaBuildWrapper(object):
46 def __init__(self):
dprankeeca4a782016-04-14 01:42:3847 self.chromium_src_dir = CHROMIUM_SRC_DIR
48 self.default_config = os.path.join(self.chromium_src_dir, 'tools', 'mb',
49 'mb_config.pyl')
kjellander902bcb62016-10-26 06:20:5050 self.default_isolate_map = os.path.join(self.chromium_src_dir, 'testing',
51 'buildbot', 'gn_isolate_map.pyl')
dpranke8c2cfd32015-09-17 20:12:3352 self.executable = sys.executable
dpranked1fba482015-04-14 20:54:5153 self.platform = sys.platform
dpranke8c2cfd32015-09-17 20:12:3354 self.sep = os.sep
dprankefe4602312015-04-08 16:20:3555 self.args = argparse.Namespace()
56 self.configs = {}
dprankefe4602312015-04-08 16:20:3557 self.masters = {}
58 self.mixins = {}
dprankefe4602312015-04-08 16:20:3559
dpranke255085e2016-03-16 05:23:5960 def Main(self, args):
61 self.ParseArgs(args)
62 try:
63 ret = self.args.func()
64 if ret:
65 self.DumpInputFiles()
66 return ret
67 except KeyboardInterrupt:
dprankecb4a2e242016-09-19 01:13:1468 self.Print('interrupted, exiting')
dpranke255085e2016-03-16 05:23:5969 return 130
dprankebbe6d4672016-04-19 06:56:5770 except Exception:
dpranke255085e2016-03-16 05:23:5971 self.DumpInputFiles()
dprankebbe6d4672016-04-19 06:56:5772 s = traceback.format_exc()
73 for l in s.splitlines():
74 self.Print(l)
dpranke255085e2016-03-16 05:23:5975 return 1
76
dprankefe4602312015-04-08 16:20:3577 def ParseArgs(self, argv):
78 def AddCommonOptions(subp):
79 subp.add_argument('-b', '--builder',
80 help='builder name to look up config from')
81 subp.add_argument('-m', '--master',
82 help='master name to look up config from')
83 subp.add_argument('-c', '--config',
84 help='configuration to analyze')
shenghuazhang804b21542016-10-11 02:06:4985 subp.add_argument('--phase',
86 help='optional phase name (used when builders '
87 'do multiple compiles with different '
88 'arguments in a single build)')
dprankefe4602312015-04-08 16:20:3589 subp.add_argument('-f', '--config-file', metavar='PATH',
90 default=self.default_config,
91 help='path to config file '
kjellander902bcb62016-10-26 06:20:5092 '(default is %(default)s)')
93 subp.add_argument('-i', '--isolate-map-file', metavar='PATH',
kjellander902bcb62016-10-26 06:20:5094 help='path to isolate map file '
Zhiling Huang66958462018-02-03 00:28:2095 '(default is %(default)s)',
96 default=[],
97 action='append',
98 dest='isolate_map_files')
dpranked0c138b2016-04-13 18:28:4799 subp.add_argument('-g', '--goma-dir',
100 help='path to goma directory')
agrieve41d21a72016-04-14 18:02:26101 subp.add_argument('--android-version-code',
Dirk Pranked181a1a2017-12-14 01:47:11102 help='Sets GN arg android_default_version_code')
agrieve41d21a72016-04-14 18:02:26103 subp.add_argument('--android-version-name',
Dirk Pranked181a1a2017-12-14 01:47:11104 help='Sets GN arg android_default_version_name')
dprankefe4602312015-04-08 16:20:35105 subp.add_argument('-n', '--dryrun', action='store_true',
106 help='Do a dry run (i.e., do nothing, just print '
107 'the commands that will run)')
dprankee0547cd2015-09-15 01:27:40108 subp.add_argument('-v', '--verbose', action='store_true',
109 help='verbose logging')
dprankefe4602312015-04-08 16:20:35110
111 parser = argparse.ArgumentParser(prog='mb')
112 subps = parser.add_subparsers()
113
114 subp = subps.add_parser('analyze',
115 help='analyze whether changes to a set of files '
116 'will cause a set of binaries to be rebuilt.')
117 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30118 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35119 help='path build was generated into.')
Dirk Prankef24e6b22018-03-27 20:12:30120 subp.add_argument('input_path',
dprankefe4602312015-04-08 16:20:35121 help='path to a file containing the input arguments '
122 'as a JSON object.')
Dirk Prankef24e6b22018-03-27 20:12:30123 subp.add_argument('output_path',
dprankefe4602312015-04-08 16:20:35124 help='path to a file containing the output arguments '
125 'as a JSON object.')
126 subp.set_defaults(func=self.CmdAnalyze)
127
dprankef37aebb92016-09-23 01:14:49128 subp = subps.add_parser('export',
129 help='print out the expanded configuration for'
130 'each builder as a JSON object')
131 subp.add_argument('-f', '--config-file', metavar='PATH',
132 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50133 help='path to config file (default is %(default)s)')
dprankef37aebb92016-09-23 01:14:49134 subp.add_argument('-g', '--goma-dir',
135 help='path to goma directory')
136 subp.set_defaults(func=self.CmdExport)
137
dprankefe4602312015-04-08 16:20:35138 subp = subps.add_parser('gen',
139 help='generate a new set of build files')
140 AddCommonOptions(subp)
dpranke74559b52015-06-10 21:20:39141 subp.add_argument('--swarming-targets-file',
142 help='save runtime dependencies for targets listed '
143 'in file.')
Dirk Prankef24e6b22018-03-27 20:12:30144 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35145 help='path to generate build into')
146 subp.set_defaults(func=self.CmdGen)
147
Erik Chen42df41d2018-08-21 17:13:31148 subp = subps.add_parser('isolate-everything',
149 help='generates a .isolate for all targets. '
150 'Requires that mb.py gen has already been '
151 'run.')
152 AddCommonOptions(subp)
153 subp.set_defaults(func=self.CmdIsolateEverything)
154 subp.add_argument('path',
155 help='path build was generated into')
156
dpranke751516a2015-10-03 01:11:34157 subp = subps.add_parser('isolate',
158 help='generate the .isolate files for a given'
159 'binary')
160 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30161 subp.add_argument('--no-build', dest='build', default=True,
162 action='store_false',
163 help='Do not build, just isolate')
164 subp.add_argument('-j', '--jobs', type=int,
165 help='Number of jobs to pass to ninja')
166 subp.add_argument('path',
dpranke751516a2015-10-03 01:11:34167 help='path build was generated into')
Dirk Prankef24e6b22018-03-27 20:12:30168 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34169 help='ninja target to generate the isolate for')
170 subp.set_defaults(func=self.CmdIsolate)
171
dprankefe4602312015-04-08 16:20:35172 subp = subps.add_parser('lookup',
173 help='look up the command for a given config or '
174 'builder')
175 AddCommonOptions(subp)
176 subp.set_defaults(func=self.CmdLookup)
177
dpranke030d7a6d2016-03-26 17:23:50178 subp = subps.add_parser(
179 'run',
180 help='build and run the isolated version of a '
181 'binary',
182 formatter_class=argparse.RawDescriptionHelpFormatter)
183 subp.description = (
184 'Build, isolate, and run the given binary with the command line\n'
185 'listed in the isolate. You may pass extra arguments after the\n'
186 'target; use "--" if the extra arguments need to include switches.\n'
187 '\n'
188 'Examples:\n'
189 '\n'
190 ' % tools/mb/mb.py run -m chromium.linux -b "Linux Builder" \\\n'
191 ' //out/Default content_browsertests\n'
192 '\n'
193 ' % tools/mb/mb.py run out/Default content_browsertests\n'
194 '\n'
195 ' % tools/mb/mb.py run out/Default content_browsertests -- \\\n'
196 ' --test-launcher-retry-limit=0'
197 '\n'
198 )
dpranke751516a2015-10-03 01:11:34199 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30200 subp.add_argument('-j', '--jobs', type=int,
dpranke751516a2015-10-03 01:11:34201 help='Number of jobs to pass to ninja')
202 subp.add_argument('--no-build', dest='build', default=True,
203 action='store_false',
204 help='Do not build, just isolate and run')
Dirk Prankef24e6b22018-03-27 20:12:30205 subp.add_argument('path',
dpranke030d7a6d2016-03-26 17:23:50206 help=('path to generate build into (or use).'
207 ' This can be either a regular path or a '
208 'GN-style source-relative path like '
209 '//out/Default.'))
Dirk Pranke8cb6aa782017-12-16 02:31:33210 subp.add_argument('-s', '--swarmed', action='store_true',
211 help='Run under swarming with the default dimensions')
212 subp.add_argument('-d', '--dimension', default=[], action='append', nargs=2,
213 dest='dimensions', metavar='FOO bar',
214 help='dimension to filter on')
215 subp.add_argument('--no-default-dimensions', action='store_false',
216 dest='default_dimensions', default=True,
217 help='Do not automatically add dimensions to the task')
Dirk Prankef24e6b22018-03-27 20:12:30218 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34219 help='ninja target to build and run')
dpranke030d7a6d2016-03-26 17:23:50220 subp.add_argument('extra_args', nargs='*',
221 help=('extra args to pass to the isolate to run. Use '
222 '"--" as the first arg if you need to pass '
223 'switches'))
dpranke751516a2015-10-03 01:11:34224 subp.set_defaults(func=self.CmdRun)
225
dprankefe4602312015-04-08 16:20:35226 subp = subps.add_parser('validate',
227 help='validate the config file')
dprankea5a77ca2015-07-16 23:24:17228 subp.add_argument('-f', '--config-file', metavar='PATH',
229 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50230 help='path to config file (default is %(default)s)')
dprankefe4602312015-04-08 16:20:35231 subp.set_defaults(func=self.CmdValidate)
232
Dirk Prankef24e6b22018-03-27 20:12:30233 subp = subps.add_parser('zip',
234 help='generate a .zip containing the files needed '
235 'for a given binary')
236 AddCommonOptions(subp)
237 subp.add_argument('--no-build', dest='build', default=True,
238 action='store_false',
239 help='Do not build, just isolate')
240 subp.add_argument('-j', '--jobs', type=int,
241 help='Number of jobs to pass to ninja')
242 subp.add_argument('path',
243 help='path build was generated into')
244 subp.add_argument('target',
245 help='ninja target to generate the isolate for')
246 subp.add_argument('zip_path',
247 help='path to zip file to create')
248 subp.set_defaults(func=self.CmdZip)
249
dprankefe4602312015-04-08 16:20:35250 subp = subps.add_parser('help',
251 help='Get help on a subcommand.')
252 subp.add_argument(nargs='?', action='store', dest='subcommand',
253 help='The command to get help for.')
254 subp.set_defaults(func=self.CmdHelp)
255
256 self.args = parser.parse_args(argv)
257
dprankeb2be10a2016-02-22 17:11:00258 def DumpInputFiles(self):
259
dprankef7b7eb7a2016-03-28 22:42:59260 def DumpContentsOfFilePassedTo(arg_name, path):
dprankeb2be10a2016-02-22 17:11:00261 if path and self.Exists(path):
dprankef7b7eb7a2016-03-28 22:42:59262 self.Print("\n# To recreate the file passed to %s:" % arg_name)
dprankecb4a2e242016-09-19 01:13:14263 self.Print("%% cat > %s <<EOF" % path)
dprankeb2be10a2016-02-22 17:11:00264 contents = self.ReadFile(path)
dprankef7b7eb7a2016-03-28 22:42:59265 self.Print(contents)
266 self.Print("EOF\n%\n")
dprankeb2be10a2016-02-22 17:11:00267
dprankef7b7eb7a2016-03-28 22:42:59268 if getattr(self.args, 'input_path', None):
269 DumpContentsOfFilePassedTo(
Dirk Prankef24e6b22018-03-27 20:12:30270 'argv[0] (input_path)', self.args.input_path)
dprankef7b7eb7a2016-03-28 22:42:59271 if getattr(self.args, 'swarming_targets_file', None):
272 DumpContentsOfFilePassedTo(
273 '--swarming-targets-file', self.args.swarming_targets_file)
dprankeb2be10a2016-02-22 17:11:00274
dprankefe4602312015-04-08 16:20:35275 def CmdAnalyze(self):
dpranke751516a2015-10-03 01:11:34276 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11277 return self.RunGNAnalyze(vals)
dprankefe4602312015-04-08 16:20:35278
dprankef37aebb92016-09-23 01:14:49279 def CmdExport(self):
280 self.ReadConfigFile()
281 obj = {}
282 for master, builders in self.masters.items():
283 obj[master] = {}
284 for builder in builders:
285 config = self.masters[master][builder]
286 if not config:
287 continue
288
shenghuazhang804b21542016-10-11 02:06:49289 if isinstance(config, dict):
290 args = {k: self.FlattenConfig(v)['gn_args']
291 for k, v in config.items()}
dprankef37aebb92016-09-23 01:14:49292 elif config.startswith('//'):
293 args = config
294 else:
295 args = self.FlattenConfig(config)['gn_args']
296 if 'error' in args:
297 continue
298
299 obj[master][builder] = args
300
301 # Dump object and trim trailing whitespace.
302 s = '\n'.join(l.rstrip() for l in
303 json.dumps(obj, sort_keys=True, indent=2).splitlines())
304 self.Print(s)
305 return 0
306
dprankefe4602312015-04-08 16:20:35307 def CmdGen(self):
dpranke751516a2015-10-03 01:11:34308 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11309 return self.RunGNGen(vals)
dprankefe4602312015-04-08 16:20:35310
Erik Chen42df41d2018-08-21 17:13:31311 def CmdIsolateEverything(self):
312 vals = self.Lookup()
313 return self.RunGNGenAllIsolates(vals)
314
dprankefe4602312015-04-08 16:20:35315 def CmdHelp(self):
316 if self.args.subcommand:
317 self.ParseArgs([self.args.subcommand, '--help'])
318 else:
319 self.ParseArgs(['--help'])
320
dpranke751516a2015-10-03 01:11:34321 def CmdIsolate(self):
322 vals = self.GetConfig()
323 if not vals:
324 return 1
Dirk Prankef24e6b22018-03-27 20:12:30325 if self.args.build:
326 ret = self.Build(self.args.target)
327 if ret:
328 return ret
Dirk Pranked181a1a2017-12-14 01:47:11329 return self.RunGNIsolate(vals)
dpranke751516a2015-10-03 01:11:34330
331 def CmdLookup(self):
332 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11333 cmd = self.GNCmd('gen', '_path_')
334 gn_args = self.GNArgs(vals)
335 self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args)
336 env = None
dpranke751516a2015-10-03 01:11:34337
338 self.PrintCmd(cmd, env)
339 return 0
340
341 def CmdRun(self):
342 vals = self.GetConfig()
343 if not vals:
344 return 1
Dirk Pranked181a1a2017-12-14 01:47:11345 if self.args.build:
Dirk Prankef24e6b22018-03-27 20:12:30346 ret = self.Build(self.args.target)
dpranke751516a2015-10-03 01:11:34347 if ret:
348 return ret
Dirk Pranked181a1a2017-12-14 01:47:11349 ret = self.RunGNIsolate(vals)
350 if ret:
351 return ret
dpranke751516a2015-10-03 01:11:34352
Dirk Pranke8cb6aa782017-12-16 02:31:33353 if self.args.swarmed:
Dirk Prankef24e6b22018-03-27 20:12:30354 return self._RunUnderSwarming(self.args.path, self.args.target)
Dirk Pranke8cb6aa782017-12-16 02:31:33355 else:
Dirk Prankef24e6b22018-03-27 20:12:30356 return self._RunLocallyIsolated(self.args.path, self.args.target)
357
358 def CmdZip(self):
359 ret = self.CmdIsolate()
360 if ret:
361 return ret
362
363 zip_dir = None
364 try:
365 zip_dir = self.TempDir()
366 remap_cmd = [
367 self.executable,
368 self.PathJoin(self.chromium_src_dir, 'tools', 'swarming_client',
369 'isolate.py'),
370 'remap',
371 '-s', self.PathJoin(self.args.path, self.args.target + '.isolated'),
372 '-o', zip_dir
373 ]
374 self.Run(remap_cmd)
375
376 zip_path = self.args.zip_path
377 with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as fp:
378 for root, _, files in os.walk(zip_dir):
379 for filename in files:
380 path = self.PathJoin(root, filename)
381 fp.write(path, self.RelPath(path, zip_dir))
382 finally:
383 if zip_dir:
384 self.RemoveDirectory(zip_dir)
Dirk Pranke8cb6aa782017-12-16 02:31:33385
Robert Iannucci5a9d75f62018-03-02 05:28:20386 @staticmethod
387 def _AddBaseSoftware(cmd):
388 # HACK(iannucci): These packages SHOULD NOT BE HERE.
389 # Remove method once Swarming Pool Task Templates are implemented.
390 # crbug.com/812428
391
392 # Add in required base software. This should be kept in sync with the
393 # `swarming` recipe module in build.git. All references to `swarming_module`
394 # below are purely due to this.
395 cipd_packages = [
396 ('infra/python/cpython/${platform}',
397 'version:2.7.14.chromium14'),
398 ('infra/tools/luci/logdog/butler/${platform}',
399 'git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c'),
400 ('infra/tools/luci/vpython-native/${platform}',
Vadim Shtayurabc21f8902018-07-27 22:11:43401 'git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc'),
Robert Iannucci5a9d75f62018-03-02 05:28:20402 ('infra/tools/luci/vpython/${platform}',
Vadim Shtayurabc21f8902018-07-27 22:11:43403 'git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc'),
Robert Iannucci5a9d75f62018-03-02 05:28:20404 ]
405 for pkg, vers in cipd_packages:
406 cmd.append('--cipd-package=.swarming_module:%s:%s' % (pkg, vers))
407
408 # Add packages to $PATH
409 cmd.extend([
410 '--env-prefix=PATH', '.swarming_module',
411 '--env-prefix=PATH', '.swarming_module/bin',
412 ])
413
414 # Add cache directives for vpython.
415 vpython_cache_path = '.swarming_module_cache/vpython'
416 cmd.extend([
417 '--named-cache=swarming_module_cache_vpython', vpython_cache_path,
418 '--env-prefix=VPYTHON_VIRTUALENV_ROOT', vpython_cache_path,
419 ])
420
Dirk Pranke8cb6aa782017-12-16 02:31:33421 def _RunUnderSwarming(self, build_dir, target):
422 # TODO(dpranke): Look up the information for the target in
423 # the //testing/buildbot.json file, if possible, so that we
424 # can determine the isolate target, command line, and additional
425 # swarming parameters, if possible.
426 #
427 # TODO(dpranke): Also, add support for sharding and merging results.
428 dimensions = []
429 for k, v in self._DefaultDimensions() + self.args.dimensions:
430 dimensions += ['-d', k, v]
431
432 cmd = [
433 self.executable,
434 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
435 'archive',
436 '-s',
437 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
438 '-I', 'isolateserver.appspot.com',
439 ]
440 ret, out, _ = self.Run(cmd, force_verbose=False)
441 if ret:
442 return ret
443
444 isolated_hash = out.splitlines()[0].split()[0]
445 cmd = [
446 self.executable,
447 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
448 'run',
449 '-s', isolated_hash,
450 '-I', 'isolateserver.appspot.com',
451 '-S', 'chromium-swarm.appspot.com',
452 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20453 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33454 if self.args.extra_args:
455 cmd += ['--'] + self.args.extra_args
456 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
457 return ret
458
459 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50460 cmd = [
dpranke751516a2015-10-03 01:11:34461 self.executable,
462 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
463 'run',
464 '-s',
dpranke030d7a6d2016-03-26 17:23:50465 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33466 ]
dpranke030d7a6d2016-03-26 17:23:50467 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33468 cmd += ['--'] + self.args.extra_args
469 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34470 return ret
471
Dirk Pranke8cb6aa782017-12-16 02:31:33472 def _DefaultDimensions(self):
473 if not self.args.default_dimensions:
474 return []
475
476 # This code is naive and just picks reasonable defaults per platform.
477 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40478 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33479 elif self.platform.startswith('linux'):
480 os_dim = ('os', 'Ubuntu-14.04')
481 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40482 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33483 else:
484 raise MBErr('unrecognized platform string "%s"' % self.platform)
485
486 return [('pool', 'Chrome'),
487 ('cpu', 'x86-64'),
488 os_dim]
489
dpranke0cafc162016-03-19 00:41:10490 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35491 errs = []
492
493 # Read the file to make sure it parses.
494 self.ReadConfigFile()
495
dpranke3be00142016-03-17 22:46:04496 # Build a list of all of the configs referenced by builders.
dprankefe4602312015-04-08 16:20:35497 all_configs = {}
dprankefe4602312015-04-08 16:20:35498 for master in self.masters:
dpranke3be00142016-03-17 22:46:04499 for config in self.masters[master].values():
shenghuazhang804b21542016-10-11 02:06:49500 if isinstance(config, dict):
501 for c in config.values():
dprankeb9380a12016-07-21 21:44:09502 all_configs[c] = master
503 else:
504 all_configs[config] = master
dprankefe4602312015-04-08 16:20:35505
dpranke9dd5e252016-04-14 04:23:09506 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35507 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09508 if config.startswith('//'):
509 if not self.Exists(self.ToAbsPath(config)):
510 errs.append('Unknown args file "%s" referenced from "%s".' %
511 (config, loc))
512 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35513 errs.append('Unknown config "%s" referenced from "%s".' %
514 (config, loc))
515
516 # Check that every actual config is actually referenced.
517 for config in self.configs:
518 if not config in all_configs:
519 errs.append('Unused config "%s".' % config)
520
521 # Figure out the whole list of mixins, and check that every mixin
522 # listed by a config or another mixin actually exists.
523 referenced_mixins = set()
524 for config, mixins in self.configs.items():
525 for mixin in mixins:
526 if not mixin in self.mixins:
527 errs.append('Unknown mixin "%s" referenced by config "%s".' %
528 (mixin, config))
529 referenced_mixins.add(mixin)
530
531 for mixin in self.mixins:
532 for sub_mixin in self.mixins[mixin].get('mixins', []):
533 if not sub_mixin in self.mixins:
534 errs.append('Unknown mixin "%s" referenced by mixin "%s".' %
535 (sub_mixin, mixin))
536 referenced_mixins.add(sub_mixin)
537
538 # Check that every mixin defined is actually referenced somewhere.
539 for mixin in self.mixins:
540 if not mixin in referenced_mixins:
541 errs.append('Unreferenced mixin "%s".' % mixin)
542
dpranke255085e2016-03-16 05:23:59543 # If we're checking the Chromium config, check that the 'chromium' bots
544 # which build public artifacts do not include the chrome_with_codecs mixin.
545 if self.args.config_file == self.default_config:
546 if 'chromium' in self.masters:
547 for builder in self.masters['chromium']:
548 config = self.masters['chromium'][builder]
549 def RecurseMixins(current_mixin):
550 if current_mixin == 'chrome_with_codecs':
551 errs.append('Public artifact builder "%s" can not contain the '
552 '"chrome_with_codecs" mixin.' % builder)
553 return
554 if not 'mixins' in self.mixins[current_mixin]:
555 return
556 for mixin in self.mixins[current_mixin]['mixins']:
557 RecurseMixins(mixin)
dalecurtis56fd27e2016-03-09 23:06:41558
dpranke255085e2016-03-16 05:23:59559 for mixin in self.configs[config]:
560 RecurseMixins(mixin)
561 else:
562 errs.append('Missing "chromium" master. Please update this '
563 'proprietary codecs check with the name of the master '
564 'responsible for public build artifacts.')
dalecurtis56fd27e2016-03-09 23:06:41565
dprankefe4602312015-04-08 16:20:35566 if errs:
dpranke4323c80632015-08-10 22:53:54567 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17568 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35569
dpranke0cafc162016-03-19 00:41:10570 if print_ok:
571 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35572 return 0
573
574 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30575 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34576
dprankef37aebb92016-09-23 01:14:49577 vals = self.DefaultVals()
dpranke751516a2015-10-03 01:11:34578 if self.args.builder or self.args.master or self.args.config:
579 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11580 # Re-run gn gen in order to ensure the config is consistent with the
581 # build dir.
582 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34583 return vals
584
Dirk Pranked181a1a2017-12-14 01:47:11585 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
586 'toolchain.ninja')
587 if not self.Exists(toolchain_path):
588 self.Print('Must either specify a path to an existing GN build dir '
589 'or pass in a -m/-b pair or a -c flag to specify the '
590 'configuration')
591 return {}
dpranke751516a2015-10-03 01:11:34592
Dirk Pranked181a1a2017-12-14 01:47:11593 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34594 return vals
595
dprankef37aebb92016-09-23 01:14:49596 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58597 args_contents = ""
598 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
599 if self.Exists(gn_args_path):
600 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34601 gn_args = []
602 for l in args_contents.splitlines():
603 fields = l.split(' ')
604 name = fields[0]
605 val = ' '.join(fields[2:])
606 gn_args.append('%s=%s' % (name, val))
607
dprankef37aebb92016-09-23 01:14:49608 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34609
610 def Lookup(self):
dprankef37aebb92016-09-23 01:14:49611 vals = self.ReadIOSBotConfig()
dprankee0f486f2015-11-19 23:42:00612 if not vals:
613 self.ReadConfigFile()
614 config = self.ConfigFromArgs()
dpranke9dd5e252016-04-14 04:23:09615 if config.startswith('//'):
616 if not self.Exists(self.ToAbsPath(config)):
617 raise MBErr('args file "%s" not found' % config)
dprankef37aebb92016-09-23 01:14:49618 vals = self.DefaultVals()
619 vals['args_file'] = config
dpranke9dd5e252016-04-14 04:23:09620 else:
621 if not config in self.configs:
622 raise MBErr('Config "%s" not found in %s' %
623 (config, self.args.config_file))
624 vals = self.FlattenConfig(config)
dpranke751516a2015-10-03 01:11:34625 return vals
dprankefe4602312015-04-08 16:20:35626
dprankef37aebb92016-09-23 01:14:49627 def ReadIOSBotConfig(self):
dprankee0f486f2015-11-19 23:42:00628 if not self.args.master or not self.args.builder:
629 return {}
630 path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
631 self.args.master, self.args.builder + '.json')
632 if not self.Exists(path):
633 return {}
634
635 contents = json.loads(self.ReadFile(path))
dprankee0f486f2015-11-19 23:42:00636 gn_args = ' '.join(contents.get('gn_args', []))
637
dprankef37aebb92016-09-23 01:14:49638 vals = self.DefaultVals()
639 vals['gn_args'] = gn_args
dprankef37aebb92016-09-23 01:14:49640 return vals
dprankee0f486f2015-11-19 23:42:00641
dprankefe4602312015-04-08 16:20:35642 def ReadConfigFile(self):
643 if not self.Exists(self.args.config_file):
644 raise MBErr('config file not found at %s' % self.args.config_file)
645
646 try:
647 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
648 except SyntaxError as e:
649 raise MBErr('Failed to parse config file "%s": %s' %
650 (self.args.config_file, e))
651
dprankefe4602312015-04-08 16:20:35652 self.configs = contents['configs']
dprankefe4602312015-04-08 16:20:35653 self.masters = contents['masters']
654 self.mixins = contents['mixins']
dprankefe4602312015-04-08 16:20:35655
dprankecb4a2e242016-09-19 01:13:14656 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20657 if not self.args.isolate_map_files:
658 self.args.isolate_map_files = [self.default_isolate_map]
659
660 for f in self.args.isolate_map_files:
661 if not self.Exists(f):
662 raise MBErr('isolate map file not found at %s' % f)
663 isolate_maps = {}
664 for isolate_map in self.args.isolate_map_files:
665 try:
666 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
667 duplicates = set(isolate_map).intersection(isolate_maps)
668 if duplicates:
669 raise MBErr(
670 'Duplicate targets in isolate map files: %s.' %
671 ', '.join(duplicates))
672 isolate_maps.update(isolate_map)
673 except SyntaxError as e:
674 raise MBErr(
675 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
676 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14677
dprankefe4602312015-04-08 16:20:35678 def ConfigFromArgs(self):
679 if self.args.config:
680 if self.args.master or self.args.builder:
681 raise MBErr('Can not specific both -c/--config and -m/--master or '
682 '-b/--builder')
683
684 return self.args.config
685
686 if not self.args.master or not self.args.builder:
687 raise MBErr('Must specify either -c/--config or '
688 '(-m/--master and -b/--builder)')
689
690 if not self.args.master in self.masters:
691 raise MBErr('Master name "%s" not found in "%s"' %
692 (self.args.master, self.args.config_file))
693
694 if not self.args.builder in self.masters[self.args.master]:
695 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
696 (self.args.builder, self.args.master, self.args.config_file))
697
dprankeb9380a12016-07-21 21:44:09698 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49699 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09700 if self.args.phase is None:
701 raise MBErr('Must specify a build --phase for %s on %s' %
702 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49703 phase = str(self.args.phase)
704 if phase not in config:
705 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09706 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49707 return config[phase]
dprankeb9380a12016-07-21 21:44:09708
709 if self.args.phase is not None:
710 raise MBErr('Must not specify a build --phase for %s on %s' %
711 (self.args.builder, self.args.master))
712 return config
dprankefe4602312015-04-08 16:20:35713
714 def FlattenConfig(self, config):
715 mixins = self.configs[config]
dprankef37aebb92016-09-23 01:14:49716 vals = self.DefaultVals()
dprankefe4602312015-04-08 16:20:35717
718 visited = []
719 self.FlattenMixins(mixins, vals, visited)
720 return vals
721
dprankef37aebb92016-09-23 01:14:49722 def DefaultVals(self):
723 return {
724 'args_file': '',
725 'cros_passthrough': False,
726 'gn_args': '',
dprankef37aebb92016-09-23 01:14:49727 }
728
dprankefe4602312015-04-08 16:20:35729 def FlattenMixins(self, mixins, vals, visited):
730 for m in mixins:
731 if m not in self.mixins:
732 raise MBErr('Unknown mixin "%s"' % m)
dprankeee5b51f62015-04-09 00:03:22733
dprankefe4602312015-04-08 16:20:35734 visited.append(m)
735
736 mixin_vals = self.mixins[m]
dpranke73ed0d62016-04-25 19:18:34737
738 if 'cros_passthrough' in mixin_vals:
739 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
Dirk Pranke6b99f072017-04-05 00:58:30740 if 'args_file' in mixin_vals:
741 if vals['args_file']:
742 raise MBErr('args_file specified multiple times in mixins '
743 'for %s on %s' % (self.args.builder, self.args.master))
744 vals['args_file'] = mixin_vals['args_file']
dprankefe4602312015-04-08 16:20:35745 if 'gn_args' in mixin_vals:
746 if vals['gn_args']:
747 vals['gn_args'] += ' ' + mixin_vals['gn_args']
748 else:
749 vals['gn_args'] = mixin_vals['gn_args']
dpranke73ed0d62016-04-25 19:18:34750
dprankefe4602312015-04-08 16:20:35751 if 'mixins' in mixin_vals:
752 self.FlattenMixins(mixin_vals['mixins'], vals, visited)
753 return vals
754
Takuto Ikuta9dffd7e2018-09-05 01:04:00755 def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
Dirk Prankef24e6b22018-03-27 20:12:30756 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50757
Takuto Ikuta9dffd7e2018-09-05 01:04:00758 if check:
759 cmd = self.GNCmd('gen', build_dir, '--check')
760 else:
761 cmd = self.GNCmd('gen', build_dir)
dprankeeca4a782016-04-14 01:42:38762 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:09763 if compute_inputs_for_analyze:
764 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:38765
766 # Since GN hasn't run yet, the build directory may not even exist.
767 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
768
769 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:54770 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:39771
dpranke751516a2015-10-03 01:11:34772 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:39773 # We need GN to generate the list of runtime dependencies for
774 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:14775 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:39776 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:00777 path = self.args.swarming_targets_file
778 if not self.Exists(path):
779 self.WriteFailureAndRaise('"%s" does not exist' % path,
780 output_path=None)
781 contents = self.ReadFile(path)
Erik Chen42df41d2018-08-21 17:13:31782 isolate_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:00783
dprankecb4a2e242016-09-19 01:13:14784 isolate_map = self.ReadIsolateMap()
Erik Chen42df41d2018-08-21 17:13:31785 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:00786 if err:
dprankecb4a2e242016-09-19 01:13:14787 raise MBErr(err)
dpranke74559b52015-06-10 21:20:39788
dpranke751516a2015-10-03 01:11:34789 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:14790 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:39791 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
792
dprankefe4602312015-04-08 16:20:35793 ret, _, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:40794 if ret:
795 # If `gn gen` failed, we should exit early rather than trying to
796 # generate isolates. Run() will have already logged any error output.
797 self.Print('GN gen failed: %d' % ret)
798 return ret
dpranke74559b52015-06-10 21:20:39799
Erik Chen42df41d2018-08-21 17:13:31800 if getattr(self.args, 'swarming_targets_file', None):
801 return self.GenerateIsolates(vals, isolate_targets, isolate_map,
802 build_dir)
803
804 return 0
805
806 def RunGNGenAllIsolates(self, vals):
807 """
808 This command generates all .isolate files.
809
810 This command assumes that "mb.py gen" has already been run, as it relies on
811 "gn ls" to fetch all gn targets. If uses that output, combined with the
812 isolate_map, to determine all isolates that can be generated for the current
813 gn configuration.
814 """
815 build_dir = self.args.path
816 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
817 force_verbose=False)
818 if ret:
819 # If `gn ls` failed, we should exit early rather than trying to
820 # generate isolates.
821 self.Print('GN ls failed: %d' % ret)
822 return ret
823
824 # Create a reverse map from isolate label to isolate dict.
825 isolate_map = self.ReadIsolateMap()
826 isolate_dict_map = {}
827 for key, isolate_dict in isolate_map.iteritems():
828 isolate_dict_map[isolate_dict['label']] = isolate_dict
829 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
830
831 runtime_deps = []
832
833 isolate_targets = []
834 # For every GN target, look up the isolate dict.
835 for line in output.splitlines():
836 target = line.strip()
837 if target in isolate_dict_map:
838 if isolate_dict_map[target]['type'] == 'additional_compile_target':
839 # By definition, additional_compile_targets are not tests, so we
840 # shouldn't generate isolates for them.
841 continue
842
843 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
844 runtime_deps.append(target)
845
846 # Now we need to run "gn gen" again with --runtime-deps-list-file
847 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
848 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
849 cmd = self.GNCmd('gen', build_dir)
850 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
851 self.Run(cmd)
852
853 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
854
855 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
856 """
857 Generates isolates for a list of ninja targets.
858
859 Ninja targets are transformed to GN targets via isolate_map.
860
861 This function assumes that a previous invocation of "mb.py gen" has
862 generated runtime deps for all targets.
863 """
jbudoricke3c4f95e2016-04-28 23:17:38864 android = 'target_os="android"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:42865 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:30866 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Erik Chen42df41d2018-08-21 17:13:31867 for target in ninja_targets:
868 # TODO(https://crbug.com/876065): 'official_tests' use
869 # type='additional_compile_target' to isolate tests. This is not the
870 # intended use for 'additional_compile_target'.
871 if (isolate_map[target]['type'] == 'additional_compile_target' and
872 target != 'official_tests'):
873 # By definition, additional_compile_targets are not tests, so we
874 # shouldn't generate isolates for them.
875 self.Print('Cannot generate isolate for %s since it is an '
876 'additional_compile_target.' % target)
877 return 1
878 elif android:
jbudoricke3c4f95e2016-04-28 23:17:38879 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:02880 # will result in runtime_deps associated with the stamp file, while the
881 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:44882 label = isolate_map[target]['label']
jbudorick91c8a6012016-01-29 23:20:02883 runtime_deps_targets = [
dprankecb4a2e242016-09-19 01:13:14884 target + '.runtime_deps',
dpranke48ccf8f2016-03-28 23:58:28885 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
Kevin Marshallf35fa5f2018-01-29 19:24:42886 elif fuchsia:
887 # Only emit a runtime deps file for the group() target on Fuchsia.
Abhishek Arya2f5f7342018-06-13 16:59:44888 label = isolate_map[target]['label']
Kevin Marshallf35fa5f2018-01-29 19:24:42889 runtime_deps_targets = [
890 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
dprankecb4a2e242016-09-19 01:13:14891 elif (isolate_map[target]['type'] == 'script' or
892 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:11893 # For script targets, the build target is usually a group,
894 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:49895 # for the label, which lives under the obj/ directory, but it may
896 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:44897 label = isolate_map[target]['label']
dpranke48ccf8f2016-03-28 23:58:28898 runtime_deps_targets = [
899 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
Nico Weberd94b71a2018-02-22 22:00:30900 if win:
eyaich82d5ac942016-11-03 12:13:49901 runtime_deps_targets += [ target + '.exe.runtime_deps' ]
902 else:
903 runtime_deps_targets += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:30904 elif win:
dpranke48ccf8f2016-03-28 23:58:28905 runtime_deps_targets = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:52906 else:
dpranke48ccf8f2016-03-28 23:58:28907 runtime_deps_targets = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:02908
dpranke48ccf8f2016-03-28 23:58:28909 for r in runtime_deps_targets:
910 runtime_deps_path = self.ToAbsPath(build_dir, r)
911 if self.Exists(runtime_deps_path):
jbudorick91c8a6012016-01-29 23:20:02912 break
913 else:
dpranke48ccf8f2016-03-28 23:58:28914 raise MBErr('did not generate any of %s' %
915 ', '.join(runtime_deps_targets))
dpranke74559b52015-06-10 21:20:39916
dprankecb4a2e242016-09-19 01:13:14917 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke48ccf8f2016-03-28 23:58:28918 runtime_deps = self.ReadFile(runtime_deps_path).splitlines()
dpranked5b2b9432015-06-23 16:55:30919
Erik Chen42df41d2018-08-21 17:13:31920 canonical_target = target.replace(':','_').replace('/','_')
921 self.WriteIsolateFiles(build_dir, command, canonical_target, runtime_deps,
dpranke751516a2015-10-03 01:11:34922 extra_files)
dpranked5b2b9432015-06-23 16:55:30923
dpranke751516a2015-10-03 01:11:34924 return 0
925
926 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:30927 target = self.args.target
dprankecb4a2e242016-09-19 01:13:14928 isolate_map = self.ReadIsolateMap()
929 err, labels = self.MapTargetsToLabels(isolate_map, [target])
930 if err:
931 raise MBErr(err)
932 label = labels[0]
dpranke751516a2015-10-03 01:11:34933
Dirk Prankef24e6b22018-03-27 20:12:30934 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:14935 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:34936
dprankeeca4a782016-04-14 01:42:38937 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:20938 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:34939 if ret:
dpranke030d7a6d2016-03-26 17:23:50940 if out:
941 self.Print(out)
dpranke751516a2015-10-03 01:11:34942 return ret
943
944 runtime_deps = out.splitlines()
945
946 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
947 extra_files)
948
949 ret, _, _ = self.Run([
950 self.executable,
951 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
952 'check',
953 '-i',
954 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
955 '-s',
956 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
957 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:30958
dprankefe4602312015-04-08 16:20:35959 return ret
960
dpranke751516a2015-10-03 01:11:34961 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps,
962 extra_files):
963 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
964 self.WriteFile(isolate_path,
965 pprint.pformat({
966 'variables': {
967 'command': command,
968 'files': sorted(runtime_deps + extra_files),
969 }
970 }) + '\n')
971
972 self.WriteJSON(
973 {
974 'args': [
975 '--isolated',
976 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
977 '--isolate',
978 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
979 ],
980 'dir': self.chromium_src_dir,
981 'version': 1,
982 },
983 isolate_path + 'd.gen.json',
984 )
985
dprankecb4a2e242016-09-19 01:13:14986 def MapTargetsToLabels(self, isolate_map, targets):
987 labels = []
988 err = ''
989
dprankecb4a2e242016-09-19 01:13:14990 for target in targets:
991 if target == 'all':
992 labels.append(target)
993 elif target.startswith('//'):
994 labels.append(target)
995 else:
996 if target in isolate_map:
thakis024d6f32017-05-16 23:21:42997 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:14998 err += ('test target "%s" type is unknown\n' % target)
999 else:
thakis024d6f32017-05-16 23:21:421000 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141001 else:
1002 err += ('target "%s" not found in '
1003 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1004
1005 return err, labels
1006
dprankeeca4a782016-04-14 01:42:381007 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461008 if self.platform == 'linux2':
1009 subdir, exe = 'linux64', 'gn'
1010 elif self.platform == 'darwin':
1011 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251012 elif self.platform == 'aix6':
1013 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461014 else:
1015 subdir, exe = 'win', 'gn.exe'
1016
1017 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081018 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521019
dprankecb4a2e242016-09-19 01:13:141020
dprankeeca4a782016-04-14 01:42:381021 def GNArgs(self, vals):
dpranke73ed0d62016-04-25 19:18:341022 if vals['cros_passthrough']:
1023 if not 'GN_ARGS' in os.environ:
1024 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1025 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161026 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301027 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341028 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351029 if vals['gn_args']:
1030 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341031 else:
1032 gn_args = vals['gn_args']
1033
dpranked0c138b2016-04-13 18:28:471034 if self.args.goma_dir:
1035 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381036
agrieve41d21a72016-04-14 18:02:261037 android_version_code = self.args.android_version_code
1038 if android_version_code:
1039 gn_args += ' android_default_version_code="%s"' % android_version_code
1040
1041 android_version_name = self.args.android_version_name
1042 if android_version_name:
1043 gn_args += ' android_default_version_name="%s"' % android_version_name
1044
dprankeeca4a782016-04-14 01:42:381045 # Canonicalize the arg string into a sorted, newline-separated list
1046 # of key-value pairs, and de-dup the keys if need be so that only
1047 # the last instance of each arg is listed.
1048 gn_args = gn_helpers.ToGNString(gn_helpers.FromGNArgs(gn_args))
1049
Ben Pastene65ccf6132018-11-08 00:47:591050 # If we're using the Simple Chrome SDK, add a comment at the top that
1051 # points to the doc. This must happen after the gn_helpers.ToGNString()
1052 # call above since gn_helpers strips comments.
1053 if vals['cros_passthrough']:
1054 simplechrome_comment = [
1055 '# These args are generated via the Simple Chrome SDK. See the link',
1056 '# below for more details:',
1057 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
1058 ]
1059 gn_args = '%s\n%s' % ('\n'.join(simplechrome_comment), gn_args)
1060
dpranke9dd5e252016-04-14 04:23:091061 args_file = vals.get('args_file', None)
1062 if args_file:
1063 gn_args = ('import("%s")\n' % vals['args_file']) + gn_args
dprankeeca4a782016-04-14 01:42:381064 return gn_args
dprankefe4602312015-04-08 16:20:351065
dprankecb4a2e242016-09-19 01:13:141066 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071067 isolate_map = self.ReadIsolateMap()
1068
Scott Graham3be4b4162017-09-12 00:41:411069 is_android = 'target_os="android"' in vals['gn_args']
Benjamin Pastene3bce864e2018-04-14 01:16:321070 is_simplechrome = vals.get('cros_passthrough', False)
Scott Graham3be4b4162017-09-12 00:41:411071 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:301072 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061073
kylechar39705682017-01-19 14:37:231074 # This should be true if tests with type='windowed_test_launcher' are
1075 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081076 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1077 # backends. Currently, tests are executed for the headless and X11 backends
1078 # and both can run under Xvfb.
1079 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1080 # backend.
Scott Graham3be4b4162017-09-12 00:41:411081 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251082
1083 asan = 'is_asan=true' in vals['gn_args']
1084 msan = 'is_msan=true' in vals['gn_args']
1085 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411086 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251087
dprankecb4a2e242016-09-19 01:13:141088 test_type = isolate_map[target]['type']
dprankefe0d35e2016-02-05 02:43:591089
dprankecb4a2e242016-09-19 01:13:141090 executable = isolate_map[target].get('executable', target)
Nico Weberd94b71a2018-02-22 22:00:301091 executable_suffix = '.exe' if is_win else ''
dprankefe0d35e2016-02-05 02:43:591092
dprankea55584f12015-07-22 00:52:471093 cmdline = []
Andrii Shyshkalovc158e0102018-01-10 05:52:001094 extra_files = [
1095 '../../.vpython',
1096 '../../testing/test_env.py',
1097 ]
dpranked8113582015-06-05 20:08:251098
dprankecb4a2e242016-09-19 01:13:141099 if test_type == 'nontest':
1100 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1101 output_path=None)
1102
Scott Graham3be4b4162017-09-12 00:41:411103 if is_android and test_type != "script":
bpastenee428ea92017-02-17 02:20:321104 cmdline = [
John Budorickfb97a852017-12-20 20:10:191105 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041106 '../../build/android/test_wrapper/logdog_wrapper.py',
1107 '--target', target,
hzl9ae14452017-04-04 23:38:021108 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481109 '--store-tombstones']
Scott Graham3be4b4162017-09-12 00:41:411110 elif is_fuchsia and test_type != 'script':
John Budorickfb97a852017-12-20 20:10:191111 cmdline = [
1112 '../../testing/test_env.py',
1113 os.path.join('bin', 'run_%s' % target),
1114 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321115 elif is_simplechrome and test_type != 'script':
1116 cmdline = [
1117 '../../testing/test_env.py',
1118 os.path.join('bin', 'run_%s' % target),
1119 ]
kylechar39705682017-01-19 14:37:231120 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001121 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471122 cmdline = [
dprankefe0d35e2016-02-05 02:43:591123 '../../testing/xvfb.py',
dprankefe0d35e2016-02-05 02:43:591124 './' + str(executable) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591125 '--test-launcher-bot-mode',
1126 '--asan=%d' % asan,
1127 '--msan=%d' % msan,
1128 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411129 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471130 ]
1131 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471132 cmdline = [
1133 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591134 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251135 '--test-launcher-bot-mode',
1136 '--asan=%d' % asan,
1137 '--msan=%d' % msan,
1138 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411139 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471140 ]
dpranke6abd8652015-08-28 03:21:111141 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241142 cmdline = []
1143 # If we're testing a CrOS simplechrome build, assume we need to launch a
1144 # VM first. So prepend the command to run with the VM launcher.
1145 # TODO(bpastene): Differentiate between CrOS VM and hardware tests.
1146 if is_simplechrome:
1147 cmdline = [os.path.join('bin', 'launch_cros_vm')]
1148 cmdline += [
dpranke6abd8652015-08-28 03:21:111149 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141150 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591151 ]
Dirk Prankef24e6b22018-03-27 20:12:301152 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471153 cmdline = [
1154 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591155 ]
dprankea55584f12015-07-22 00:52:471156 else:
1157 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1158 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251159
Abhishek Arya2f5f7342018-06-13 16:59:441160 if is_win and asan:
Alexander Dunaev384ba482018-03-21 17:56:121161 # Sandbox is not yet supported by ASAN for Windows.
1162 # Perhaps this is only needed for tests that use the sandbox?
1163 cmdline.append('--no-sandbox')
1164
dprankecb4a2e242016-09-19 01:13:141165 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591166
dpranked8113582015-06-05 20:08:251167 return cmdline, extra_files
1168
dpranke74559b52015-06-10 21:20:391169 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331170 return self.PathJoin(self.chromium_src_dir,
1171 self.ToSrcRelPath(build_path),
1172 *comps)
dpranked8113582015-06-05 20:08:251173
dprankeee5b51f62015-04-09 00:03:221174 def ToSrcRelPath(self, path):
1175 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501176 if path.startswith('//'):
1177 return path[2:].replace('/', self.sep)
1178 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351179
Dirk Pranke0fd41bcd2015-06-19 00:05:501180 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141181 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501182 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001183 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501184 if ret:
1185 return ret
1186
Dirk Prankef24e6b22018-03-27 20:12:301187 build_path = self.args.path
1188 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141189 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301190 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141191 gn_output_path = output_path + '.gn'
1192
dpranke7837fc362015-11-19 03:54:161193 inp = self.ReadInputJSON(['files', 'test_targets',
1194 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321195 if self.args.verbose:
1196 self.Print()
1197 self.Print('analyze input:')
1198 self.PrintJSON(inp)
1199 self.Print()
1200
dpranke76734662015-04-16 02:17:501201
dpranke7c5f614d2015-07-22 23:43:391202 # This shouldn't normally happen, but could due to unusual race conditions,
1203 # like a try job that gets scheduled before a patch lands but runs after
1204 # the patch has landed.
1205 if not inp['files']:
1206 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161207 self.WriteJSON({
1208 'status': 'No dependency',
1209 'compile_targets': [],
1210 'test_targets': [],
1211 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391212 return 0
1213
dprankecb4a2e242016-09-19 01:13:141214 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161215 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561216
dprankecb4a2e242016-09-19 01:13:141217 isolate_map = self.ReadIsolateMap()
1218 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1219 isolate_map, inp['additional_compile_targets'])
1220 if err:
1221 raise MBErr(err)
1222
1223 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1224 isolate_map, inp['test_targets'])
1225 if err:
1226 raise MBErr(err)
1227 labels_to_targets = {}
1228 for i, label in enumerate(gn_inp['test_targets']):
1229 labels_to_targets[label] = inp['test_targets'][i]
1230
dprankef61de2f2015-05-14 04:09:561231 try:
dprankecb4a2e242016-09-19 01:13:141232 self.WriteJSON(gn_inp, gn_input_path)
1233 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
1234 ret, _, _ = self.Run(cmd, force_verbose=True)
1235 if ret:
1236 return ret
dpranke067d0142015-05-14 22:52:451237
dprankecb4a2e242016-09-19 01:13:141238 gn_outp_str = self.ReadFile(gn_output_path)
1239 try:
1240 gn_outp = json.loads(gn_outp_str)
1241 except Exception as e:
1242 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1243 % (repr(gn_outp_str), str(e)))
1244 raise
1245
1246 outp = {}
1247 if 'status' in gn_outp:
1248 outp['status'] = gn_outp['status']
1249 if 'error' in gn_outp:
1250 outp['error'] = gn_outp['error']
1251 if 'invalid_targets' in gn_outp:
1252 outp['invalid_targets'] = gn_outp['invalid_targets']
1253 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491254 all_input_compile_targets = sorted(
1255 set(inp['test_targets'] + inp['additional_compile_targets']))
1256
1257 # If we're building 'all', we can throw away the rest of the targets
1258 # since they're redundant.
dpranke385a3102016-09-20 22:04:081259 if 'all' in gn_outp['compile_targets']:
1260 outp['compile_targets'] = ['all']
1261 else:
Dirk Pranke45165072017-11-08 04:57:491262 outp['compile_targets'] = gn_outp['compile_targets']
1263
1264 # crbug.com/736215: When GN returns targets back, for targets in
1265 # the default toolchain, GN will have generated a phony ninja
1266 # target matching the label, and so we can safely (and easily)
1267 # transform any GN label into the matching ninja target. For
1268 # targets in other toolchains, though, GN doesn't generate the
1269 # phony targets, and we don't know how to turn the labels into
1270 # compile targets. In this case, we also conservatively give up
1271 # and build everything. Probably the right thing to do here is
1272 # to have GN return the compile targets directly.
1273 if any("(" in target for target in outp['compile_targets']):
1274 self.Print('WARNING: targets with non-default toolchains were '
1275 'found, building everything instead.')
1276 outp['compile_targets'] = all_input_compile_targets
1277 else:
dpranke385a3102016-09-20 22:04:081278 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491279 label.replace('//', '') for label in outp['compile_targets']]
1280
1281 # Windows has a maximum command line length of 8k; even Linux
1282 # maxes out at 128k; if analyze returns a *really long* list of
1283 # targets, we just give up and conservatively build everything instead.
1284 # Probably the right thing here is for ninja to support response
1285 # files as input on the command line
1286 # (see https://github.com/ninja-build/ninja/issues/1355).
1287 if len(' '.join(outp['compile_targets'])) > 7*1024:
1288 self.Print('WARNING: Too many compile targets were affected.')
1289 self.Print('WARNING: Building everything instead to avoid '
1290 'command-line length issues.')
1291 outp['compile_targets'] = all_input_compile_targets
1292
1293
dprankecb4a2e242016-09-19 01:13:141294 if 'test_targets' in gn_outp:
1295 outp['test_targets'] = [
1296 labels_to_targets[label] for label in gn_outp['test_targets']]
1297
1298 if self.args.verbose:
1299 self.Print()
1300 self.Print('analyze output:')
1301 self.PrintJSON(outp)
1302 self.Print()
1303
1304 self.WriteJSON(outp, output_path)
1305
dprankef61de2f2015-05-14 04:09:561306 finally:
dprankecb4a2e242016-09-19 01:13:141307 if self.Exists(gn_input_path):
1308 self.RemoveFile(gn_input_path)
1309 if self.Exists(gn_output_path):
1310 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351311
1312 return 0
1313
dpranked8113582015-06-05 20:08:251314 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301315 path = self.args.input_path
1316 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351317 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321318 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351319
1320 try:
1321 inp = json.loads(self.ReadFile(path))
1322 except Exception as e:
1323 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321324 (path, e), output_path)
dpranked8113582015-06-05 20:08:251325
1326 for k in required_keys:
1327 if not k in inp:
1328 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1329 output_path)
dprankefe4602312015-04-08 16:20:351330
1331 return inp
1332
dpranked5b2b9432015-06-23 16:55:301333 def WriteFailureAndRaise(self, msg, output_path):
1334 if output_path:
dprankee0547cd2015-09-15 01:27:401335 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351336 raise MBErr(msg)
1337
dprankee0547cd2015-09-15 01:27:401338 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321339 try:
dprankee0547cd2015-09-15 01:27:401340 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1341 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321342 except Exception as e:
1343 raise MBErr('Error %s writing to the output path "%s"' %
1344 (e, path))
dprankefe4602312015-04-08 16:20:351345
aneeshmde50f472016-04-01 01:13:101346 def CheckCompile(self, master, builder):
1347 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1348 url = urllib2.quote(url_template.format(master=master, builder=builder),
1349 safe=':/()?=')
1350 try:
1351 builds = json.loads(self.Fetch(url))
1352 except Exception as e:
1353 return str(e)
1354 successes = sorted(
1355 [int(x) for x in builds.keys() if "text" in builds[x] and
1356 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1357 reverse=True)
1358 if not successes:
1359 return "no successful builds"
1360 build = builds[str(successes[0])]
1361 step_names = set([step["name"] for step in build["steps"]])
1362 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1363 if compile_indicators & step_names:
1364 return "compiles"
1365 return "does not compile"
1366
dpranke3cec199c2015-09-22 23:29:021367 def PrintCmd(self, cmd, env):
1368 if self.platform == 'win32':
1369 env_prefix = 'set '
1370 env_quoter = QuoteForSet
1371 shell_quoter = QuoteForCmd
1372 else:
1373 env_prefix = ''
1374 env_quoter = pipes.quote
1375 shell_quoter = pipes.quote
1376
1377 def print_env(var):
1378 if env and var in env:
1379 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1380
dprankeec079262016-06-07 02:21:201381 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021382
dpranke8c2cfd32015-09-17 20:12:331383 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351384 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021385 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351386
dprankecda00332015-04-11 04:18:321387 def PrintJSON(self, obj):
1388 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1389
dpranke751516a2015-10-03 01:11:341390 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301391 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381392 if self.platform == 'win32':
1393 # On Windows use the batch script since there is no exe
1394 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1395 else:
1396 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341397 if self.args.jobs:
1398 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1399 ninja_cmd.append(target)
1400 ret, _, _ = self.Run(ninja_cmd, force_verbose=False, buffer_output=False)
1401 return ret
1402
1403 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351404 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401405 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021406 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351407 if self.args.dryrun:
1408 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401409
dpranke751516a2015-10-03 01:11:341410 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401411 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341412 if ret:
1413 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351414 if out:
dprankeee5b51f62015-04-09 00:03:221415 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351416 if err:
dprankeee5b51f62015-04-09 00:03:221417 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351418 return ret, out, err
1419
dpranke751516a2015-10-03 01:11:341420 def Call(self, cmd, env=None, buffer_output=True):
1421 if buffer_output:
1422 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1423 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1424 env=env)
1425 out, err = p.communicate()
1426 else:
1427 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1428 env=env)
1429 p.wait()
1430 out = err = ''
dprankefe4602312015-04-08 16:20:351431 return p.returncode, out, err
1432
1433 def ExpandUser(self, path):
1434 # This function largely exists so it can be overridden for testing.
1435 return os.path.expanduser(path)
1436
1437 def Exists(self, path):
1438 # This function largely exists so it can be overridden for testing.
1439 return os.path.exists(path)
1440
dpranke867bcf4a2016-03-14 22:28:321441 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501442 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321443 f = urllib2.urlopen(url)
1444 contents = f.read()
1445 f.close()
1446 return contents
1447
dprankec3441d12015-06-23 23:01:351448 def MaybeMakeDirectory(self, path):
1449 try:
1450 os.makedirs(path)
1451 except OSError, e:
1452 if e.errno != errno.EEXIST:
1453 raise
1454
dpranke8c2cfd32015-09-17 20:12:331455 def PathJoin(self, *comps):
1456 # This function largely exists so it can be overriden for testing.
1457 return os.path.join(*comps)
1458
dpranke030d7a6d2016-03-26 17:23:501459 def Print(self, *args, **kwargs):
1460 # This function largely exists so it can be overridden for testing.
1461 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101462 if kwargs.get('stream', sys.stdout) == sys.stdout:
1463 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501464
dprankefe4602312015-04-08 16:20:351465 def ReadFile(self, path):
1466 # This function largely exists so it can be overriden for testing.
1467 with open(path) as fp:
1468 return fp.read()
1469
dpranke030d7a6d2016-03-26 17:23:501470 def RelPath(self, path, start='.'):
1471 # This function largely exists so it can be overriden for testing.
1472 return os.path.relpath(path, start)
1473
dprankef61de2f2015-05-14 04:09:561474 def RemoveFile(self, path):
1475 # This function largely exists so it can be overriden for testing.
1476 os.remove(path)
1477
dprankec161aa92015-09-14 20:21:131478 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331479 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131480 # In other places in chromium, we often have to retry this command
1481 # because we're worried about other processes still holding on to
1482 # file handles, but when MB is invoked, it will be early enough in the
1483 # build that their should be no other processes to interfere. We
1484 # can change this if need be.
1485 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1486 else:
1487 shutil.rmtree(abs_path, ignore_errors=True)
1488
Dirk Prankef24e6b22018-03-27 20:12:301489 def TempDir(self):
1490 # This function largely exists so it can be overriden for testing.
1491 return tempfile.mkdtemp(prefix='mb_')
1492
dprankef61de2f2015-05-14 04:09:561493 def TempFile(self, mode='w'):
1494 # This function largely exists so it can be overriden for testing.
1495 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1496
dprankee0547cd2015-09-15 01:27:401497 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351498 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401499 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301500 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351501 with open(path, 'w') as fp:
1502 return fp.write(contents)
1503
dprankef61de2f2015-05-14 04:09:561504
dprankefe4602312015-04-08 16:20:351505class MBErr(Exception):
1506 pass
1507
1508
dpranke3cec199c2015-09-22 23:29:021509# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1510# details of this next section, which handles escaping command lines
1511# so that they can be copied and pasted into a cmd window.
1512UNSAFE_FOR_SET = set('^<>&|')
1513UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1514ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1515
1516
1517def QuoteForSet(arg):
1518 if any(a in UNSAFE_FOR_SET for a in arg):
1519 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1520 return arg
1521
1522
1523def QuoteForCmd(arg):
1524 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021525 if arg == '' or ' ' in arg or '"' in arg:
1526 quote_re = re.compile(r'(\\*)"')
1527 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1528
1529 # Then check to see if the arg contains any metacharacters other than
1530 # double quotes; if it does, quote everything (including the double
1531 # quotes) for safety.
1532 if any(a in UNSAFE_FOR_CMD for a in arg):
1533 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1534 return arg
1535
1536
dprankefe4602312015-04-08 16:20:351537if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591538 sys.exit(main(sys.argv[1:]))