blob: a949d7770924ebad40c3f1067d0698c82ad895f8 [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
John Budorick9d9175372019-04-01 19:04:24394 # `chromium_swarming` recipe module in build.git. All references to
395 # `swarming_module` below are purely due to this.
Robert Iannucci5a9d75f62018-03-02 05:28:20396 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}',
John Budorick9d9175372019-04-01 19:04:24402 'git_revision:2973c0809cdc7122b7123e42b163a54d4983503f'),
Robert Iannucci5a9d75f62018-03-02 05:28:20403 ('infra/tools/luci/vpython/${platform}',
John Budorick9d9175372019-04-01 19:04:24404 'git_revision:2973c0809cdc7122b7123e42b163a54d4983503f'),
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):
Marc-Antoine Ruel559cc4732019-03-19 22:20:46423 isolate_server = 'isolateserver.appspot.com'
424 namespace = 'default-gzip'
425 swarming_server = 'chromium-swarm.appspot.com'
Dirk Pranke8cb6aa782017-12-16 02:31:33426 # TODO(dpranke): Look up the information for the target in
427 # the //testing/buildbot.json file, if possible, so that we
428 # can determine the isolate target, command line, and additional
429 # swarming parameters, if possible.
430 #
431 # TODO(dpranke): Also, add support for sharding and merging results.
432 dimensions = []
433 for k, v in self._DefaultDimensions() + self.args.dimensions:
434 dimensions += ['-d', k, v]
435
436 cmd = [
437 self.executable,
438 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
439 'archive',
Marc-Antoine Ruel559cc4732019-03-19 22:20:46440 '-s', self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
441 '-I', isolate_server,
442 '--namespace', namespace,
Dirk Pranke8cb6aa782017-12-16 02:31:33443 ]
444 ret, out, _ = self.Run(cmd, force_verbose=False)
445 if ret:
446 return ret
447
448 isolated_hash = out.splitlines()[0].split()[0]
449 cmd = [
450 self.executable,
451 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
452 'run',
453 '-s', isolated_hash,
Marc-Antoine Ruel559cc4732019-03-19 22:20:46454 '-I', isolate_server,
455 '--namespace', namespace,
456 '-S', swarming_server,
Dirk Pranke8cb6aa782017-12-16 02:31:33457 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20458 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33459 if self.args.extra_args:
460 cmd += ['--'] + self.args.extra_args
461 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
462 return ret
463
464 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50465 cmd = [
dpranke751516a2015-10-03 01:11:34466 self.executable,
467 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
468 'run',
469 '-s',
dpranke030d7a6d2016-03-26 17:23:50470 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33471 ]
dpranke030d7a6d2016-03-26 17:23:50472 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33473 cmd += ['--'] + self.args.extra_args
474 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34475 return ret
476
Dirk Pranke8cb6aa782017-12-16 02:31:33477 def _DefaultDimensions(self):
478 if not self.args.default_dimensions:
479 return []
480
481 # This code is naive and just picks reasonable defaults per platform.
482 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40483 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33484 elif self.platform.startswith('linux'):
485 os_dim = ('os', 'Ubuntu-14.04')
486 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40487 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33488 else:
489 raise MBErr('unrecognized platform string "%s"' % self.platform)
490
491 return [('pool', 'Chrome'),
492 ('cpu', 'x86-64'),
493 os_dim]
494
dpranke0cafc162016-03-19 00:41:10495 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35496 errs = []
497
498 # Read the file to make sure it parses.
499 self.ReadConfigFile()
500
dpranke3be00142016-03-17 22:46:04501 # Build a list of all of the configs referenced by builders.
dprankefe4602312015-04-08 16:20:35502 all_configs = {}
dprankefe4602312015-04-08 16:20:35503 for master in self.masters:
dpranke3be00142016-03-17 22:46:04504 for config in self.masters[master].values():
shenghuazhang804b21542016-10-11 02:06:49505 if isinstance(config, dict):
506 for c in config.values():
dprankeb9380a12016-07-21 21:44:09507 all_configs[c] = master
508 else:
509 all_configs[config] = master
dprankefe4602312015-04-08 16:20:35510
dpranke9dd5e252016-04-14 04:23:09511 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35512 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09513 if config.startswith('//'):
514 if not self.Exists(self.ToAbsPath(config)):
515 errs.append('Unknown args file "%s" referenced from "%s".' %
516 (config, loc))
517 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35518 errs.append('Unknown config "%s" referenced from "%s".' %
519 (config, loc))
520
521 # Check that every actual config is actually referenced.
522 for config in self.configs:
523 if not config in all_configs:
524 errs.append('Unused config "%s".' % config)
525
526 # Figure out the whole list of mixins, and check that every mixin
527 # listed by a config or another mixin actually exists.
528 referenced_mixins = set()
529 for config, mixins in self.configs.items():
530 for mixin in mixins:
531 if not mixin in self.mixins:
532 errs.append('Unknown mixin "%s" referenced by config "%s".' %
533 (mixin, config))
534 referenced_mixins.add(mixin)
535
536 for mixin in self.mixins:
537 for sub_mixin in self.mixins[mixin].get('mixins', []):
538 if not sub_mixin in self.mixins:
539 errs.append('Unknown mixin "%s" referenced by mixin "%s".' %
540 (sub_mixin, mixin))
541 referenced_mixins.add(sub_mixin)
542
543 # Check that every mixin defined is actually referenced somewhere.
544 for mixin in self.mixins:
545 if not mixin in referenced_mixins:
546 errs.append('Unreferenced mixin "%s".' % mixin)
547
dpranke255085e2016-03-16 05:23:59548 # If we're checking the Chromium config, check that the 'chromium' bots
549 # which build public artifacts do not include the chrome_with_codecs mixin.
550 if self.args.config_file == self.default_config:
551 if 'chromium' in self.masters:
552 for builder in self.masters['chromium']:
553 config = self.masters['chromium'][builder]
554 def RecurseMixins(current_mixin):
555 if current_mixin == 'chrome_with_codecs':
556 errs.append('Public artifact builder "%s" can not contain the '
557 '"chrome_with_codecs" mixin.' % builder)
558 return
559 if not 'mixins' in self.mixins[current_mixin]:
560 return
561 for mixin in self.mixins[current_mixin]['mixins']:
562 RecurseMixins(mixin)
dalecurtis56fd27e2016-03-09 23:06:41563
dpranke255085e2016-03-16 05:23:59564 for mixin in self.configs[config]:
565 RecurseMixins(mixin)
566 else:
567 errs.append('Missing "chromium" master. Please update this '
568 'proprietary codecs check with the name of the master '
569 'responsible for public build artifacts.')
dalecurtis56fd27e2016-03-09 23:06:41570
dprankefe4602312015-04-08 16:20:35571 if errs:
dpranke4323c80632015-08-10 22:53:54572 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17573 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35574
dpranke0cafc162016-03-19 00:41:10575 if print_ok:
576 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35577 return 0
578
579 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30580 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34581
dprankef37aebb92016-09-23 01:14:49582 vals = self.DefaultVals()
dpranke751516a2015-10-03 01:11:34583 if self.args.builder or self.args.master or self.args.config:
584 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11585 # Re-run gn gen in order to ensure the config is consistent with the
586 # build dir.
587 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34588 return vals
589
Dirk Pranked181a1a2017-12-14 01:47:11590 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
591 'toolchain.ninja')
592 if not self.Exists(toolchain_path):
593 self.Print('Must either specify a path to an existing GN build dir '
594 'or pass in a -m/-b pair or a -c flag to specify the '
595 'configuration')
596 return {}
dpranke751516a2015-10-03 01:11:34597
Dirk Pranked181a1a2017-12-14 01:47:11598 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34599 return vals
600
dprankef37aebb92016-09-23 01:14:49601 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58602 args_contents = ""
603 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
604 if self.Exists(gn_args_path):
605 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34606 gn_args = []
607 for l in args_contents.splitlines():
608 fields = l.split(' ')
609 name = fields[0]
610 val = ' '.join(fields[2:])
611 gn_args.append('%s=%s' % (name, val))
612
dprankef37aebb92016-09-23 01:14:49613 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34614
615 def Lookup(self):
dprankef37aebb92016-09-23 01:14:49616 vals = self.ReadIOSBotConfig()
dprankee0f486f2015-11-19 23:42:00617 if not vals:
618 self.ReadConfigFile()
619 config = self.ConfigFromArgs()
dpranke9dd5e252016-04-14 04:23:09620 if config.startswith('//'):
621 if not self.Exists(self.ToAbsPath(config)):
622 raise MBErr('args file "%s" not found' % config)
dprankef37aebb92016-09-23 01:14:49623 vals = self.DefaultVals()
624 vals['args_file'] = config
dpranke9dd5e252016-04-14 04:23:09625 else:
626 if not config in self.configs:
627 raise MBErr('Config "%s" not found in %s' %
628 (config, self.args.config_file))
629 vals = self.FlattenConfig(config)
dpranke751516a2015-10-03 01:11:34630 return vals
dprankefe4602312015-04-08 16:20:35631
dprankef37aebb92016-09-23 01:14:49632 def ReadIOSBotConfig(self):
dprankee0f486f2015-11-19 23:42:00633 if not self.args.master or not self.args.builder:
634 return {}
635 path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
636 self.args.master, self.args.builder + '.json')
637 if not self.Exists(path):
638 return {}
639
640 contents = json.loads(self.ReadFile(path))
dprankee0f486f2015-11-19 23:42:00641 gn_args = ' '.join(contents.get('gn_args', []))
642
dprankef37aebb92016-09-23 01:14:49643 vals = self.DefaultVals()
644 vals['gn_args'] = gn_args
dprankef37aebb92016-09-23 01:14:49645 return vals
dprankee0f486f2015-11-19 23:42:00646
dprankefe4602312015-04-08 16:20:35647 def ReadConfigFile(self):
648 if not self.Exists(self.args.config_file):
649 raise MBErr('config file not found at %s' % self.args.config_file)
650
651 try:
652 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
653 except SyntaxError as e:
654 raise MBErr('Failed to parse config file "%s": %s' %
655 (self.args.config_file, e))
656
dprankefe4602312015-04-08 16:20:35657 self.configs = contents['configs']
dprankefe4602312015-04-08 16:20:35658 self.masters = contents['masters']
659 self.mixins = contents['mixins']
dprankefe4602312015-04-08 16:20:35660
dprankecb4a2e242016-09-19 01:13:14661 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20662 if not self.args.isolate_map_files:
663 self.args.isolate_map_files = [self.default_isolate_map]
664
665 for f in self.args.isolate_map_files:
666 if not self.Exists(f):
667 raise MBErr('isolate map file not found at %s' % f)
668 isolate_maps = {}
669 for isolate_map in self.args.isolate_map_files:
670 try:
671 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
672 duplicates = set(isolate_map).intersection(isolate_maps)
673 if duplicates:
674 raise MBErr(
675 'Duplicate targets in isolate map files: %s.' %
676 ', '.join(duplicates))
677 isolate_maps.update(isolate_map)
678 except SyntaxError as e:
679 raise MBErr(
680 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
681 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14682
dprankefe4602312015-04-08 16:20:35683 def ConfigFromArgs(self):
684 if self.args.config:
685 if self.args.master or self.args.builder:
686 raise MBErr('Can not specific both -c/--config and -m/--master or '
687 '-b/--builder')
688
689 return self.args.config
690
691 if not self.args.master or not self.args.builder:
692 raise MBErr('Must specify either -c/--config or '
693 '(-m/--master and -b/--builder)')
694
695 if not self.args.master in self.masters:
696 raise MBErr('Master name "%s" not found in "%s"' %
697 (self.args.master, self.args.config_file))
698
699 if not self.args.builder in self.masters[self.args.master]:
700 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
701 (self.args.builder, self.args.master, self.args.config_file))
702
dprankeb9380a12016-07-21 21:44:09703 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49704 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09705 if self.args.phase is None:
706 raise MBErr('Must specify a build --phase for %s on %s' %
707 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49708 phase = str(self.args.phase)
709 if phase not in config:
710 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09711 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49712 return config[phase]
dprankeb9380a12016-07-21 21:44:09713
714 if self.args.phase is not None:
715 raise MBErr('Must not specify a build --phase for %s on %s' %
716 (self.args.builder, self.args.master))
717 return config
dprankefe4602312015-04-08 16:20:35718
719 def FlattenConfig(self, config):
720 mixins = self.configs[config]
dprankef37aebb92016-09-23 01:14:49721 vals = self.DefaultVals()
dprankefe4602312015-04-08 16:20:35722
723 visited = []
724 self.FlattenMixins(mixins, vals, visited)
725 return vals
726
dprankef37aebb92016-09-23 01:14:49727 def DefaultVals(self):
728 return {
729 'args_file': '',
730 'cros_passthrough': False,
731 'gn_args': '',
dprankef37aebb92016-09-23 01:14:49732 }
733
dprankefe4602312015-04-08 16:20:35734 def FlattenMixins(self, mixins, vals, visited):
735 for m in mixins:
736 if m not in self.mixins:
737 raise MBErr('Unknown mixin "%s"' % m)
dprankeee5b51f62015-04-09 00:03:22738
dprankefe4602312015-04-08 16:20:35739 visited.append(m)
740
741 mixin_vals = self.mixins[m]
dpranke73ed0d62016-04-25 19:18:34742
743 if 'cros_passthrough' in mixin_vals:
744 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
Dirk Pranke6b99f072017-04-05 00:58:30745 if 'args_file' in mixin_vals:
746 if vals['args_file']:
747 raise MBErr('args_file specified multiple times in mixins '
748 'for %s on %s' % (self.args.builder, self.args.master))
749 vals['args_file'] = mixin_vals['args_file']
dprankefe4602312015-04-08 16:20:35750 if 'gn_args' in mixin_vals:
751 if vals['gn_args']:
752 vals['gn_args'] += ' ' + mixin_vals['gn_args']
753 else:
754 vals['gn_args'] = mixin_vals['gn_args']
dpranke73ed0d62016-04-25 19:18:34755
dprankefe4602312015-04-08 16:20:35756 if 'mixins' in mixin_vals:
757 self.FlattenMixins(mixin_vals['mixins'], vals, visited)
758 return vals
759
Takuto Ikuta9dffd7e2018-09-05 01:04:00760 def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
Dirk Prankef24e6b22018-03-27 20:12:30761 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50762
Takuto Ikuta9dffd7e2018-09-05 01:04:00763 if check:
764 cmd = self.GNCmd('gen', build_dir, '--check')
765 else:
766 cmd = self.GNCmd('gen', build_dir)
dprankeeca4a782016-04-14 01:42:38767 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:09768 if compute_inputs_for_analyze:
769 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:38770
771 # Since GN hasn't run yet, the build directory may not even exist.
772 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
773
774 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:54775 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:39776
dpranke751516a2015-10-03 01:11:34777 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:39778 # We need GN to generate the list of runtime dependencies for
779 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:14780 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:39781 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:00782 path = self.args.swarming_targets_file
783 if not self.Exists(path):
784 self.WriteFailureAndRaise('"%s" does not exist' % path,
785 output_path=None)
786 contents = self.ReadFile(path)
Erik Chen42df41d2018-08-21 17:13:31787 isolate_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:00788
dprankecb4a2e242016-09-19 01:13:14789 isolate_map = self.ReadIsolateMap()
Dirk Pranke7a7e9b62019-02-17 01:46:25790 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
791 isolate_map, build_dir)
792
Erik Chen42df41d2018-08-21 17:13:31793 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:00794 if err:
Dirk Pranke7a7e9b62019-02-17 01:46:25795 raise MBErr(err)
dpranke74559b52015-06-10 21:20:39796
dpranke751516a2015-10-03 01:11:34797 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:14798 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:39799 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
800
dprankefe4602312015-04-08 16:20:35801 ret, _, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:40802 if ret:
Dirk Pranke7a7e9b62019-02-17 01:46:25803 # If `gn gen` failed, we should exit early rather than trying to
804 # generate isolates. Run() will have already logged any error output.
805 self.Print('GN gen failed: %d' % ret)
806 return ret
dpranke74559b52015-06-10 21:20:39807
Erik Chen42df41d2018-08-21 17:13:31808 if getattr(self.args, 'swarming_targets_file', None):
Dirk Pranke7a7e9b62019-02-17 01:46:25809 self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
Erik Chen42df41d2018-08-21 17:13:31810
811 return 0
812
813 def RunGNGenAllIsolates(self, vals):
814 """
815 This command generates all .isolate files.
816
817 This command assumes that "mb.py gen" has already been run, as it relies on
818 "gn ls" to fetch all gn targets. If uses that output, combined with the
819 isolate_map, to determine all isolates that can be generated for the current
820 gn configuration.
821 """
822 build_dir = self.args.path
823 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
824 force_verbose=False)
825 if ret:
826 # If `gn ls` failed, we should exit early rather than trying to
827 # generate isolates.
828 self.Print('GN ls failed: %d' % ret)
829 return ret
830
831 # Create a reverse map from isolate label to isolate dict.
832 isolate_map = self.ReadIsolateMap()
833 isolate_dict_map = {}
834 for key, isolate_dict in isolate_map.iteritems():
835 isolate_dict_map[isolate_dict['label']] = isolate_dict
836 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
837
838 runtime_deps = []
839
840 isolate_targets = []
841 # For every GN target, look up the isolate dict.
842 for line in output.splitlines():
843 target = line.strip()
844 if target in isolate_dict_map:
845 if isolate_dict_map[target]['type'] == 'additional_compile_target':
846 # By definition, additional_compile_targets are not tests, so we
847 # shouldn't generate isolates for them.
848 continue
849
850 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
851 runtime_deps.append(target)
852
Dirk Pranke7a7e9b62019-02-17 01:46:25853 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
854 isolate_map, build_dir)
855
Erik Chen42df41d2018-08-21 17:13:31856 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
857 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
858 cmd = self.GNCmd('gen', build_dir)
859 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
860 self.Run(cmd)
861
862 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
863
Dirk Pranke7a7e9b62019-02-17 01:46:25864 def RemovePossiblyStaleRuntimeDepsFiles(self, vals, targets, isolate_map,
865 build_dir):
866 # TODO(crbug.com/932700): Because `gn gen --runtime-deps-list-file`
867 # puts the runtime_deps file in different locations based on the actual
868 # type of a target, we may end up with multiple possible runtime_deps
869 # files in a given build directory, where some of the entries might be
870 # stale (since we might be reusing an existing build directory).
871 #
872 # We need to be able to get the right one reliably; you might think
873 # we can just pick the newest file, but because GN won't update timestamps
874 # if the contents of the files change, an older runtime_deps
875 # file might actually be the one we should use over a newer one (see
876 # crbug.com/932387 for a more complete explanation and example).
877 #
878 # In order to avoid this, we need to delete any possible runtime_deps
879 # files *prior* to running GN. As long as the files aren't actually
880 # needed during the build, this hopefully will not cause unnecessary
881 # build work, and so it should be safe.
882 #
883 # Ultimately, we should just make sure we get the runtime_deps files
884 # in predictable locations so we don't have this issue at all, and
885 # that's what crbug.com/932700 is for.
886 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, targets, isolate_map)
887 for rpaths in possible_rpaths.values():
888 for rpath in rpaths:
889 path = self.ToAbsPath(build_dir, rpath)
890 if self.Exists(path):
891 self.RemoveFile(path)
892
Erik Chen42df41d2018-08-21 17:13:31893 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
894 """
895 Generates isolates for a list of ninja targets.
896
897 Ninja targets are transformed to GN targets via isolate_map.
898
899 This function assumes that a previous invocation of "mb.py gen" has
900 generated runtime deps for all targets.
901 """
Dirk Pranke7a7e9b62019-02-17 01:46:25902 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, ninja_targets,
903 isolate_map)
904
905 for target, rpaths in possible_rpaths.items():
906 # TODO(crbug.com/932700): We don't know where each .runtime_deps
907 # file might be, but assuming we called
908 # RemovePossiblyStaleRuntimeDepsFiles prior to calling `gn gen`,
909 # there should only be one file.
910 found_one = False
911 path_to_use = None
912 for r in rpaths:
913 path = self.ToAbsPath(build_dir, r)
914 if self.Exists(path):
915 if found_one:
916 raise MBErr('Found more than one of %s' % ', '.join(rpaths))
917 path_to_use = path
918 found_one = True
919
920 if not found_one:
921 raise MBErr('Did not find any of %s' % ', '.join(rpaths))
922
923 command, extra_files = self.GetIsolateCommand(target, vals)
924 runtime_deps = self.ReadFile(path_to_use).splitlines()
925
926 canonical_target = target.replace(':','_').replace('/','_')
927 self.WriteIsolateFiles(build_dir, command, canonical_target, runtime_deps,
928 extra_files)
929
930 def PossibleRuntimeDepsPaths(self, vals, ninja_targets, isolate_map):
931 """Returns a map of targets to possible .runtime_deps paths.
932
933 Each ninja target maps on to a GN label, but depending on the type
934 of the GN target, `gn gen --runtime-deps-list-file` will write
935 the .runtime_deps files into different locations. Unfortunately, in
936 some cases we don't actually know which of multiple locations will
937 actually be used, so we return all plausible candidates.
938
939 The paths that are returned are relative to the build directory.
940 """
941
jbudoricke3c4f95e2016-04-28 23:17:38942 android = 'target_os="android"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:42943 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:30944 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Dirk Pranke7a7e9b62019-02-17 01:46:25945 possible_runtime_deps_rpaths = {}
Erik Chen42df41d2018-08-21 17:13:31946 for target in ninja_targets:
947 # TODO(https://crbug.com/876065): 'official_tests' use
948 # type='additional_compile_target' to isolate tests. This is not the
949 # intended use for 'additional_compile_target'.
950 if (isolate_map[target]['type'] == 'additional_compile_target' and
951 target != 'official_tests'):
952 # By definition, additional_compile_targets are not tests, so we
953 # shouldn't generate isolates for them.
Dirk Pranke7a7e9b62019-02-17 01:46:25954 raise MBErr('Cannot generate isolate for %s since it is an '
955 'additional_compile_target.' % target)
Erik Chen42df41d2018-08-21 17:13:31956 elif android:
jbudoricke3c4f95e2016-04-28 23:17:38957 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:02958 # will result in runtime_deps associated with the stamp file, while the
959 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:44960 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:25961 rpaths = [
dprankecb4a2e242016-09-19 01:13:14962 target + '.runtime_deps',
dpranke48ccf8f2016-03-28 23:58:28963 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
Kevin Marshallf35fa5f2018-01-29 19:24:42964 elif fuchsia:
965 # Only emit a runtime deps file for the group() target on Fuchsia.
Abhishek Arya2f5f7342018-06-13 16:59:44966 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:25967 rpaths = ['obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
dprankecb4a2e242016-09-19 01:13:14968 elif (isolate_map[target]['type'] == 'script' or
Roberto Carrillo1460da852018-12-14 17:10:39969 isolate_map[target]['type'] == 'fuzzer' or
dprankecb4a2e242016-09-19 01:13:14970 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:11971 # For script targets, the build target is usually a group,
972 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:49973 # for the label, which lives under the obj/ directory, but it may
974 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:44975 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:25976 rpaths = ['obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
Nico Weberd94b71a2018-02-22 22:00:30977 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:25978 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:49979 else:
Dirk Pranke7a7e9b62019-02-17 01:46:25980 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:30981 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:25982 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:52983 else:
Dirk Pranke7a7e9b62019-02-17 01:46:25984 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:02985
Dirk Pranke7a7e9b62019-02-17 01:46:25986 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:41987
Dirk Pranke7a7e9b62019-02-17 01:46:25988 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:34989
990 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:30991 target = self.args.target
dprankecb4a2e242016-09-19 01:13:14992 isolate_map = self.ReadIsolateMap()
993 err, labels = self.MapTargetsToLabels(isolate_map, [target])
994 if err:
995 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:25996
dprankecb4a2e242016-09-19 01:13:14997 label = labels[0]
dpranke751516a2015-10-03 01:11:34998
Dirk Prankef24e6b22018-03-27 20:12:30999 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:141000 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:341001
dprankeeca4a782016-04-14 01:42:381002 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:201003 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:341004 if ret:
dpranke030d7a6d2016-03-26 17:23:501005 if out:
1006 self.Print(out)
dpranke751516a2015-10-03 01:11:341007 return ret
1008
1009 runtime_deps = out.splitlines()
1010
1011 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
1012 extra_files)
1013
1014 ret, _, _ = self.Run([
1015 self.executable,
1016 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1017 'check',
1018 '-i',
1019 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1020 '-s',
1021 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1022 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301023
dprankefe4602312015-04-08 16:20:351024 return ret
1025
dpranke751516a2015-10-03 01:11:341026 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps,
1027 extra_files):
1028 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
1029 self.WriteFile(isolate_path,
1030 pprint.pformat({
1031 'variables': {
1032 'command': command,
1033 'files': sorted(runtime_deps + extra_files),
1034 }
1035 }) + '\n')
1036
1037 self.WriteJSON(
1038 {
1039 'args': [
1040 '--isolated',
1041 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1042 '--isolate',
1043 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1044 ],
1045 'dir': self.chromium_src_dir,
1046 'version': 1,
1047 },
1048 isolate_path + 'd.gen.json',
1049 )
1050
dprankecb4a2e242016-09-19 01:13:141051 def MapTargetsToLabels(self, isolate_map, targets):
1052 labels = []
1053 err = ''
1054
dprankecb4a2e242016-09-19 01:13:141055 for target in targets:
1056 if target == 'all':
1057 labels.append(target)
1058 elif target.startswith('//'):
1059 labels.append(target)
1060 else:
1061 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421062 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141063 err += ('test target "%s" type is unknown\n' % target)
1064 else:
thakis024d6f32017-05-16 23:21:421065 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141066 else:
1067 err += ('target "%s" not found in '
1068 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1069
1070 return err, labels
1071
dprankeeca4a782016-04-14 01:42:381072 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461073 if self.platform == 'linux2':
1074 subdir, exe = 'linux64', 'gn'
1075 elif self.platform == 'darwin':
1076 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251077 elif self.platform == 'aix6':
1078 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461079 else:
1080 subdir, exe = 'win', 'gn.exe'
1081
1082 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081083 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521084
dprankecb4a2e242016-09-19 01:13:141085
dprankeeca4a782016-04-14 01:42:381086 def GNArgs(self, vals):
dpranke73ed0d62016-04-25 19:18:341087 if vals['cros_passthrough']:
1088 if not 'GN_ARGS' in os.environ:
1089 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1090 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161091 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301092 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341093 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351094 if vals['gn_args']:
1095 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341096 else:
1097 gn_args = vals['gn_args']
1098
dpranked0c138b2016-04-13 18:28:471099 if self.args.goma_dir:
1100 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381101
agrieve41d21a72016-04-14 18:02:261102 android_version_code = self.args.android_version_code
1103 if android_version_code:
1104 gn_args += ' android_default_version_code="%s"' % android_version_code
1105
1106 android_version_name = self.args.android_version_name
1107 if android_version_name:
1108 gn_args += ' android_default_version_name="%s"' % android_version_name
1109
dprankeeca4a782016-04-14 01:42:381110 # Canonicalize the arg string into a sorted, newline-separated list
1111 # of key-value pairs, and de-dup the keys if need be so that only
1112 # the last instance of each arg is listed.
1113 gn_args = gn_helpers.ToGNString(gn_helpers.FromGNArgs(gn_args))
1114
Ben Pastene65ccf6132018-11-08 00:47:591115 # If we're using the Simple Chrome SDK, add a comment at the top that
1116 # points to the doc. This must happen after the gn_helpers.ToGNString()
1117 # call above since gn_helpers strips comments.
1118 if vals['cros_passthrough']:
1119 simplechrome_comment = [
1120 '# These args are generated via the Simple Chrome SDK. See the link',
1121 '# below for more details:',
1122 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
1123 ]
1124 gn_args = '%s\n%s' % ('\n'.join(simplechrome_comment), gn_args)
1125
dpranke9dd5e252016-04-14 04:23:091126 args_file = vals.get('args_file', None)
1127 if args_file:
1128 gn_args = ('import("%s")\n' % vals['args_file']) + gn_args
dprankeeca4a782016-04-14 01:42:381129 return gn_args
dprankefe4602312015-04-08 16:20:351130
dprankecb4a2e242016-09-19 01:13:141131 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071132 isolate_map = self.ReadIsolateMap()
1133
Scott Graham3be4b4162017-09-12 00:41:411134 is_android = 'target_os="android"' in vals['gn_args']
Benjamin Pastene3bce864e2018-04-14 01:16:321135 is_simplechrome = vals.get('cros_passthrough', False)
Scott Graham3be4b4162017-09-12 00:41:411136 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:301137 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061138
kylechar39705682017-01-19 14:37:231139 # This should be true if tests with type='windowed_test_launcher' are
1140 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081141 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1142 # backends. Currently, tests are executed for the headless and X11 backends
1143 # and both can run under Xvfb.
1144 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1145 # backend.
Scott Graham3be4b4162017-09-12 00:41:411146 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251147
1148 asan = 'is_asan=true' in vals['gn_args']
1149 msan = 'is_msan=true' in vals['gn_args']
1150 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411151 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251152
dprankecb4a2e242016-09-19 01:13:141153 test_type = isolate_map[target]['type']
dprankefe0d35e2016-02-05 02:43:591154
dprankecb4a2e242016-09-19 01:13:141155 executable = isolate_map[target].get('executable', target)
Nico Weberd94b71a2018-02-22 22:00:301156 executable_suffix = '.exe' if is_win else ''
dprankefe0d35e2016-02-05 02:43:591157
dprankea55584f12015-07-22 00:52:471158 cmdline = []
Andrii Shyshkalovc158e0102018-01-10 05:52:001159 extra_files = [
1160 '../../.vpython',
1161 '../../testing/test_env.py',
1162 ]
dpranked8113582015-06-05 20:08:251163
dprankecb4a2e242016-09-19 01:13:141164 if test_type == 'nontest':
1165 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1166 output_path=None)
1167
Roberto Carrillo1460da852018-12-14 17:10:391168 if test_type == 'fuzzer':
1169 cmdline = [
1170 '../../testing/test_env.py',
1171 '../../tools/code_coverage/run_fuzz_target.py',
1172 '--fuzzer', './' + target,
1173 '--output-dir', '${ISOLATED_OUTDIR}',
1174 '--timeout', '3600']
1175 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011176 cmdline = []
1177 if asan:
1178 cmdline += [os.path.join('bin', 'run_with_asan')]
1179 cmdline += [
John Budorickfb97a852017-12-20 20:10:191180 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041181 '../../build/android/test_wrapper/logdog_wrapper.py',
1182 '--target', target,
hzl9ae14452017-04-04 23:38:021183 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481184 '--store-tombstones']
Scott Graham3be4b4162017-09-12 00:41:411185 elif is_fuchsia and test_type != 'script':
John Budorickfb97a852017-12-20 20:10:191186 cmdline = [
1187 '../../testing/test_env.py',
1188 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441189 '--test-launcher-bot-mode',
John Budorickfb97a852017-12-20 20:10:191190 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321191 elif is_simplechrome and test_type != 'script':
1192 cmdline = [
1193 '../../testing/test_env.py',
1194 os.path.join('bin', 'run_%s' % target),
1195 ]
kylechar39705682017-01-19 14:37:231196 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001197 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471198 cmdline = [
dprankefe0d35e2016-02-05 02:43:591199 '../../testing/xvfb.py',
dprankefe0d35e2016-02-05 02:43:591200 './' + str(executable) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591201 '--test-launcher-bot-mode',
1202 '--asan=%d' % asan,
1203 '--msan=%d' % msan,
1204 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411205 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471206 ]
1207 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471208 cmdline = [
1209 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591210 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251211 '--test-launcher-bot-mode',
1212 '--asan=%d' % asan,
1213 '--msan=%d' % msan,
1214 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411215 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471216 ]
dpranke6abd8652015-08-28 03:21:111217 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241218 cmdline = []
1219 # If we're testing a CrOS simplechrome build, assume we need to launch a
1220 # VM first. So prepend the command to run with the VM launcher.
1221 # TODO(bpastene): Differentiate between CrOS VM and hardware tests.
1222 if is_simplechrome:
1223 cmdline = [os.path.join('bin', 'launch_cros_vm')]
1224 cmdline += [
dpranke6abd8652015-08-28 03:21:111225 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141226 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591227 ]
Dirk Prankef24e6b22018-03-27 20:12:301228 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471229 cmdline = [
1230 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591231 ]
dprankea55584f12015-07-22 00:52:471232 else:
1233 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1234 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251235
Abhishek Arya2f5f7342018-06-13 16:59:441236 if is_win and asan:
Alexander Dunaev384ba482018-03-21 17:56:121237 # Sandbox is not yet supported by ASAN for Windows.
1238 # Perhaps this is only needed for tests that use the sandbox?
1239 cmdline.append('--no-sandbox')
1240
dprankecb4a2e242016-09-19 01:13:141241 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591242
dpranked8113582015-06-05 20:08:251243 return cmdline, extra_files
1244
dpranke74559b52015-06-10 21:20:391245 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331246 return self.PathJoin(self.chromium_src_dir,
1247 self.ToSrcRelPath(build_path),
1248 *comps)
dpranked8113582015-06-05 20:08:251249
dprankeee5b51f62015-04-09 00:03:221250 def ToSrcRelPath(self, path):
1251 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501252 if path.startswith('//'):
1253 return path[2:].replace('/', self.sep)
1254 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351255
Dirk Pranke0fd41bcd2015-06-19 00:05:501256 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141257 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501258 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001259 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501260 if ret:
1261 return ret
1262
Dirk Prankef24e6b22018-03-27 20:12:301263 build_path = self.args.path
1264 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141265 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301266 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141267 gn_output_path = output_path + '.gn'
1268
dpranke7837fc362015-11-19 03:54:161269 inp = self.ReadInputJSON(['files', 'test_targets',
1270 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321271 if self.args.verbose:
1272 self.Print()
1273 self.Print('analyze input:')
1274 self.PrintJSON(inp)
1275 self.Print()
1276
dpranke76734662015-04-16 02:17:501277
dpranke7c5f614d2015-07-22 23:43:391278 # This shouldn't normally happen, but could due to unusual race conditions,
1279 # like a try job that gets scheduled before a patch lands but runs after
1280 # the patch has landed.
1281 if not inp['files']:
1282 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161283 self.WriteJSON({
1284 'status': 'No dependency',
1285 'compile_targets': [],
1286 'test_targets': [],
1287 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391288 return 0
1289
dprankecb4a2e242016-09-19 01:13:141290 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161291 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561292
dprankecb4a2e242016-09-19 01:13:141293 isolate_map = self.ReadIsolateMap()
1294 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1295 isolate_map, inp['additional_compile_targets'])
1296 if err:
1297 raise MBErr(err)
1298
1299 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1300 isolate_map, inp['test_targets'])
1301 if err:
1302 raise MBErr(err)
1303 labels_to_targets = {}
1304 for i, label in enumerate(gn_inp['test_targets']):
1305 labels_to_targets[label] = inp['test_targets'][i]
1306
dprankef61de2f2015-05-14 04:09:561307 try:
dprankecb4a2e242016-09-19 01:13:141308 self.WriteJSON(gn_inp, gn_input_path)
1309 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
1310 ret, _, _ = self.Run(cmd, force_verbose=True)
1311 if ret:
1312 return ret
dpranke067d0142015-05-14 22:52:451313
dprankecb4a2e242016-09-19 01:13:141314 gn_outp_str = self.ReadFile(gn_output_path)
1315 try:
1316 gn_outp = json.loads(gn_outp_str)
1317 except Exception as e:
1318 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1319 % (repr(gn_outp_str), str(e)))
1320 raise
1321
1322 outp = {}
1323 if 'status' in gn_outp:
1324 outp['status'] = gn_outp['status']
1325 if 'error' in gn_outp:
1326 outp['error'] = gn_outp['error']
1327 if 'invalid_targets' in gn_outp:
1328 outp['invalid_targets'] = gn_outp['invalid_targets']
1329 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491330 all_input_compile_targets = sorted(
1331 set(inp['test_targets'] + inp['additional_compile_targets']))
1332
1333 # If we're building 'all', we can throw away the rest of the targets
1334 # since they're redundant.
dpranke385a3102016-09-20 22:04:081335 if 'all' in gn_outp['compile_targets']:
1336 outp['compile_targets'] = ['all']
1337 else:
Dirk Pranke45165072017-11-08 04:57:491338 outp['compile_targets'] = gn_outp['compile_targets']
1339
1340 # crbug.com/736215: When GN returns targets back, for targets in
1341 # the default toolchain, GN will have generated a phony ninja
1342 # target matching the label, and so we can safely (and easily)
1343 # transform any GN label into the matching ninja target. For
1344 # targets in other toolchains, though, GN doesn't generate the
1345 # phony targets, and we don't know how to turn the labels into
1346 # compile targets. In this case, we also conservatively give up
1347 # and build everything. Probably the right thing to do here is
1348 # to have GN return the compile targets directly.
1349 if any("(" in target for target in outp['compile_targets']):
1350 self.Print('WARNING: targets with non-default toolchains were '
1351 'found, building everything instead.')
1352 outp['compile_targets'] = all_input_compile_targets
1353 else:
dpranke385a3102016-09-20 22:04:081354 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491355 label.replace('//', '') for label in outp['compile_targets']]
1356
1357 # Windows has a maximum command line length of 8k; even Linux
1358 # maxes out at 128k; if analyze returns a *really long* list of
1359 # targets, we just give up and conservatively build everything instead.
1360 # Probably the right thing here is for ninja to support response
1361 # files as input on the command line
1362 # (see https://github.com/ninja-build/ninja/issues/1355).
1363 if len(' '.join(outp['compile_targets'])) > 7*1024:
1364 self.Print('WARNING: Too many compile targets were affected.')
1365 self.Print('WARNING: Building everything instead to avoid '
1366 'command-line length issues.')
1367 outp['compile_targets'] = all_input_compile_targets
1368
1369
dprankecb4a2e242016-09-19 01:13:141370 if 'test_targets' in gn_outp:
1371 outp['test_targets'] = [
1372 labels_to_targets[label] for label in gn_outp['test_targets']]
1373
1374 if self.args.verbose:
1375 self.Print()
1376 self.Print('analyze output:')
1377 self.PrintJSON(outp)
1378 self.Print()
1379
1380 self.WriteJSON(outp, output_path)
1381
dprankef61de2f2015-05-14 04:09:561382 finally:
dprankecb4a2e242016-09-19 01:13:141383 if self.Exists(gn_input_path):
1384 self.RemoveFile(gn_input_path)
1385 if self.Exists(gn_output_path):
1386 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351387
1388 return 0
1389
dpranked8113582015-06-05 20:08:251390 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301391 path = self.args.input_path
1392 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351393 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321394 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351395
1396 try:
1397 inp = json.loads(self.ReadFile(path))
1398 except Exception as e:
1399 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321400 (path, e), output_path)
dpranked8113582015-06-05 20:08:251401
1402 for k in required_keys:
1403 if not k in inp:
1404 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1405 output_path)
dprankefe4602312015-04-08 16:20:351406
1407 return inp
1408
dpranked5b2b9432015-06-23 16:55:301409 def WriteFailureAndRaise(self, msg, output_path):
1410 if output_path:
dprankee0547cd2015-09-15 01:27:401411 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351412 raise MBErr(msg)
1413
dprankee0547cd2015-09-15 01:27:401414 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321415 try:
dprankee0547cd2015-09-15 01:27:401416 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1417 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321418 except Exception as e:
1419 raise MBErr('Error %s writing to the output path "%s"' %
1420 (e, path))
dprankefe4602312015-04-08 16:20:351421
aneeshmde50f472016-04-01 01:13:101422 def CheckCompile(self, master, builder):
1423 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1424 url = urllib2.quote(url_template.format(master=master, builder=builder),
1425 safe=':/()?=')
1426 try:
1427 builds = json.loads(self.Fetch(url))
1428 except Exception as e:
1429 return str(e)
1430 successes = sorted(
1431 [int(x) for x in builds.keys() if "text" in builds[x] and
1432 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1433 reverse=True)
1434 if not successes:
1435 return "no successful builds"
1436 build = builds[str(successes[0])]
1437 step_names = set([step["name"] for step in build["steps"]])
1438 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1439 if compile_indicators & step_names:
1440 return "compiles"
1441 return "does not compile"
1442
dpranke3cec199c2015-09-22 23:29:021443 def PrintCmd(self, cmd, env):
1444 if self.platform == 'win32':
1445 env_prefix = 'set '
1446 env_quoter = QuoteForSet
1447 shell_quoter = QuoteForCmd
1448 else:
1449 env_prefix = ''
1450 env_quoter = pipes.quote
1451 shell_quoter = pipes.quote
1452
1453 def print_env(var):
1454 if env and var in env:
1455 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1456
dprankeec079262016-06-07 02:21:201457 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021458
dpranke8c2cfd32015-09-17 20:12:331459 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351460 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021461 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351462
dprankecda00332015-04-11 04:18:321463 def PrintJSON(self, obj):
1464 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1465
dpranke751516a2015-10-03 01:11:341466 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301467 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381468 if self.platform == 'win32':
1469 # On Windows use the batch script since there is no exe
1470 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1471 else:
1472 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341473 if self.args.jobs:
1474 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1475 ninja_cmd.append(target)
1476 ret, _, _ = self.Run(ninja_cmd, force_verbose=False, buffer_output=False)
1477 return ret
1478
1479 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351480 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401481 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021482 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351483 if self.args.dryrun:
1484 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401485
dpranke751516a2015-10-03 01:11:341486 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401487 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341488 if ret:
1489 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351490 if out:
dprankeee5b51f62015-04-09 00:03:221491 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351492 if err:
dprankeee5b51f62015-04-09 00:03:221493 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351494 return ret, out, err
1495
dpranke751516a2015-10-03 01:11:341496 def Call(self, cmd, env=None, buffer_output=True):
1497 if buffer_output:
1498 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1499 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1500 env=env)
1501 out, err = p.communicate()
1502 else:
1503 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1504 env=env)
1505 p.wait()
1506 out = err = ''
dprankefe4602312015-04-08 16:20:351507 return p.returncode, out, err
1508
1509 def ExpandUser(self, path):
1510 # This function largely exists so it can be overridden for testing.
1511 return os.path.expanduser(path)
1512
1513 def Exists(self, path):
1514 # This function largely exists so it can be overridden for testing.
1515 return os.path.exists(path)
1516
dpranke867bcf4a2016-03-14 22:28:321517 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501518 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321519 f = urllib2.urlopen(url)
1520 contents = f.read()
1521 f.close()
1522 return contents
1523
dprankec3441d12015-06-23 23:01:351524 def MaybeMakeDirectory(self, path):
1525 try:
1526 os.makedirs(path)
1527 except OSError, e:
1528 if e.errno != errno.EEXIST:
1529 raise
1530
dpranke8c2cfd32015-09-17 20:12:331531 def PathJoin(self, *comps):
1532 # This function largely exists so it can be overriden for testing.
1533 return os.path.join(*comps)
1534
dpranke030d7a6d2016-03-26 17:23:501535 def Print(self, *args, **kwargs):
1536 # This function largely exists so it can be overridden for testing.
1537 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101538 if kwargs.get('stream', sys.stdout) == sys.stdout:
1539 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501540
dprankefe4602312015-04-08 16:20:351541 def ReadFile(self, path):
1542 # This function largely exists so it can be overriden for testing.
1543 with open(path) as fp:
1544 return fp.read()
1545
dpranke030d7a6d2016-03-26 17:23:501546 def RelPath(self, path, start='.'):
1547 # This function largely exists so it can be overriden for testing.
1548 return os.path.relpath(path, start)
1549
dprankef61de2f2015-05-14 04:09:561550 def RemoveFile(self, path):
1551 # This function largely exists so it can be overriden for testing.
1552 os.remove(path)
1553
dprankec161aa92015-09-14 20:21:131554 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331555 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131556 # In other places in chromium, we often have to retry this command
1557 # because we're worried about other processes still holding on to
1558 # file handles, but when MB is invoked, it will be early enough in the
1559 # build that their should be no other processes to interfere. We
1560 # can change this if need be.
1561 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1562 else:
1563 shutil.rmtree(abs_path, ignore_errors=True)
1564
Dirk Prankef24e6b22018-03-27 20:12:301565 def TempDir(self):
1566 # This function largely exists so it can be overriden for testing.
1567 return tempfile.mkdtemp(prefix='mb_')
1568
dprankef61de2f2015-05-14 04:09:561569 def TempFile(self, mode='w'):
1570 # This function largely exists so it can be overriden for testing.
1571 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1572
dprankee0547cd2015-09-15 01:27:401573 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351574 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401575 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301576 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351577 with open(path, 'w') as fp:
1578 return fp.write(contents)
1579
dprankef61de2f2015-05-14 04:09:561580
dprankefe4602312015-04-08 16:20:351581class MBErr(Exception):
1582 pass
1583
1584
dpranke3cec199c2015-09-22 23:29:021585# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1586# details of this next section, which handles escaping command lines
1587# so that they can be copied and pasted into a cmd window.
1588UNSAFE_FOR_SET = set('^<>&|')
1589UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1590ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1591
1592
1593def QuoteForSet(arg):
1594 if any(a in UNSAFE_FOR_SET for a in arg):
1595 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1596 return arg
1597
1598
1599def QuoteForCmd(arg):
1600 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021601 if arg == '' or ' ' in arg or '"' in arg:
1602 quote_re = re.compile(r'(\\*)"')
1603 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1604
1605 # Then check to see if the arg contains any metacharacters other than
1606 # double quotes; if it does, quote everything (including the double
1607 # quotes) for safety.
1608 if any(a in UNSAFE_FOR_CMD for a in arg):
1609 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1610 return arg
1611
1612
dprankefe4602312015-04-08 16:20:351613if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591614 sys.exit(main(sys.argv[1:]))