blob: 7c52b5dec2fcefd0996216afeb233948c90d43f6 [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',
Kenneth Russell2e75e2f2018-11-15 22:37:28371 '--collapse_symlinks',
Dirk Prankef24e6b22018-03-27 20:12:30372 '-s', self.PathJoin(self.args.path, self.args.target + '.isolated'),
373 '-o', zip_dir
374 ]
375 self.Run(remap_cmd)
376
377 zip_path = self.args.zip_path
378 with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as fp:
379 for root, _, files in os.walk(zip_dir):
380 for filename in files:
381 path = self.PathJoin(root, filename)
382 fp.write(path, self.RelPath(path, zip_dir))
383 finally:
384 if zip_dir:
385 self.RemoveDirectory(zip_dir)
Dirk Pranke8cb6aa782017-12-16 02:31:33386
Robert Iannucci5a9d75f62018-03-02 05:28:20387 @staticmethod
388 def _AddBaseSoftware(cmd):
389 # HACK(iannucci): These packages SHOULD NOT BE HERE.
390 # Remove method once Swarming Pool Task Templates are implemented.
391 # crbug.com/812428
392
393 # Add in required base software. This should be kept in sync with the
394 # `swarming` recipe module in build.git. All references to `swarming_module`
395 # below are purely due to this.
396 cipd_packages = [
397 ('infra/python/cpython/${platform}',
398 'version:2.7.14.chromium14'),
399 ('infra/tools/luci/logdog/butler/${platform}',
400 'git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c'),
401 ('infra/tools/luci/vpython-native/${platform}',
Vadim Shtayurabc21f8902018-07-27 22:11:43402 'git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc'),
Robert Iannucci5a9d75f62018-03-02 05:28:20403 ('infra/tools/luci/vpython/${platform}',
Vadim Shtayurabc21f8902018-07-27 22:11:43404 'git_revision:b6cdec8586c9f8d3d728b1bc0bd4331330ba66fc'),
Robert Iannucci5a9d75f62018-03-02 05:28:20405 ]
406 for pkg, vers in cipd_packages:
407 cmd.append('--cipd-package=.swarming_module:%s:%s' % (pkg, vers))
408
409 # Add packages to $PATH
410 cmd.extend([
411 '--env-prefix=PATH', '.swarming_module',
412 '--env-prefix=PATH', '.swarming_module/bin',
413 ])
414
415 # Add cache directives for vpython.
416 vpython_cache_path = '.swarming_module_cache/vpython'
417 cmd.extend([
418 '--named-cache=swarming_module_cache_vpython', vpython_cache_path,
419 '--env-prefix=VPYTHON_VIRTUALENV_ROOT', vpython_cache_path,
420 ])
421
Dirk Pranke8cb6aa782017-12-16 02:31:33422 def _RunUnderSwarming(self, build_dir, target):
423 # TODO(dpranke): Look up the information for the target in
424 # the //testing/buildbot.json file, if possible, so that we
425 # can determine the isolate target, command line, and additional
426 # swarming parameters, if possible.
427 #
428 # TODO(dpranke): Also, add support for sharding and merging results.
429 dimensions = []
430 for k, v in self._DefaultDimensions() + self.args.dimensions:
431 dimensions += ['-d', k, v]
432
433 cmd = [
434 self.executable,
435 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
436 'archive',
437 '-s',
438 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
439 '-I', 'isolateserver.appspot.com',
440 ]
441 ret, out, _ = self.Run(cmd, force_verbose=False)
442 if ret:
443 return ret
444
445 isolated_hash = out.splitlines()[0].split()[0]
446 cmd = [
447 self.executable,
448 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
449 'run',
450 '-s', isolated_hash,
451 '-I', 'isolateserver.appspot.com',
452 '-S', 'chromium-swarm.appspot.com',
453 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20454 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33455 if self.args.extra_args:
456 cmd += ['--'] + self.args.extra_args
457 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
458 return ret
459
460 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50461 cmd = [
dpranke751516a2015-10-03 01:11:34462 self.executable,
463 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
464 'run',
465 '-s',
dpranke030d7a6d2016-03-26 17:23:50466 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33467 ]
dpranke030d7a6d2016-03-26 17:23:50468 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33469 cmd += ['--'] + self.args.extra_args
470 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34471 return ret
472
Dirk Pranke8cb6aa782017-12-16 02:31:33473 def _DefaultDimensions(self):
474 if not self.args.default_dimensions:
475 return []
476
477 # This code is naive and just picks reasonable defaults per platform.
478 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40479 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33480 elif self.platform.startswith('linux'):
481 os_dim = ('os', 'Ubuntu-14.04')
482 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40483 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33484 else:
485 raise MBErr('unrecognized platform string "%s"' % self.platform)
486
487 return [('pool', 'Chrome'),
488 ('cpu', 'x86-64'),
489 os_dim]
490
dpranke0cafc162016-03-19 00:41:10491 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35492 errs = []
493
494 # Read the file to make sure it parses.
495 self.ReadConfigFile()
496
dpranke3be00142016-03-17 22:46:04497 # Build a list of all of the configs referenced by builders.
dprankefe4602312015-04-08 16:20:35498 all_configs = {}
dprankefe4602312015-04-08 16:20:35499 for master in self.masters:
dpranke3be00142016-03-17 22:46:04500 for config in self.masters[master].values():
shenghuazhang804b21542016-10-11 02:06:49501 if isinstance(config, dict):
502 for c in config.values():
dprankeb9380a12016-07-21 21:44:09503 all_configs[c] = master
504 else:
505 all_configs[config] = master
dprankefe4602312015-04-08 16:20:35506
dpranke9dd5e252016-04-14 04:23:09507 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35508 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09509 if config.startswith('//'):
510 if not self.Exists(self.ToAbsPath(config)):
511 errs.append('Unknown args file "%s" referenced from "%s".' %
512 (config, loc))
513 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35514 errs.append('Unknown config "%s" referenced from "%s".' %
515 (config, loc))
516
517 # Check that every actual config is actually referenced.
518 for config in self.configs:
519 if not config in all_configs:
520 errs.append('Unused config "%s".' % config)
521
522 # Figure out the whole list of mixins, and check that every mixin
523 # listed by a config or another mixin actually exists.
524 referenced_mixins = set()
525 for config, mixins in self.configs.items():
526 for mixin in mixins:
527 if not mixin in self.mixins:
528 errs.append('Unknown mixin "%s" referenced by config "%s".' %
529 (mixin, config))
530 referenced_mixins.add(mixin)
531
532 for mixin in self.mixins:
533 for sub_mixin in self.mixins[mixin].get('mixins', []):
534 if not sub_mixin in self.mixins:
535 errs.append('Unknown mixin "%s" referenced by mixin "%s".' %
536 (sub_mixin, mixin))
537 referenced_mixins.add(sub_mixin)
538
539 # Check that every mixin defined is actually referenced somewhere.
540 for mixin in self.mixins:
541 if not mixin in referenced_mixins:
542 errs.append('Unreferenced mixin "%s".' % mixin)
543
dpranke255085e2016-03-16 05:23:59544 # If we're checking the Chromium config, check that the 'chromium' bots
545 # which build public artifacts do not include the chrome_with_codecs mixin.
546 if self.args.config_file == self.default_config:
547 if 'chromium' in self.masters:
548 for builder in self.masters['chromium']:
549 config = self.masters['chromium'][builder]
550 def RecurseMixins(current_mixin):
551 if current_mixin == 'chrome_with_codecs':
552 errs.append('Public artifact builder "%s" can not contain the '
553 '"chrome_with_codecs" mixin.' % builder)
554 return
555 if not 'mixins' in self.mixins[current_mixin]:
556 return
557 for mixin in self.mixins[current_mixin]['mixins']:
558 RecurseMixins(mixin)
dalecurtis56fd27e2016-03-09 23:06:41559
dpranke255085e2016-03-16 05:23:59560 for mixin in self.configs[config]:
561 RecurseMixins(mixin)
562 else:
563 errs.append('Missing "chromium" master. Please update this '
564 'proprietary codecs check with the name of the master '
565 'responsible for public build artifacts.')
dalecurtis56fd27e2016-03-09 23:06:41566
dprankefe4602312015-04-08 16:20:35567 if errs:
dpranke4323c80632015-08-10 22:53:54568 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17569 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35570
dpranke0cafc162016-03-19 00:41:10571 if print_ok:
572 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35573 return 0
574
575 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30576 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34577
dprankef37aebb92016-09-23 01:14:49578 vals = self.DefaultVals()
dpranke751516a2015-10-03 01:11:34579 if self.args.builder or self.args.master or self.args.config:
580 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11581 # Re-run gn gen in order to ensure the config is consistent with the
582 # build dir.
583 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34584 return vals
585
Dirk Pranked181a1a2017-12-14 01:47:11586 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
587 'toolchain.ninja')
588 if not self.Exists(toolchain_path):
589 self.Print('Must either specify a path to an existing GN build dir '
590 'or pass in a -m/-b pair or a -c flag to specify the '
591 'configuration')
592 return {}
dpranke751516a2015-10-03 01:11:34593
Dirk Pranked181a1a2017-12-14 01:47:11594 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34595 return vals
596
dprankef37aebb92016-09-23 01:14:49597 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58598 args_contents = ""
599 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
600 if self.Exists(gn_args_path):
601 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34602 gn_args = []
603 for l in args_contents.splitlines():
604 fields = l.split(' ')
605 name = fields[0]
606 val = ' '.join(fields[2:])
607 gn_args.append('%s=%s' % (name, val))
608
dprankef37aebb92016-09-23 01:14:49609 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34610
611 def Lookup(self):
dprankef37aebb92016-09-23 01:14:49612 vals = self.ReadIOSBotConfig()
dprankee0f486f2015-11-19 23:42:00613 if not vals:
614 self.ReadConfigFile()
615 config = self.ConfigFromArgs()
dpranke9dd5e252016-04-14 04:23:09616 if config.startswith('//'):
617 if not self.Exists(self.ToAbsPath(config)):
618 raise MBErr('args file "%s" not found' % config)
dprankef37aebb92016-09-23 01:14:49619 vals = self.DefaultVals()
620 vals['args_file'] = config
dpranke9dd5e252016-04-14 04:23:09621 else:
622 if not config in self.configs:
623 raise MBErr('Config "%s" not found in %s' %
624 (config, self.args.config_file))
625 vals = self.FlattenConfig(config)
dpranke751516a2015-10-03 01:11:34626 return vals
dprankefe4602312015-04-08 16:20:35627
dprankef37aebb92016-09-23 01:14:49628 def ReadIOSBotConfig(self):
dprankee0f486f2015-11-19 23:42:00629 if not self.args.master or not self.args.builder:
630 return {}
631 path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
632 self.args.master, self.args.builder + '.json')
633 if not self.Exists(path):
634 return {}
635
636 contents = json.loads(self.ReadFile(path))
dprankee0f486f2015-11-19 23:42:00637 gn_args = ' '.join(contents.get('gn_args', []))
638
dprankef37aebb92016-09-23 01:14:49639 vals = self.DefaultVals()
640 vals['gn_args'] = gn_args
dprankef37aebb92016-09-23 01:14:49641 return vals
dprankee0f486f2015-11-19 23:42:00642
dprankefe4602312015-04-08 16:20:35643 def ReadConfigFile(self):
644 if not self.Exists(self.args.config_file):
645 raise MBErr('config file not found at %s' % self.args.config_file)
646
647 try:
648 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
649 except SyntaxError as e:
650 raise MBErr('Failed to parse config file "%s": %s' %
651 (self.args.config_file, e))
652
dprankefe4602312015-04-08 16:20:35653 self.configs = contents['configs']
dprankefe4602312015-04-08 16:20:35654 self.masters = contents['masters']
655 self.mixins = contents['mixins']
dprankefe4602312015-04-08 16:20:35656
dprankecb4a2e242016-09-19 01:13:14657 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20658 if not self.args.isolate_map_files:
659 self.args.isolate_map_files = [self.default_isolate_map]
660
661 for f in self.args.isolate_map_files:
662 if not self.Exists(f):
663 raise MBErr('isolate map file not found at %s' % f)
664 isolate_maps = {}
665 for isolate_map in self.args.isolate_map_files:
666 try:
667 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
668 duplicates = set(isolate_map).intersection(isolate_maps)
669 if duplicates:
670 raise MBErr(
671 'Duplicate targets in isolate map files: %s.' %
672 ', '.join(duplicates))
673 isolate_maps.update(isolate_map)
674 except SyntaxError as e:
675 raise MBErr(
676 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
677 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14678
dprankefe4602312015-04-08 16:20:35679 def ConfigFromArgs(self):
680 if self.args.config:
681 if self.args.master or self.args.builder:
682 raise MBErr('Can not specific both -c/--config and -m/--master or '
683 '-b/--builder')
684
685 return self.args.config
686
687 if not self.args.master or not self.args.builder:
688 raise MBErr('Must specify either -c/--config or '
689 '(-m/--master and -b/--builder)')
690
691 if not self.args.master in self.masters:
692 raise MBErr('Master name "%s" not found in "%s"' %
693 (self.args.master, self.args.config_file))
694
695 if not self.args.builder in self.masters[self.args.master]:
696 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
697 (self.args.builder, self.args.master, self.args.config_file))
698
dprankeb9380a12016-07-21 21:44:09699 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49700 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09701 if self.args.phase is None:
702 raise MBErr('Must specify a build --phase for %s on %s' %
703 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49704 phase = str(self.args.phase)
705 if phase not in config:
706 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09707 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49708 return config[phase]
dprankeb9380a12016-07-21 21:44:09709
710 if self.args.phase is not None:
711 raise MBErr('Must not specify a build --phase for %s on %s' %
712 (self.args.builder, self.args.master))
713 return config
dprankefe4602312015-04-08 16:20:35714
715 def FlattenConfig(self, config):
716 mixins = self.configs[config]
dprankef37aebb92016-09-23 01:14:49717 vals = self.DefaultVals()
dprankefe4602312015-04-08 16:20:35718
719 visited = []
720 self.FlattenMixins(mixins, vals, visited)
721 return vals
722
dprankef37aebb92016-09-23 01:14:49723 def DefaultVals(self):
724 return {
725 'args_file': '',
726 'cros_passthrough': False,
727 'gn_args': '',
dprankef37aebb92016-09-23 01:14:49728 }
729
dprankefe4602312015-04-08 16:20:35730 def FlattenMixins(self, mixins, vals, visited):
731 for m in mixins:
732 if m not in self.mixins:
733 raise MBErr('Unknown mixin "%s"' % m)
dprankeee5b51f62015-04-09 00:03:22734
dprankefe4602312015-04-08 16:20:35735 visited.append(m)
736
737 mixin_vals = self.mixins[m]
dpranke73ed0d62016-04-25 19:18:34738
739 if 'cros_passthrough' in mixin_vals:
740 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
Dirk Pranke6b99f072017-04-05 00:58:30741 if 'args_file' in mixin_vals:
742 if vals['args_file']:
743 raise MBErr('args_file specified multiple times in mixins '
744 'for %s on %s' % (self.args.builder, self.args.master))
745 vals['args_file'] = mixin_vals['args_file']
dprankefe4602312015-04-08 16:20:35746 if 'gn_args' in mixin_vals:
747 if vals['gn_args']:
748 vals['gn_args'] += ' ' + mixin_vals['gn_args']
749 else:
750 vals['gn_args'] = mixin_vals['gn_args']
dpranke73ed0d62016-04-25 19:18:34751
dprankefe4602312015-04-08 16:20:35752 if 'mixins' in mixin_vals:
753 self.FlattenMixins(mixin_vals['mixins'], vals, visited)
754 return vals
755
Takuto Ikuta9dffd7e2018-09-05 01:04:00756 def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
Dirk Prankef24e6b22018-03-27 20:12:30757 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50758
Takuto Ikuta9dffd7e2018-09-05 01:04:00759 if check:
760 cmd = self.GNCmd('gen', build_dir, '--check')
761 else:
762 cmd = self.GNCmd('gen', build_dir)
dprankeeca4a782016-04-14 01:42:38763 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:09764 if compute_inputs_for_analyze:
765 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:38766
767 # Since GN hasn't run yet, the build directory may not even exist.
768 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
769
770 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:54771 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:39772
dpranke751516a2015-10-03 01:11:34773 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:39774 # We need GN to generate the list of runtime dependencies for
775 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:14776 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:39777 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:00778 path = self.args.swarming_targets_file
779 if not self.Exists(path):
780 self.WriteFailureAndRaise('"%s" does not exist' % path,
781 output_path=None)
782 contents = self.ReadFile(path)
Erik Chen42df41d2018-08-21 17:13:31783 isolate_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:00784
dprankecb4a2e242016-09-19 01:13:14785 isolate_map = self.ReadIsolateMap()
Erik Chen42df41d2018-08-21 17:13:31786 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:00787 if err:
dprankecb4a2e242016-09-19 01:13:14788 raise MBErr(err)
dpranke74559b52015-06-10 21:20:39789
dpranke751516a2015-10-03 01:11:34790 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:14791 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:39792 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
793
dprankefe4602312015-04-08 16:20:35794 ret, _, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:40795 if ret:
796 # If `gn gen` failed, we should exit early rather than trying to
797 # generate isolates. Run() will have already logged any error output.
798 self.Print('GN gen failed: %d' % ret)
799 return ret
dpranke74559b52015-06-10 21:20:39800
Erik Chen42df41d2018-08-21 17:13:31801 if getattr(self.args, 'swarming_targets_file', None):
802 return self.GenerateIsolates(vals, isolate_targets, isolate_map,
803 build_dir)
804
805 return 0
806
807 def RunGNGenAllIsolates(self, vals):
808 """
809 This command generates all .isolate files.
810
811 This command assumes that "mb.py gen" has already been run, as it relies on
812 "gn ls" to fetch all gn targets. If uses that output, combined with the
813 isolate_map, to determine all isolates that can be generated for the current
814 gn configuration.
815 """
816 build_dir = self.args.path
817 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
818 force_verbose=False)
819 if ret:
820 # If `gn ls` failed, we should exit early rather than trying to
821 # generate isolates.
822 self.Print('GN ls failed: %d' % ret)
823 return ret
824
825 # Create a reverse map from isolate label to isolate dict.
826 isolate_map = self.ReadIsolateMap()
827 isolate_dict_map = {}
828 for key, isolate_dict in isolate_map.iteritems():
829 isolate_dict_map[isolate_dict['label']] = isolate_dict
830 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
831
832 runtime_deps = []
833
834 isolate_targets = []
835 # For every GN target, look up the isolate dict.
836 for line in output.splitlines():
837 target = line.strip()
838 if target in isolate_dict_map:
839 if isolate_dict_map[target]['type'] == 'additional_compile_target':
840 # By definition, additional_compile_targets are not tests, so we
841 # shouldn't generate isolates for them.
842 continue
843
844 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
845 runtime_deps.append(target)
846
847 # Now we need to run "gn gen" again with --runtime-deps-list-file
848 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
849 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
850 cmd = self.GNCmd('gen', build_dir)
851 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
852 self.Run(cmd)
853
854 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
855
856 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
857 """
858 Generates isolates for a list of ninja targets.
859
860 Ninja targets are transformed to GN targets via isolate_map.
861
862 This function assumes that a previous invocation of "mb.py gen" has
863 generated runtime deps for all targets.
864 """
jbudoricke3c4f95e2016-04-28 23:17:38865 android = 'target_os="android"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:42866 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:30867 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Erik Chen42df41d2018-08-21 17:13:31868 for target in ninja_targets:
869 # TODO(https://crbug.com/876065): 'official_tests' use
870 # type='additional_compile_target' to isolate tests. This is not the
871 # intended use for 'additional_compile_target'.
872 if (isolate_map[target]['type'] == 'additional_compile_target' and
873 target != 'official_tests'):
874 # By definition, additional_compile_targets are not tests, so we
875 # shouldn't generate isolates for them.
876 self.Print('Cannot generate isolate for %s since it is an '
877 'additional_compile_target.' % target)
878 return 1
879 elif android:
jbudoricke3c4f95e2016-04-28 23:17:38880 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:02881 # will result in runtime_deps associated with the stamp file, while the
882 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:44883 label = isolate_map[target]['label']
jbudorick91c8a6012016-01-29 23:20:02884 runtime_deps_targets = [
dprankecb4a2e242016-09-19 01:13:14885 target + '.runtime_deps',
dpranke48ccf8f2016-03-28 23:58:28886 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
Kevin Marshallf35fa5f2018-01-29 19:24:42887 elif fuchsia:
888 # Only emit a runtime deps file for the group() target on Fuchsia.
Abhishek Arya2f5f7342018-06-13 16:59:44889 label = isolate_map[target]['label']
Kevin Marshallf35fa5f2018-01-29 19:24:42890 runtime_deps_targets = [
891 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
dprankecb4a2e242016-09-19 01:13:14892 elif (isolate_map[target]['type'] == 'script' or
Roberto Carrillo1460da852018-12-14 17:10:39893 isolate_map[target]['type'] == 'fuzzer' or
dprankecb4a2e242016-09-19 01:13:14894 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:11895 # For script targets, the build target is usually a group,
896 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:49897 # for the label, which lives under the obj/ directory, but it may
898 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:44899 label = isolate_map[target]['label']
dpranke48ccf8f2016-03-28 23:58:28900 runtime_deps_targets = [
901 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
Nico Weberd94b71a2018-02-22 22:00:30902 if win:
eyaich82d5ac942016-11-03 12:13:49903 runtime_deps_targets += [ target + '.exe.runtime_deps' ]
904 else:
905 runtime_deps_targets += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:30906 elif win:
dpranke48ccf8f2016-03-28 23:58:28907 runtime_deps_targets = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:52908 else:
dpranke48ccf8f2016-03-28 23:58:28909 runtime_deps_targets = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:02910
Dirk Prankeb3b725c2019-02-16 02:18:41911 # TODO(crbug.com/932700): If there are multiple .runtime_deps files
912 # for a target, it's actually unclear which one is the right one to use.
913 # It's possible to get multiple files because different target types
914 # store them in different places, and if you change the target type
915 # (and don't do a clobber build), you might find an old one and a new
916 # one. The proper fix is to make sure we only get one file.
917 newest_path = None
918 newest_path_mtime = 0
dpranke48ccf8f2016-03-28 23:58:28919 for r in runtime_deps_targets:
Dirk Prankeb3b725c2019-02-16 02:18:41920 path = self.ToAbsPath(build_dir, r)
921 if self.Exists(path):
922 mtime = self.Mtime(path)
923 if mtime > newest_path_mtime:
924 newest_path = path
925 newest_path_mtime = mtime
926
927 if not newest_path:
dpranke48ccf8f2016-03-28 23:58:28928 raise MBErr('did not generate any of %s' %
929 ', '.join(runtime_deps_targets))
dpranke74559b52015-06-10 21:20:39930
dprankecb4a2e242016-09-19 01:13:14931 command, extra_files = self.GetIsolateCommand(target, vals)
Dirk Prankeb3b725c2019-02-16 02:18:41932 runtime_deps = self.ReadFile(newest_path).splitlines()
dpranked5b2b9432015-06-23 16:55:30933
Erik Chen42df41d2018-08-21 17:13:31934 canonical_target = target.replace(':','_').replace('/','_')
935 self.WriteIsolateFiles(build_dir, command, canonical_target, runtime_deps,
dpranke751516a2015-10-03 01:11:34936 extra_files)
dpranked5b2b9432015-06-23 16:55:30937
dpranke751516a2015-10-03 01:11:34938 return 0
939
940 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:30941 target = self.args.target
dprankecb4a2e242016-09-19 01:13:14942 isolate_map = self.ReadIsolateMap()
943 err, labels = self.MapTargetsToLabels(isolate_map, [target])
944 if err:
945 raise MBErr(err)
946 label = labels[0]
dpranke751516a2015-10-03 01:11:34947
Dirk Prankef24e6b22018-03-27 20:12:30948 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:14949 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:34950
dprankeeca4a782016-04-14 01:42:38951 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:20952 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:34953 if ret:
dpranke030d7a6d2016-03-26 17:23:50954 if out:
955 self.Print(out)
dpranke751516a2015-10-03 01:11:34956 return ret
957
958 runtime_deps = out.splitlines()
959
960 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
961 extra_files)
962
963 ret, _, _ = self.Run([
964 self.executable,
965 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
966 'check',
967 '-i',
968 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
969 '-s',
970 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
971 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:30972
dprankefe4602312015-04-08 16:20:35973 return ret
974
dpranke751516a2015-10-03 01:11:34975 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps,
976 extra_files):
977 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
978 self.WriteFile(isolate_path,
979 pprint.pformat({
980 'variables': {
981 'command': command,
982 'files': sorted(runtime_deps + extra_files),
983 }
984 }) + '\n')
985
986 self.WriteJSON(
987 {
988 'args': [
989 '--isolated',
990 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
991 '--isolate',
992 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
993 ],
994 'dir': self.chromium_src_dir,
995 'version': 1,
996 },
997 isolate_path + 'd.gen.json',
998 )
999
dprankecb4a2e242016-09-19 01:13:141000 def MapTargetsToLabels(self, isolate_map, targets):
1001 labels = []
1002 err = ''
1003
dprankecb4a2e242016-09-19 01:13:141004 for target in targets:
1005 if target == 'all':
1006 labels.append(target)
1007 elif target.startswith('//'):
1008 labels.append(target)
1009 else:
1010 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421011 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141012 err += ('test target "%s" type is unknown\n' % target)
1013 else:
thakis024d6f32017-05-16 23:21:421014 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141015 else:
1016 err += ('target "%s" not found in '
1017 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1018
1019 return err, labels
1020
dprankeeca4a782016-04-14 01:42:381021 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461022 if self.platform == 'linux2':
1023 subdir, exe = 'linux64', 'gn'
1024 elif self.platform == 'darwin':
1025 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251026 elif self.platform == 'aix6':
1027 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461028 else:
1029 subdir, exe = 'win', 'gn.exe'
1030
1031 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081032 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521033
dprankecb4a2e242016-09-19 01:13:141034
dprankeeca4a782016-04-14 01:42:381035 def GNArgs(self, vals):
dpranke73ed0d62016-04-25 19:18:341036 if vals['cros_passthrough']:
1037 if not 'GN_ARGS' in os.environ:
1038 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1039 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161040 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301041 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341042 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351043 if vals['gn_args']:
1044 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341045 else:
1046 gn_args = vals['gn_args']
1047
dpranked0c138b2016-04-13 18:28:471048 if self.args.goma_dir:
1049 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381050
agrieve41d21a72016-04-14 18:02:261051 android_version_code = self.args.android_version_code
1052 if android_version_code:
1053 gn_args += ' android_default_version_code="%s"' % android_version_code
1054
1055 android_version_name = self.args.android_version_name
1056 if android_version_name:
1057 gn_args += ' android_default_version_name="%s"' % android_version_name
1058
dprankeeca4a782016-04-14 01:42:381059 # Canonicalize the arg string into a sorted, newline-separated list
1060 # of key-value pairs, and de-dup the keys if need be so that only
1061 # the last instance of each arg is listed.
1062 gn_args = gn_helpers.ToGNString(gn_helpers.FromGNArgs(gn_args))
1063
Ben Pastene65ccf6132018-11-08 00:47:591064 # If we're using the Simple Chrome SDK, add a comment at the top that
1065 # points to the doc. This must happen after the gn_helpers.ToGNString()
1066 # call above since gn_helpers strips comments.
1067 if vals['cros_passthrough']:
1068 simplechrome_comment = [
1069 '# These args are generated via the Simple Chrome SDK. See the link',
1070 '# below for more details:',
1071 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
1072 ]
1073 gn_args = '%s\n%s' % ('\n'.join(simplechrome_comment), gn_args)
1074
dpranke9dd5e252016-04-14 04:23:091075 args_file = vals.get('args_file', None)
1076 if args_file:
1077 gn_args = ('import("%s")\n' % vals['args_file']) + gn_args
dprankeeca4a782016-04-14 01:42:381078 return gn_args
dprankefe4602312015-04-08 16:20:351079
dprankecb4a2e242016-09-19 01:13:141080 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071081 isolate_map = self.ReadIsolateMap()
1082
Scott Graham3be4b4162017-09-12 00:41:411083 is_android = 'target_os="android"' in vals['gn_args']
Benjamin Pastene3bce864e2018-04-14 01:16:321084 is_simplechrome = vals.get('cros_passthrough', False)
Scott Graham3be4b4162017-09-12 00:41:411085 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:301086 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061087
kylechar39705682017-01-19 14:37:231088 # This should be true if tests with type='windowed_test_launcher' are
1089 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081090 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1091 # backends. Currently, tests are executed for the headless and X11 backends
1092 # and both can run under Xvfb.
1093 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1094 # backend.
Scott Graham3be4b4162017-09-12 00:41:411095 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251096
1097 asan = 'is_asan=true' in vals['gn_args']
1098 msan = 'is_msan=true' in vals['gn_args']
1099 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411100 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251101
dprankecb4a2e242016-09-19 01:13:141102 test_type = isolate_map[target]['type']
dprankefe0d35e2016-02-05 02:43:591103
dprankecb4a2e242016-09-19 01:13:141104 executable = isolate_map[target].get('executable', target)
Nico Weberd94b71a2018-02-22 22:00:301105 executable_suffix = '.exe' if is_win else ''
dprankefe0d35e2016-02-05 02:43:591106
dprankea55584f12015-07-22 00:52:471107 cmdline = []
Andrii Shyshkalovc158e0102018-01-10 05:52:001108 extra_files = [
1109 '../../.vpython',
1110 '../../testing/test_env.py',
1111 ]
dpranked8113582015-06-05 20:08:251112
dprankecb4a2e242016-09-19 01:13:141113 if test_type == 'nontest':
1114 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1115 output_path=None)
1116
Roberto Carrillo1460da852018-12-14 17:10:391117 if test_type == 'fuzzer':
1118 cmdline = [
1119 '../../testing/test_env.py',
1120 '../../tools/code_coverage/run_fuzz_target.py',
1121 '--fuzzer', './' + target,
1122 '--output-dir', '${ISOLATED_OUTDIR}',
1123 '--timeout', '3600']
1124 elif is_android and test_type != "script":
bpastenee428ea92017-02-17 02:20:321125 cmdline = [
John Budorickfb97a852017-12-20 20:10:191126 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041127 '../../build/android/test_wrapper/logdog_wrapper.py',
1128 '--target', target,
hzl9ae14452017-04-04 23:38:021129 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481130 '--store-tombstones']
Scott Graham3be4b4162017-09-12 00:41:411131 elif is_fuchsia and test_type != 'script':
John Budorickfb97a852017-12-20 20:10:191132 cmdline = [
1133 '../../testing/test_env.py',
1134 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441135 '--test-launcher-bot-mode',
John Budorickfb97a852017-12-20 20:10:191136 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321137 elif is_simplechrome and test_type != 'script':
1138 cmdline = [
1139 '../../testing/test_env.py',
1140 os.path.join('bin', 'run_%s' % target),
1141 ]
kylechar39705682017-01-19 14:37:231142 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001143 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471144 cmdline = [
dprankefe0d35e2016-02-05 02:43:591145 '../../testing/xvfb.py',
dprankefe0d35e2016-02-05 02:43:591146 './' + str(executable) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591147 '--test-launcher-bot-mode',
1148 '--asan=%d' % asan,
1149 '--msan=%d' % msan,
1150 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411151 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471152 ]
1153 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471154 cmdline = [
1155 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591156 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251157 '--test-launcher-bot-mode',
1158 '--asan=%d' % asan,
1159 '--msan=%d' % msan,
1160 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411161 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471162 ]
dpranke6abd8652015-08-28 03:21:111163 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241164 cmdline = []
1165 # If we're testing a CrOS simplechrome build, assume we need to launch a
1166 # VM first. So prepend the command to run with the VM launcher.
1167 # TODO(bpastene): Differentiate between CrOS VM and hardware tests.
1168 if is_simplechrome:
1169 cmdline = [os.path.join('bin', 'launch_cros_vm')]
1170 cmdline += [
dpranke6abd8652015-08-28 03:21:111171 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141172 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591173 ]
Dirk Prankef24e6b22018-03-27 20:12:301174 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471175 cmdline = [
1176 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591177 ]
dprankea55584f12015-07-22 00:52:471178 else:
1179 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1180 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251181
Abhishek Arya2f5f7342018-06-13 16:59:441182 if is_win and asan:
Alexander Dunaev384ba482018-03-21 17:56:121183 # Sandbox is not yet supported by ASAN for Windows.
1184 # Perhaps this is only needed for tests that use the sandbox?
1185 cmdline.append('--no-sandbox')
1186
dprankecb4a2e242016-09-19 01:13:141187 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591188
dpranked8113582015-06-05 20:08:251189 return cmdline, extra_files
1190
dpranke74559b52015-06-10 21:20:391191 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331192 return self.PathJoin(self.chromium_src_dir,
1193 self.ToSrcRelPath(build_path),
1194 *comps)
dpranked8113582015-06-05 20:08:251195
dprankeee5b51f62015-04-09 00:03:221196 def ToSrcRelPath(self, path):
1197 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501198 if path.startswith('//'):
1199 return path[2:].replace('/', self.sep)
1200 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351201
Dirk Pranke0fd41bcd2015-06-19 00:05:501202 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141203 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501204 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001205 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501206 if ret:
1207 return ret
1208
Dirk Prankef24e6b22018-03-27 20:12:301209 build_path = self.args.path
1210 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141211 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301212 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141213 gn_output_path = output_path + '.gn'
1214
dpranke7837fc362015-11-19 03:54:161215 inp = self.ReadInputJSON(['files', 'test_targets',
1216 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321217 if self.args.verbose:
1218 self.Print()
1219 self.Print('analyze input:')
1220 self.PrintJSON(inp)
1221 self.Print()
1222
dpranke76734662015-04-16 02:17:501223
dpranke7c5f614d2015-07-22 23:43:391224 # This shouldn't normally happen, but could due to unusual race conditions,
1225 # like a try job that gets scheduled before a patch lands but runs after
1226 # the patch has landed.
1227 if not inp['files']:
1228 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161229 self.WriteJSON({
1230 'status': 'No dependency',
1231 'compile_targets': [],
1232 'test_targets': [],
1233 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391234 return 0
1235
dprankecb4a2e242016-09-19 01:13:141236 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161237 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561238
dprankecb4a2e242016-09-19 01:13:141239 isolate_map = self.ReadIsolateMap()
1240 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1241 isolate_map, inp['additional_compile_targets'])
1242 if err:
1243 raise MBErr(err)
1244
1245 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1246 isolate_map, inp['test_targets'])
1247 if err:
1248 raise MBErr(err)
1249 labels_to_targets = {}
1250 for i, label in enumerate(gn_inp['test_targets']):
1251 labels_to_targets[label] = inp['test_targets'][i]
1252
dprankef61de2f2015-05-14 04:09:561253 try:
dprankecb4a2e242016-09-19 01:13:141254 self.WriteJSON(gn_inp, gn_input_path)
1255 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
1256 ret, _, _ = self.Run(cmd, force_verbose=True)
1257 if ret:
1258 return ret
dpranke067d0142015-05-14 22:52:451259
dprankecb4a2e242016-09-19 01:13:141260 gn_outp_str = self.ReadFile(gn_output_path)
1261 try:
1262 gn_outp = json.loads(gn_outp_str)
1263 except Exception as e:
1264 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1265 % (repr(gn_outp_str), str(e)))
1266 raise
1267
1268 outp = {}
1269 if 'status' in gn_outp:
1270 outp['status'] = gn_outp['status']
1271 if 'error' in gn_outp:
1272 outp['error'] = gn_outp['error']
1273 if 'invalid_targets' in gn_outp:
1274 outp['invalid_targets'] = gn_outp['invalid_targets']
1275 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491276 all_input_compile_targets = sorted(
1277 set(inp['test_targets'] + inp['additional_compile_targets']))
1278
1279 # If we're building 'all', we can throw away the rest of the targets
1280 # since they're redundant.
dpranke385a3102016-09-20 22:04:081281 if 'all' in gn_outp['compile_targets']:
1282 outp['compile_targets'] = ['all']
1283 else:
Dirk Pranke45165072017-11-08 04:57:491284 outp['compile_targets'] = gn_outp['compile_targets']
1285
1286 # crbug.com/736215: When GN returns targets back, for targets in
1287 # the default toolchain, GN will have generated a phony ninja
1288 # target matching the label, and so we can safely (and easily)
1289 # transform any GN label into the matching ninja target. For
1290 # targets in other toolchains, though, GN doesn't generate the
1291 # phony targets, and we don't know how to turn the labels into
1292 # compile targets. In this case, we also conservatively give up
1293 # and build everything. Probably the right thing to do here is
1294 # to have GN return the compile targets directly.
1295 if any("(" in target for target in outp['compile_targets']):
1296 self.Print('WARNING: targets with non-default toolchains were '
1297 'found, building everything instead.')
1298 outp['compile_targets'] = all_input_compile_targets
1299 else:
dpranke385a3102016-09-20 22:04:081300 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491301 label.replace('//', '') for label in outp['compile_targets']]
1302
1303 # Windows has a maximum command line length of 8k; even Linux
1304 # maxes out at 128k; if analyze returns a *really long* list of
1305 # targets, we just give up and conservatively build everything instead.
1306 # Probably the right thing here is for ninja to support response
1307 # files as input on the command line
1308 # (see https://github.com/ninja-build/ninja/issues/1355).
1309 if len(' '.join(outp['compile_targets'])) > 7*1024:
1310 self.Print('WARNING: Too many compile targets were affected.')
1311 self.Print('WARNING: Building everything instead to avoid '
1312 'command-line length issues.')
1313 outp['compile_targets'] = all_input_compile_targets
1314
1315
dprankecb4a2e242016-09-19 01:13:141316 if 'test_targets' in gn_outp:
1317 outp['test_targets'] = [
1318 labels_to_targets[label] for label in gn_outp['test_targets']]
1319
1320 if self.args.verbose:
1321 self.Print()
1322 self.Print('analyze output:')
1323 self.PrintJSON(outp)
1324 self.Print()
1325
1326 self.WriteJSON(outp, output_path)
1327
dprankef61de2f2015-05-14 04:09:561328 finally:
dprankecb4a2e242016-09-19 01:13:141329 if self.Exists(gn_input_path):
1330 self.RemoveFile(gn_input_path)
1331 if self.Exists(gn_output_path):
1332 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351333
1334 return 0
1335
dpranked8113582015-06-05 20:08:251336 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301337 path = self.args.input_path
1338 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351339 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321340 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351341
1342 try:
1343 inp = json.loads(self.ReadFile(path))
1344 except Exception as e:
1345 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321346 (path, e), output_path)
dpranked8113582015-06-05 20:08:251347
1348 for k in required_keys:
1349 if not k in inp:
1350 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1351 output_path)
dprankefe4602312015-04-08 16:20:351352
1353 return inp
1354
dpranked5b2b9432015-06-23 16:55:301355 def WriteFailureAndRaise(self, msg, output_path):
1356 if output_path:
dprankee0547cd2015-09-15 01:27:401357 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351358 raise MBErr(msg)
1359
dprankee0547cd2015-09-15 01:27:401360 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321361 try:
dprankee0547cd2015-09-15 01:27:401362 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1363 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321364 except Exception as e:
1365 raise MBErr('Error %s writing to the output path "%s"' %
1366 (e, path))
dprankefe4602312015-04-08 16:20:351367
aneeshmde50f472016-04-01 01:13:101368 def CheckCompile(self, master, builder):
1369 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1370 url = urllib2.quote(url_template.format(master=master, builder=builder),
1371 safe=':/()?=')
1372 try:
1373 builds = json.loads(self.Fetch(url))
1374 except Exception as e:
1375 return str(e)
1376 successes = sorted(
1377 [int(x) for x in builds.keys() if "text" in builds[x] and
1378 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1379 reverse=True)
1380 if not successes:
1381 return "no successful builds"
1382 build = builds[str(successes[0])]
1383 step_names = set([step["name"] for step in build["steps"]])
1384 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1385 if compile_indicators & step_names:
1386 return "compiles"
1387 return "does not compile"
1388
dpranke3cec199c2015-09-22 23:29:021389 def PrintCmd(self, cmd, env):
1390 if self.platform == 'win32':
1391 env_prefix = 'set '
1392 env_quoter = QuoteForSet
1393 shell_quoter = QuoteForCmd
1394 else:
1395 env_prefix = ''
1396 env_quoter = pipes.quote
1397 shell_quoter = pipes.quote
1398
1399 def print_env(var):
1400 if env and var in env:
1401 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1402
dprankeec079262016-06-07 02:21:201403 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021404
dpranke8c2cfd32015-09-17 20:12:331405 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351406 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021407 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351408
dprankecda00332015-04-11 04:18:321409 def PrintJSON(self, obj):
1410 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1411
dpranke751516a2015-10-03 01:11:341412 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301413 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381414 if self.platform == 'win32':
1415 # On Windows use the batch script since there is no exe
1416 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1417 else:
1418 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341419 if self.args.jobs:
1420 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1421 ninja_cmd.append(target)
1422 ret, _, _ = self.Run(ninja_cmd, force_verbose=False, buffer_output=False)
1423 return ret
1424
1425 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351426 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401427 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021428 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351429 if self.args.dryrun:
1430 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401431
dpranke751516a2015-10-03 01:11:341432 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401433 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341434 if ret:
1435 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351436 if out:
dprankeee5b51f62015-04-09 00:03:221437 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351438 if err:
dprankeee5b51f62015-04-09 00:03:221439 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351440 return ret, out, err
1441
dpranke751516a2015-10-03 01:11:341442 def Call(self, cmd, env=None, buffer_output=True):
1443 if buffer_output:
1444 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1445 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1446 env=env)
1447 out, err = p.communicate()
1448 else:
1449 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1450 env=env)
1451 p.wait()
1452 out = err = ''
dprankefe4602312015-04-08 16:20:351453 return p.returncode, out, err
1454
1455 def ExpandUser(self, path):
1456 # This function largely exists so it can be overridden for testing.
1457 return os.path.expanduser(path)
1458
1459 def Exists(self, path):
1460 # This function largely exists so it can be overridden for testing.
1461 return os.path.exists(path)
1462
Dirk Prankeb3b725c2019-02-16 02:18:411463 def Mtime(self, path):
1464 # This function largely exists so it can be overridden for testing.
1465 return os.stat(path).st_mtime
1466
dpranke867bcf4a2016-03-14 22:28:321467 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501468 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321469 f = urllib2.urlopen(url)
1470 contents = f.read()
1471 f.close()
1472 return contents
1473
dprankec3441d12015-06-23 23:01:351474 def MaybeMakeDirectory(self, path):
1475 try:
1476 os.makedirs(path)
1477 except OSError, e:
1478 if e.errno != errno.EEXIST:
1479 raise
1480
dpranke8c2cfd32015-09-17 20:12:331481 def PathJoin(self, *comps):
1482 # This function largely exists so it can be overriden for testing.
1483 return os.path.join(*comps)
1484
dpranke030d7a6d2016-03-26 17:23:501485 def Print(self, *args, **kwargs):
1486 # This function largely exists so it can be overridden for testing.
1487 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101488 if kwargs.get('stream', sys.stdout) == sys.stdout:
1489 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501490
dprankefe4602312015-04-08 16:20:351491 def ReadFile(self, path):
1492 # This function largely exists so it can be overriden for testing.
1493 with open(path) as fp:
1494 return fp.read()
1495
dpranke030d7a6d2016-03-26 17:23:501496 def RelPath(self, path, start='.'):
1497 # This function largely exists so it can be overriden for testing.
1498 return os.path.relpath(path, start)
1499
dprankef61de2f2015-05-14 04:09:561500 def RemoveFile(self, path):
1501 # This function largely exists so it can be overriden for testing.
1502 os.remove(path)
1503
dprankec161aa92015-09-14 20:21:131504 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331505 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131506 # In other places in chromium, we often have to retry this command
1507 # because we're worried about other processes still holding on to
1508 # file handles, but when MB is invoked, it will be early enough in the
1509 # build that their should be no other processes to interfere. We
1510 # can change this if need be.
1511 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1512 else:
1513 shutil.rmtree(abs_path, ignore_errors=True)
1514
Dirk Prankef24e6b22018-03-27 20:12:301515 def TempDir(self):
1516 # This function largely exists so it can be overriden for testing.
1517 return tempfile.mkdtemp(prefix='mb_')
1518
dprankef61de2f2015-05-14 04:09:561519 def TempFile(self, mode='w'):
1520 # This function largely exists so it can be overriden for testing.
1521 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1522
dprankee0547cd2015-09-15 01:27:401523 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351524 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401525 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301526 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351527 with open(path, 'w') as fp:
1528 return fp.write(contents)
1529
dprankef61de2f2015-05-14 04:09:561530
dprankefe4602312015-04-08 16:20:351531class MBErr(Exception):
1532 pass
1533
1534
dpranke3cec199c2015-09-22 23:29:021535# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1536# details of this next section, which handles escaping command lines
1537# so that they can be copied and pasted into a cmd window.
1538UNSAFE_FOR_SET = set('^<>&|')
1539UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1540ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1541
1542
1543def QuoteForSet(arg):
1544 if any(a in UNSAFE_FOR_SET for a in arg):
1545 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1546 return arg
1547
1548
1549def QuoteForCmd(arg):
1550 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021551 if arg == '' or ' ' in arg or '"' in arg:
1552 quote_re = re.compile(r'(\\*)"')
1553 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1554
1555 # Then check to see if the arg contains any metacharacters other than
1556 # double quotes; if it does, quote everything (including the double
1557 # quotes) for safety.
1558 if any(a in UNSAFE_FOR_CMD for a in arg):
1559 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1560 return arg
1561
1562
dprankefe4602312015-04-08 16:20:351563if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591564 sys.exit(main(sys.argv[1:]))