blob: 056e8fb77b1bd7b09a2e553f665d2197018a0ab1 [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()
Dirk Pranke7a7e9b62019-02-17 01:46:25786 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
787 isolate_map, build_dir)
788
Erik Chen42df41d2018-08-21 17:13:31789 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:00790 if err:
Dirk Pranke7a7e9b62019-02-17 01:46:25791 raise MBErr(err)
dpranke74559b52015-06-10 21:20:39792
dpranke751516a2015-10-03 01:11:34793 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:14794 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:39795 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
796
dprankefe4602312015-04-08 16:20:35797 ret, _, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:40798 if ret:
Dirk Pranke7a7e9b62019-02-17 01:46:25799 # If `gn gen` failed, we should exit early rather than trying to
800 # generate isolates. Run() will have already logged any error output.
801 self.Print('GN gen failed: %d' % ret)
802 return ret
dpranke74559b52015-06-10 21:20:39803
Erik Chen42df41d2018-08-21 17:13:31804 if getattr(self.args, 'swarming_targets_file', None):
Dirk Pranke7a7e9b62019-02-17 01:46:25805 self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
Erik Chen42df41d2018-08-21 17:13:31806
807 return 0
808
809 def RunGNGenAllIsolates(self, vals):
810 """
811 This command generates all .isolate files.
812
813 This command assumes that "mb.py gen" has already been run, as it relies on
814 "gn ls" to fetch all gn targets. If uses that output, combined with the
815 isolate_map, to determine all isolates that can be generated for the current
816 gn configuration.
817 """
818 build_dir = self.args.path
819 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
820 force_verbose=False)
821 if ret:
822 # If `gn ls` failed, we should exit early rather than trying to
823 # generate isolates.
824 self.Print('GN ls failed: %d' % ret)
825 return ret
826
827 # Create a reverse map from isolate label to isolate dict.
828 isolate_map = self.ReadIsolateMap()
829 isolate_dict_map = {}
830 for key, isolate_dict in isolate_map.iteritems():
831 isolate_dict_map[isolate_dict['label']] = isolate_dict
832 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
833
834 runtime_deps = []
835
836 isolate_targets = []
837 # For every GN target, look up the isolate dict.
838 for line in output.splitlines():
839 target = line.strip()
840 if target in isolate_dict_map:
841 if isolate_dict_map[target]['type'] == 'additional_compile_target':
842 # By definition, additional_compile_targets are not tests, so we
843 # shouldn't generate isolates for them.
844 continue
845
846 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
847 runtime_deps.append(target)
848
Dirk Pranke7a7e9b62019-02-17 01:46:25849 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
850 isolate_map, build_dir)
851
Erik Chen42df41d2018-08-21 17:13:31852 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
853 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
854 cmd = self.GNCmd('gen', build_dir)
855 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
856 self.Run(cmd)
857
858 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
859
Dirk Pranke7a7e9b62019-02-17 01:46:25860 def RemovePossiblyStaleRuntimeDepsFiles(self, vals, targets, isolate_map,
861 build_dir):
862 # TODO(crbug.com/932700): Because `gn gen --runtime-deps-list-file`
863 # puts the runtime_deps file in different locations based on the actual
864 # type of a target, we may end up with multiple possible runtime_deps
865 # files in a given build directory, where some of the entries might be
866 # stale (since we might be reusing an existing build directory).
867 #
868 # We need to be able to get the right one reliably; you might think
869 # we can just pick the newest file, but because GN won't update timestamps
870 # if the contents of the files change, an older runtime_deps
871 # file might actually be the one we should use over a newer one (see
872 # crbug.com/932387 for a more complete explanation and example).
873 #
874 # In order to avoid this, we need to delete any possible runtime_deps
875 # files *prior* to running GN. As long as the files aren't actually
876 # needed during the build, this hopefully will not cause unnecessary
877 # build work, and so it should be safe.
878 #
879 # Ultimately, we should just make sure we get the runtime_deps files
880 # in predictable locations so we don't have this issue at all, and
881 # that's what crbug.com/932700 is for.
882 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, targets, isolate_map)
883 for rpaths in possible_rpaths.values():
884 for rpath in rpaths:
885 path = self.ToAbsPath(build_dir, rpath)
886 if self.Exists(path):
887 self.RemoveFile(path)
888
Erik Chen42df41d2018-08-21 17:13:31889 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
890 """
891 Generates isolates for a list of ninja targets.
892
893 Ninja targets are transformed to GN targets via isolate_map.
894
895 This function assumes that a previous invocation of "mb.py gen" has
896 generated runtime deps for all targets.
897 """
Dirk Pranke7a7e9b62019-02-17 01:46:25898 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, ninja_targets,
899 isolate_map)
900
901 for target, rpaths in possible_rpaths.items():
902 # TODO(crbug.com/932700): We don't know where each .runtime_deps
903 # file might be, but assuming we called
904 # RemovePossiblyStaleRuntimeDepsFiles prior to calling `gn gen`,
905 # there should only be one file.
906 found_one = False
907 path_to_use = None
908 for r in rpaths:
909 path = self.ToAbsPath(build_dir, r)
910 if self.Exists(path):
911 if found_one:
912 raise MBErr('Found more than one of %s' % ', '.join(rpaths))
913 path_to_use = path
914 found_one = True
915
916 if not found_one:
917 raise MBErr('Did not find any of %s' % ', '.join(rpaths))
918
919 command, extra_files = self.GetIsolateCommand(target, vals)
920 runtime_deps = self.ReadFile(path_to_use).splitlines()
921
922 canonical_target = target.replace(':','_').replace('/','_')
923 self.WriteIsolateFiles(build_dir, command, canonical_target, runtime_deps,
924 extra_files)
925
926 def PossibleRuntimeDepsPaths(self, vals, ninja_targets, isolate_map):
927 """Returns a map of targets to possible .runtime_deps paths.
928
929 Each ninja target maps on to a GN label, but depending on the type
930 of the GN target, `gn gen --runtime-deps-list-file` will write
931 the .runtime_deps files into different locations. Unfortunately, in
932 some cases we don't actually know which of multiple locations will
933 actually be used, so we return all plausible candidates.
934
935 The paths that are returned are relative to the build directory.
936 """
937
jbudoricke3c4f95e2016-04-28 23:17:38938 android = 'target_os="android"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:42939 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:30940 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Dirk Pranke7a7e9b62019-02-17 01:46:25941 possible_runtime_deps_rpaths = {}
Erik Chen42df41d2018-08-21 17:13:31942 for target in ninja_targets:
943 # TODO(https://crbug.com/876065): 'official_tests' use
944 # type='additional_compile_target' to isolate tests. This is not the
945 # intended use for 'additional_compile_target'.
946 if (isolate_map[target]['type'] == 'additional_compile_target' and
947 target != 'official_tests'):
948 # By definition, additional_compile_targets are not tests, so we
949 # shouldn't generate isolates for them.
Dirk Pranke7a7e9b62019-02-17 01:46:25950 raise MBErr('Cannot generate isolate for %s since it is an '
951 'additional_compile_target.' % target)
Erik Chen42df41d2018-08-21 17:13:31952 elif android:
jbudoricke3c4f95e2016-04-28 23:17:38953 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:02954 # will result in runtime_deps associated with the stamp file, while the
955 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:44956 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:25957 rpaths = [
dprankecb4a2e242016-09-19 01:13:14958 target + '.runtime_deps',
dpranke48ccf8f2016-03-28 23:58:28959 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
Kevin Marshallf35fa5f2018-01-29 19:24:42960 elif fuchsia:
961 # Only emit a runtime deps file for the group() target on Fuchsia.
Abhishek Arya2f5f7342018-06-13 16:59:44962 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:25963 rpaths = ['obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
dprankecb4a2e242016-09-19 01:13:14964 elif (isolate_map[target]['type'] == 'script' or
Roberto Carrillo1460da852018-12-14 17:10:39965 isolate_map[target]['type'] == 'fuzzer' or
dprankecb4a2e242016-09-19 01:13:14966 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:11967 # For script targets, the build target is usually a group,
968 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:49969 # for the label, which lives under the obj/ directory, but it may
970 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:44971 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:25972 rpaths = ['obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
Nico Weberd94b71a2018-02-22 22:00:30973 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:25974 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:49975 else:
Dirk Pranke7a7e9b62019-02-17 01:46:25976 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:30977 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:25978 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:52979 else:
Dirk Pranke7a7e9b62019-02-17 01:46:25980 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:02981
Dirk Pranke7a7e9b62019-02-17 01:46:25982 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:41983
Dirk Pranke7a7e9b62019-02-17 01:46:25984 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:34985
986 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:30987 target = self.args.target
dprankecb4a2e242016-09-19 01:13:14988 isolate_map = self.ReadIsolateMap()
989 err, labels = self.MapTargetsToLabels(isolate_map, [target])
990 if err:
991 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:25992
dprankecb4a2e242016-09-19 01:13:14993 label = labels[0]
dpranke751516a2015-10-03 01:11:34994
Dirk Prankef24e6b22018-03-27 20:12:30995 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:14996 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:34997
dprankeeca4a782016-04-14 01:42:38998 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:20999 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:341000 if ret:
dpranke030d7a6d2016-03-26 17:23:501001 if out:
1002 self.Print(out)
dpranke751516a2015-10-03 01:11:341003 return ret
1004
1005 runtime_deps = out.splitlines()
1006
1007 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
1008 extra_files)
1009
1010 ret, _, _ = self.Run([
1011 self.executable,
1012 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1013 'check',
1014 '-i',
1015 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1016 '-s',
1017 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1018 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301019
dprankefe4602312015-04-08 16:20:351020 return ret
1021
dpranke751516a2015-10-03 01:11:341022 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps,
1023 extra_files):
1024 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
1025 self.WriteFile(isolate_path,
1026 pprint.pformat({
1027 'variables': {
1028 'command': command,
1029 'files': sorted(runtime_deps + extra_files),
1030 }
1031 }) + '\n')
1032
1033 self.WriteJSON(
1034 {
1035 'args': [
1036 '--isolated',
1037 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1038 '--isolate',
1039 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1040 ],
1041 'dir': self.chromium_src_dir,
1042 'version': 1,
1043 },
1044 isolate_path + 'd.gen.json',
1045 )
1046
dprankecb4a2e242016-09-19 01:13:141047 def MapTargetsToLabels(self, isolate_map, targets):
1048 labels = []
1049 err = ''
1050
dprankecb4a2e242016-09-19 01:13:141051 for target in targets:
1052 if target == 'all':
1053 labels.append(target)
1054 elif target.startswith('//'):
1055 labels.append(target)
1056 else:
1057 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421058 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141059 err += ('test target "%s" type is unknown\n' % target)
1060 else:
thakis024d6f32017-05-16 23:21:421061 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141062 else:
1063 err += ('target "%s" not found in '
1064 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1065
1066 return err, labels
1067
dprankeeca4a782016-04-14 01:42:381068 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461069 if self.platform == 'linux2':
1070 subdir, exe = 'linux64', 'gn'
1071 elif self.platform == 'darwin':
1072 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251073 elif self.platform == 'aix6':
1074 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461075 else:
1076 subdir, exe = 'win', 'gn.exe'
1077
1078 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081079 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521080
dprankecb4a2e242016-09-19 01:13:141081
dprankeeca4a782016-04-14 01:42:381082 def GNArgs(self, vals):
dpranke73ed0d62016-04-25 19:18:341083 if vals['cros_passthrough']:
1084 if not 'GN_ARGS' in os.environ:
1085 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1086 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161087 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301088 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341089 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351090 if vals['gn_args']:
1091 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341092 else:
1093 gn_args = vals['gn_args']
1094
dpranked0c138b2016-04-13 18:28:471095 if self.args.goma_dir:
1096 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381097
agrieve41d21a72016-04-14 18:02:261098 android_version_code = self.args.android_version_code
1099 if android_version_code:
1100 gn_args += ' android_default_version_code="%s"' % android_version_code
1101
1102 android_version_name = self.args.android_version_name
1103 if android_version_name:
1104 gn_args += ' android_default_version_name="%s"' % android_version_name
1105
dprankeeca4a782016-04-14 01:42:381106 # Canonicalize the arg string into a sorted, newline-separated list
1107 # of key-value pairs, and de-dup the keys if need be so that only
1108 # the last instance of each arg is listed.
1109 gn_args = gn_helpers.ToGNString(gn_helpers.FromGNArgs(gn_args))
1110
Ben Pastene65ccf6132018-11-08 00:47:591111 # If we're using the Simple Chrome SDK, add a comment at the top that
1112 # points to the doc. This must happen after the gn_helpers.ToGNString()
1113 # call above since gn_helpers strips comments.
1114 if vals['cros_passthrough']:
1115 simplechrome_comment = [
1116 '# These args are generated via the Simple Chrome SDK. See the link',
1117 '# below for more details:',
1118 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
1119 ]
1120 gn_args = '%s\n%s' % ('\n'.join(simplechrome_comment), gn_args)
1121
dpranke9dd5e252016-04-14 04:23:091122 args_file = vals.get('args_file', None)
1123 if args_file:
1124 gn_args = ('import("%s")\n' % vals['args_file']) + gn_args
dprankeeca4a782016-04-14 01:42:381125 return gn_args
dprankefe4602312015-04-08 16:20:351126
dprankecb4a2e242016-09-19 01:13:141127 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071128 isolate_map = self.ReadIsolateMap()
1129
Scott Graham3be4b4162017-09-12 00:41:411130 is_android = 'target_os="android"' in vals['gn_args']
Benjamin Pastene3bce864e2018-04-14 01:16:321131 is_simplechrome = vals.get('cros_passthrough', False)
Scott Graham3be4b4162017-09-12 00:41:411132 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:301133 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061134
kylechar39705682017-01-19 14:37:231135 # This should be true if tests with type='windowed_test_launcher' are
1136 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081137 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1138 # backends. Currently, tests are executed for the headless and X11 backends
1139 # and both can run under Xvfb.
1140 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1141 # backend.
Scott Graham3be4b4162017-09-12 00:41:411142 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251143
1144 asan = 'is_asan=true' in vals['gn_args']
1145 msan = 'is_msan=true' in vals['gn_args']
1146 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411147 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251148
dprankecb4a2e242016-09-19 01:13:141149 test_type = isolate_map[target]['type']
dprankefe0d35e2016-02-05 02:43:591150
dprankecb4a2e242016-09-19 01:13:141151 executable = isolate_map[target].get('executable', target)
Nico Weberd94b71a2018-02-22 22:00:301152 executable_suffix = '.exe' if is_win else ''
dprankefe0d35e2016-02-05 02:43:591153
dprankea55584f12015-07-22 00:52:471154 cmdline = []
Andrii Shyshkalovc158e0102018-01-10 05:52:001155 extra_files = [
1156 '../../.vpython',
1157 '../../testing/test_env.py',
1158 ]
dpranked8113582015-06-05 20:08:251159
dprankecb4a2e242016-09-19 01:13:141160 if test_type == 'nontest':
1161 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1162 output_path=None)
1163
Roberto Carrillo1460da852018-12-14 17:10:391164 if test_type == 'fuzzer':
1165 cmdline = [
1166 '../../testing/test_env.py',
1167 '../../tools/code_coverage/run_fuzz_target.py',
1168 '--fuzzer', './' + target,
1169 '--output-dir', '${ISOLATED_OUTDIR}',
1170 '--timeout', '3600']
1171 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011172 cmdline = []
1173 if asan:
1174 cmdline += [os.path.join('bin', 'run_with_asan')]
1175 cmdline += [
John Budorickfb97a852017-12-20 20:10:191176 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041177 '../../build/android/test_wrapper/logdog_wrapper.py',
1178 '--target', target,
hzl9ae14452017-04-04 23:38:021179 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481180 '--store-tombstones']
Scott Graham3be4b4162017-09-12 00:41:411181 elif is_fuchsia and test_type != 'script':
John Budorickfb97a852017-12-20 20:10:191182 cmdline = [
1183 '../../testing/test_env.py',
1184 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441185 '--test-launcher-bot-mode',
John Budorickfb97a852017-12-20 20:10:191186 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321187 elif is_simplechrome and test_type != 'script':
1188 cmdline = [
1189 '../../testing/test_env.py',
1190 os.path.join('bin', 'run_%s' % target),
1191 ]
kylechar39705682017-01-19 14:37:231192 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001193 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471194 cmdline = [
dprankefe0d35e2016-02-05 02:43:591195 '../../testing/xvfb.py',
dprankefe0d35e2016-02-05 02:43:591196 './' + str(executable) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591197 '--test-launcher-bot-mode',
1198 '--asan=%d' % asan,
1199 '--msan=%d' % msan,
1200 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411201 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471202 ]
1203 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471204 cmdline = [
1205 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591206 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251207 '--test-launcher-bot-mode',
1208 '--asan=%d' % asan,
1209 '--msan=%d' % msan,
1210 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411211 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471212 ]
dpranke6abd8652015-08-28 03:21:111213 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241214 cmdline = []
1215 # If we're testing a CrOS simplechrome build, assume we need to launch a
1216 # VM first. So prepend the command to run with the VM launcher.
1217 # TODO(bpastene): Differentiate between CrOS VM and hardware tests.
1218 if is_simplechrome:
1219 cmdline = [os.path.join('bin', 'launch_cros_vm')]
1220 cmdline += [
dpranke6abd8652015-08-28 03:21:111221 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141222 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591223 ]
Dirk Prankef24e6b22018-03-27 20:12:301224 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471225 cmdline = [
1226 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591227 ]
dprankea55584f12015-07-22 00:52:471228 else:
1229 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1230 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251231
Abhishek Arya2f5f7342018-06-13 16:59:441232 if is_win and asan:
Alexander Dunaev384ba482018-03-21 17:56:121233 # Sandbox is not yet supported by ASAN for Windows.
1234 # Perhaps this is only needed for tests that use the sandbox?
1235 cmdline.append('--no-sandbox')
1236
dprankecb4a2e242016-09-19 01:13:141237 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591238
dpranked8113582015-06-05 20:08:251239 return cmdline, extra_files
1240
dpranke74559b52015-06-10 21:20:391241 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331242 return self.PathJoin(self.chromium_src_dir,
1243 self.ToSrcRelPath(build_path),
1244 *comps)
dpranked8113582015-06-05 20:08:251245
dprankeee5b51f62015-04-09 00:03:221246 def ToSrcRelPath(self, path):
1247 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501248 if path.startswith('//'):
1249 return path[2:].replace('/', self.sep)
1250 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351251
Dirk Pranke0fd41bcd2015-06-19 00:05:501252 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141253 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501254 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001255 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501256 if ret:
1257 return ret
1258
Dirk Prankef24e6b22018-03-27 20:12:301259 build_path = self.args.path
1260 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141261 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301262 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141263 gn_output_path = output_path + '.gn'
1264
dpranke7837fc362015-11-19 03:54:161265 inp = self.ReadInputJSON(['files', 'test_targets',
1266 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321267 if self.args.verbose:
1268 self.Print()
1269 self.Print('analyze input:')
1270 self.PrintJSON(inp)
1271 self.Print()
1272
dpranke76734662015-04-16 02:17:501273
dpranke7c5f614d2015-07-22 23:43:391274 # This shouldn't normally happen, but could due to unusual race conditions,
1275 # like a try job that gets scheduled before a patch lands but runs after
1276 # the patch has landed.
1277 if not inp['files']:
1278 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161279 self.WriteJSON({
1280 'status': 'No dependency',
1281 'compile_targets': [],
1282 'test_targets': [],
1283 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391284 return 0
1285
dprankecb4a2e242016-09-19 01:13:141286 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161287 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561288
dprankecb4a2e242016-09-19 01:13:141289 isolate_map = self.ReadIsolateMap()
1290 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1291 isolate_map, inp['additional_compile_targets'])
1292 if err:
1293 raise MBErr(err)
1294
1295 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1296 isolate_map, inp['test_targets'])
1297 if err:
1298 raise MBErr(err)
1299 labels_to_targets = {}
1300 for i, label in enumerate(gn_inp['test_targets']):
1301 labels_to_targets[label] = inp['test_targets'][i]
1302
dprankef61de2f2015-05-14 04:09:561303 try:
dprankecb4a2e242016-09-19 01:13:141304 self.WriteJSON(gn_inp, gn_input_path)
1305 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
1306 ret, _, _ = self.Run(cmd, force_verbose=True)
1307 if ret:
1308 return ret
dpranke067d0142015-05-14 22:52:451309
dprankecb4a2e242016-09-19 01:13:141310 gn_outp_str = self.ReadFile(gn_output_path)
1311 try:
1312 gn_outp = json.loads(gn_outp_str)
1313 except Exception as e:
1314 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1315 % (repr(gn_outp_str), str(e)))
1316 raise
1317
1318 outp = {}
1319 if 'status' in gn_outp:
1320 outp['status'] = gn_outp['status']
1321 if 'error' in gn_outp:
1322 outp['error'] = gn_outp['error']
1323 if 'invalid_targets' in gn_outp:
1324 outp['invalid_targets'] = gn_outp['invalid_targets']
1325 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491326 all_input_compile_targets = sorted(
1327 set(inp['test_targets'] + inp['additional_compile_targets']))
1328
1329 # If we're building 'all', we can throw away the rest of the targets
1330 # since they're redundant.
dpranke385a3102016-09-20 22:04:081331 if 'all' in gn_outp['compile_targets']:
1332 outp['compile_targets'] = ['all']
1333 else:
Dirk Pranke45165072017-11-08 04:57:491334 outp['compile_targets'] = gn_outp['compile_targets']
1335
1336 # crbug.com/736215: When GN returns targets back, for targets in
1337 # the default toolchain, GN will have generated a phony ninja
1338 # target matching the label, and so we can safely (and easily)
1339 # transform any GN label into the matching ninja target. For
1340 # targets in other toolchains, though, GN doesn't generate the
1341 # phony targets, and we don't know how to turn the labels into
1342 # compile targets. In this case, we also conservatively give up
1343 # and build everything. Probably the right thing to do here is
1344 # to have GN return the compile targets directly.
1345 if any("(" in target for target in outp['compile_targets']):
1346 self.Print('WARNING: targets with non-default toolchains were '
1347 'found, building everything instead.')
1348 outp['compile_targets'] = all_input_compile_targets
1349 else:
dpranke385a3102016-09-20 22:04:081350 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491351 label.replace('//', '') for label in outp['compile_targets']]
1352
1353 # Windows has a maximum command line length of 8k; even Linux
1354 # maxes out at 128k; if analyze returns a *really long* list of
1355 # targets, we just give up and conservatively build everything instead.
1356 # Probably the right thing here is for ninja to support response
1357 # files as input on the command line
1358 # (see https://github.com/ninja-build/ninja/issues/1355).
1359 if len(' '.join(outp['compile_targets'])) > 7*1024:
1360 self.Print('WARNING: Too many compile targets were affected.')
1361 self.Print('WARNING: Building everything instead to avoid '
1362 'command-line length issues.')
1363 outp['compile_targets'] = all_input_compile_targets
1364
1365
dprankecb4a2e242016-09-19 01:13:141366 if 'test_targets' in gn_outp:
1367 outp['test_targets'] = [
1368 labels_to_targets[label] for label in gn_outp['test_targets']]
1369
1370 if self.args.verbose:
1371 self.Print()
1372 self.Print('analyze output:')
1373 self.PrintJSON(outp)
1374 self.Print()
1375
1376 self.WriteJSON(outp, output_path)
1377
dprankef61de2f2015-05-14 04:09:561378 finally:
dprankecb4a2e242016-09-19 01:13:141379 if self.Exists(gn_input_path):
1380 self.RemoveFile(gn_input_path)
1381 if self.Exists(gn_output_path):
1382 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351383
1384 return 0
1385
dpranked8113582015-06-05 20:08:251386 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301387 path = self.args.input_path
1388 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351389 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321390 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351391
1392 try:
1393 inp = json.loads(self.ReadFile(path))
1394 except Exception as e:
1395 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321396 (path, e), output_path)
dpranked8113582015-06-05 20:08:251397
1398 for k in required_keys:
1399 if not k in inp:
1400 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1401 output_path)
dprankefe4602312015-04-08 16:20:351402
1403 return inp
1404
dpranked5b2b9432015-06-23 16:55:301405 def WriteFailureAndRaise(self, msg, output_path):
1406 if output_path:
dprankee0547cd2015-09-15 01:27:401407 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351408 raise MBErr(msg)
1409
dprankee0547cd2015-09-15 01:27:401410 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321411 try:
dprankee0547cd2015-09-15 01:27:401412 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1413 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321414 except Exception as e:
1415 raise MBErr('Error %s writing to the output path "%s"' %
1416 (e, path))
dprankefe4602312015-04-08 16:20:351417
aneeshmde50f472016-04-01 01:13:101418 def CheckCompile(self, master, builder):
1419 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1420 url = urllib2.quote(url_template.format(master=master, builder=builder),
1421 safe=':/()?=')
1422 try:
1423 builds = json.loads(self.Fetch(url))
1424 except Exception as e:
1425 return str(e)
1426 successes = sorted(
1427 [int(x) for x in builds.keys() if "text" in builds[x] and
1428 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1429 reverse=True)
1430 if not successes:
1431 return "no successful builds"
1432 build = builds[str(successes[0])]
1433 step_names = set([step["name"] for step in build["steps"]])
1434 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1435 if compile_indicators & step_names:
1436 return "compiles"
1437 return "does not compile"
1438
dpranke3cec199c2015-09-22 23:29:021439 def PrintCmd(self, cmd, env):
1440 if self.platform == 'win32':
1441 env_prefix = 'set '
1442 env_quoter = QuoteForSet
1443 shell_quoter = QuoteForCmd
1444 else:
1445 env_prefix = ''
1446 env_quoter = pipes.quote
1447 shell_quoter = pipes.quote
1448
1449 def print_env(var):
1450 if env and var in env:
1451 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1452
dprankeec079262016-06-07 02:21:201453 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021454
dpranke8c2cfd32015-09-17 20:12:331455 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351456 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021457 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351458
dprankecda00332015-04-11 04:18:321459 def PrintJSON(self, obj):
1460 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1461
dpranke751516a2015-10-03 01:11:341462 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301463 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381464 if self.platform == 'win32':
1465 # On Windows use the batch script since there is no exe
1466 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1467 else:
1468 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341469 if self.args.jobs:
1470 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1471 ninja_cmd.append(target)
1472 ret, _, _ = self.Run(ninja_cmd, force_verbose=False, buffer_output=False)
1473 return ret
1474
1475 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351476 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401477 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021478 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351479 if self.args.dryrun:
1480 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401481
dpranke751516a2015-10-03 01:11:341482 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401483 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341484 if ret:
1485 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351486 if out:
dprankeee5b51f62015-04-09 00:03:221487 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351488 if err:
dprankeee5b51f62015-04-09 00:03:221489 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351490 return ret, out, err
1491
dpranke751516a2015-10-03 01:11:341492 def Call(self, cmd, env=None, buffer_output=True):
1493 if buffer_output:
1494 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1495 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1496 env=env)
1497 out, err = p.communicate()
1498 else:
1499 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1500 env=env)
1501 p.wait()
1502 out = err = ''
dprankefe4602312015-04-08 16:20:351503 return p.returncode, out, err
1504
1505 def ExpandUser(self, path):
1506 # This function largely exists so it can be overridden for testing.
1507 return os.path.expanduser(path)
1508
1509 def Exists(self, path):
1510 # This function largely exists so it can be overridden for testing.
1511 return os.path.exists(path)
1512
dpranke867bcf4a2016-03-14 22:28:321513 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501514 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321515 f = urllib2.urlopen(url)
1516 contents = f.read()
1517 f.close()
1518 return contents
1519
dprankec3441d12015-06-23 23:01:351520 def MaybeMakeDirectory(self, path):
1521 try:
1522 os.makedirs(path)
1523 except OSError, e:
1524 if e.errno != errno.EEXIST:
1525 raise
1526
dpranke8c2cfd32015-09-17 20:12:331527 def PathJoin(self, *comps):
1528 # This function largely exists so it can be overriden for testing.
1529 return os.path.join(*comps)
1530
dpranke030d7a6d2016-03-26 17:23:501531 def Print(self, *args, **kwargs):
1532 # This function largely exists so it can be overridden for testing.
1533 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101534 if kwargs.get('stream', sys.stdout) == sys.stdout:
1535 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501536
dprankefe4602312015-04-08 16:20:351537 def ReadFile(self, path):
1538 # This function largely exists so it can be overriden for testing.
1539 with open(path) as fp:
1540 return fp.read()
1541
dpranke030d7a6d2016-03-26 17:23:501542 def RelPath(self, path, start='.'):
1543 # This function largely exists so it can be overriden for testing.
1544 return os.path.relpath(path, start)
1545
dprankef61de2f2015-05-14 04:09:561546 def RemoveFile(self, path):
1547 # This function largely exists so it can be overriden for testing.
1548 os.remove(path)
1549
dprankec161aa92015-09-14 20:21:131550 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331551 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131552 # In other places in chromium, we often have to retry this command
1553 # because we're worried about other processes still holding on to
1554 # file handles, but when MB is invoked, it will be early enough in the
1555 # build that their should be no other processes to interfere. We
1556 # can change this if need be.
1557 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1558 else:
1559 shutil.rmtree(abs_path, ignore_errors=True)
1560
Dirk Prankef24e6b22018-03-27 20:12:301561 def TempDir(self):
1562 # This function largely exists so it can be overriden for testing.
1563 return tempfile.mkdtemp(prefix='mb_')
1564
dprankef61de2f2015-05-14 04:09:561565 def TempFile(self, mode='w'):
1566 # This function largely exists so it can be overriden for testing.
1567 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1568
dprankee0547cd2015-09-15 01:27:401569 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351570 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401571 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301572 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351573 with open(path, 'w') as fp:
1574 return fp.write(contents)
1575
dprankef61de2f2015-05-14 04:09:561576
dprankefe4602312015-04-08 16:20:351577class MBErr(Exception):
1578 pass
1579
1580
dpranke3cec199c2015-09-22 23:29:021581# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1582# details of this next section, which handles escaping command lines
1583# so that they can be copied and pasted into a cmd window.
1584UNSAFE_FOR_SET = set('^<>&|')
1585UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1586ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1587
1588
1589def QuoteForSet(arg):
1590 if any(a in UNSAFE_FOR_SET for a in arg):
1591 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1592 return arg
1593
1594
1595def QuoteForCmd(arg):
1596 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021597 if arg == '' or ' ' in arg or '"' in arg:
1598 quote_re = re.compile(r'(\\*)"')
1599 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1600
1601 # Then check to see if the arg contains any metacharacters other than
1602 # double quotes; if it does, quote everything (including the double
1603 # quotes) for safety.
1604 if any(a in UNSAFE_FOR_CMD for a in arg):
1605 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1606 return arg
1607
1608
dprankefe4602312015-04-08 16:20:351609if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591610 sys.exit(main(sys.argv[1:]))