blob: c96ccc5d6f9c091d36e7be78e43e28b5d077355a [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']
Dirk Pranke26de05aec2019-04-03 19:18:38943 ios = 'target_os="ios"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:42944 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:30945 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Dirk Pranke7a7e9b62019-02-17 01:46:25946 possible_runtime_deps_rpaths = {}
Erik Chen42df41d2018-08-21 17:13:31947 for target in ninja_targets:
948 # TODO(https://crbug.com/876065): 'official_tests' use
949 # type='additional_compile_target' to isolate tests. This is not the
950 # intended use for 'additional_compile_target'.
951 if (isolate_map[target]['type'] == 'additional_compile_target' and
952 target != 'official_tests'):
953 # By definition, additional_compile_targets are not tests, so we
954 # shouldn't generate isolates for them.
Dirk Pranke7a7e9b62019-02-17 01:46:25955 raise MBErr('Cannot generate isolate for %s since it is an '
956 'additional_compile_target.' % target)
John Budorick56bfa782019-04-10 02:36:21957 elif isolate_map[target]['type'] == 'generated_script':
958 script = isolate_map[target]['script']
959 rpaths = ['%s.runtime_deps' % script]
Erik Chen42df41d2018-08-21 17:13:31960 elif android:
jbudoricke3c4f95e2016-04-28 23:17:38961 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:02962 # will result in runtime_deps associated with the stamp file, while the
963 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:44964 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:25965 rpaths = [
dprankecb4a2e242016-09-19 01:13:14966 target + '.runtime_deps',
dpranke48ccf8f2016-03-28 23:58:28967 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
Dirk Pranke26de05aec2019-04-03 19:18:38968 elif ios or fuchsia:
969 # iOS and Fuchsia targets end up as groups.
Abhishek Arya2f5f7342018-06-13 16:59:44970 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:25971 rpaths = ['obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
dprankecb4a2e242016-09-19 01:13:14972 elif (isolate_map[target]['type'] == 'script' or
Roberto Carrillo1460da852018-12-14 17:10:39973 isolate_map[target]['type'] == 'fuzzer' or
dprankecb4a2e242016-09-19 01:13:14974 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:11975 # For script targets, the build target is usually a group,
976 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:49977 # for the label, which lives under the obj/ directory, but it may
978 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:44979 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:25980 rpaths = ['obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
Nico Weberd94b71a2018-02-22 22:00:30981 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:25982 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:49983 else:
Dirk Pranke7a7e9b62019-02-17 01:46:25984 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:30985 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:25986 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:52987 else:
Dirk Pranke7a7e9b62019-02-17 01:46:25988 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:02989
Dirk Pranke7a7e9b62019-02-17 01:46:25990 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:41991
Dirk Pranke7a7e9b62019-02-17 01:46:25992 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:34993
994 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:30995 target = self.args.target
dprankecb4a2e242016-09-19 01:13:14996 isolate_map = self.ReadIsolateMap()
997 err, labels = self.MapTargetsToLabels(isolate_map, [target])
998 if err:
999 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:251000
dprankecb4a2e242016-09-19 01:13:141001 label = labels[0]
dpranke751516a2015-10-03 01:11:341002
Dirk Prankef24e6b22018-03-27 20:12:301003 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:141004 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:341005
dprankeeca4a782016-04-14 01:42:381006 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:201007 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:341008 if ret:
dpranke030d7a6d2016-03-26 17:23:501009 if out:
1010 self.Print(out)
dpranke751516a2015-10-03 01:11:341011 return ret
1012
1013 runtime_deps = out.splitlines()
1014
1015 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
1016 extra_files)
1017
1018 ret, _, _ = self.Run([
1019 self.executable,
1020 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1021 'check',
1022 '-i',
1023 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1024 '-s',
1025 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1026 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301027
dprankefe4602312015-04-08 16:20:351028 return ret
1029
dpranke751516a2015-10-03 01:11:341030 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps,
1031 extra_files):
1032 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
1033 self.WriteFile(isolate_path,
1034 pprint.pformat({
1035 'variables': {
1036 'command': command,
1037 'files': sorted(runtime_deps + extra_files),
1038 }
1039 }) + '\n')
1040
1041 self.WriteJSON(
1042 {
1043 'args': [
1044 '--isolated',
1045 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1046 '--isolate',
1047 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1048 ],
1049 'dir': self.chromium_src_dir,
1050 'version': 1,
1051 },
1052 isolate_path + 'd.gen.json',
1053 )
1054
dprankecb4a2e242016-09-19 01:13:141055 def MapTargetsToLabels(self, isolate_map, targets):
1056 labels = []
1057 err = ''
1058
dprankecb4a2e242016-09-19 01:13:141059 for target in targets:
1060 if target == 'all':
1061 labels.append(target)
1062 elif target.startswith('//'):
1063 labels.append(target)
1064 else:
1065 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421066 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141067 err += ('test target "%s" type is unknown\n' % target)
1068 else:
thakis024d6f32017-05-16 23:21:421069 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141070 else:
1071 err += ('target "%s" not found in '
1072 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1073
1074 return err, labels
1075
dprankeeca4a782016-04-14 01:42:381076 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461077 if self.platform == 'linux2':
1078 subdir, exe = 'linux64', 'gn'
1079 elif self.platform == 'darwin':
1080 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251081 elif self.platform == 'aix6':
1082 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461083 else:
1084 subdir, exe = 'win', 'gn.exe'
1085
1086 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081087 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521088
dprankecb4a2e242016-09-19 01:13:141089
dprankeeca4a782016-04-14 01:42:381090 def GNArgs(self, vals):
dpranke73ed0d62016-04-25 19:18:341091 if vals['cros_passthrough']:
1092 if not 'GN_ARGS' in os.environ:
1093 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1094 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161095 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301096 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341097 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351098 if vals['gn_args']:
1099 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341100 else:
1101 gn_args = vals['gn_args']
1102
dpranked0c138b2016-04-13 18:28:471103 if self.args.goma_dir:
1104 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381105
agrieve41d21a72016-04-14 18:02:261106 android_version_code = self.args.android_version_code
1107 if android_version_code:
1108 gn_args += ' android_default_version_code="%s"' % android_version_code
1109
1110 android_version_name = self.args.android_version_name
1111 if android_version_name:
1112 gn_args += ' android_default_version_name="%s"' % android_version_name
1113
dprankeeca4a782016-04-14 01:42:381114 # Canonicalize the arg string into a sorted, newline-separated list
1115 # of key-value pairs, and de-dup the keys if need be so that only
1116 # the last instance of each arg is listed.
1117 gn_args = gn_helpers.ToGNString(gn_helpers.FromGNArgs(gn_args))
1118
Ben Pastene65ccf6132018-11-08 00:47:591119 # If we're using the Simple Chrome SDK, add a comment at the top that
1120 # points to the doc. This must happen after the gn_helpers.ToGNString()
1121 # call above since gn_helpers strips comments.
1122 if vals['cros_passthrough']:
1123 simplechrome_comment = [
1124 '# These args are generated via the Simple Chrome SDK. See the link',
1125 '# below for more details:',
1126 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
1127 ]
1128 gn_args = '%s\n%s' % ('\n'.join(simplechrome_comment), gn_args)
1129
dpranke9dd5e252016-04-14 04:23:091130 args_file = vals.get('args_file', None)
1131 if args_file:
1132 gn_args = ('import("%s")\n' % vals['args_file']) + gn_args
dprankeeca4a782016-04-14 01:42:381133 return gn_args
dprankefe4602312015-04-08 16:20:351134
dprankecb4a2e242016-09-19 01:13:141135 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071136 isolate_map = self.ReadIsolateMap()
1137
Scott Graham3be4b4162017-09-12 00:41:411138 is_android = 'target_os="android"' in vals['gn_args']
Benjamin Pastene3bce864e2018-04-14 01:16:321139 is_simplechrome = vals.get('cros_passthrough', False)
Scott Graham3be4b4162017-09-12 00:41:411140 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:301141 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061142
kylechar39705682017-01-19 14:37:231143 # This should be true if tests with type='windowed_test_launcher' are
1144 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081145 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1146 # backends. Currently, tests are executed for the headless and X11 backends
1147 # and both can run under Xvfb.
1148 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1149 # backend.
Scott Graham3be4b4162017-09-12 00:41:411150 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251151
1152 asan = 'is_asan=true' in vals['gn_args']
1153 msan = 'is_msan=true' in vals['gn_args']
1154 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411155 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251156
dprankecb4a2e242016-09-19 01:13:141157 test_type = isolate_map[target]['type']
dprankefe0d35e2016-02-05 02:43:591158
dprankecb4a2e242016-09-19 01:13:141159 executable = isolate_map[target].get('executable', target)
Nico Weberd94b71a2018-02-22 22:00:301160 executable_suffix = '.exe' if is_win else ''
dprankefe0d35e2016-02-05 02:43:591161
dprankea55584f12015-07-22 00:52:471162 cmdline = []
Andrii Shyshkalovc158e0102018-01-10 05:52:001163 extra_files = [
1164 '../../.vpython',
1165 '../../testing/test_env.py',
1166 ]
dpranked8113582015-06-05 20:08:251167
dprankecb4a2e242016-09-19 01:13:141168 if test_type == 'nontest':
1169 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1170 output_path=None)
1171
Roberto Carrillo1460da852018-12-14 17:10:391172 if test_type == 'fuzzer':
1173 cmdline = [
1174 '../../testing/test_env.py',
1175 '../../tools/code_coverage/run_fuzz_target.py',
1176 '--fuzzer', './' + target,
1177 '--output-dir', '${ISOLATED_OUTDIR}',
1178 '--timeout', '3600']
1179 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011180 cmdline = []
1181 if asan:
John Budorick31cdce62019-04-03 20:56:111182 cmdline += [os.path.join('bin', 'run_with_asan'), '--']
John Budorick8c4203042019-03-19 17:22:011183 cmdline += [
John Budorickfb97a852017-12-20 20:10:191184 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041185 '../../build/android/test_wrapper/logdog_wrapper.py',
1186 '--target', target,
hzl9ae14452017-04-04 23:38:021187 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481188 '--store-tombstones']
Scott Graham3be4b4162017-09-12 00:41:411189 elif is_fuchsia and test_type != 'script':
John Budorickfb97a852017-12-20 20:10:191190 cmdline = [
1191 '../../testing/test_env.py',
1192 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441193 '--test-launcher-bot-mode',
John Budorickfb97a852017-12-20 20:10:191194 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321195 elif is_simplechrome and test_type != 'script':
1196 cmdline = [
1197 '../../testing/test_env.py',
1198 os.path.join('bin', 'run_%s' % target),
1199 ]
kylechar39705682017-01-19 14:37:231200 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001201 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471202 cmdline = [
dprankefe0d35e2016-02-05 02:43:591203 '../../testing/xvfb.py',
dprankefe0d35e2016-02-05 02:43:591204 './' + str(executable) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591205 '--test-launcher-bot-mode',
1206 '--asan=%d' % asan,
1207 '--msan=%d' % msan,
1208 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411209 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471210 ]
1211 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471212 cmdline = [
1213 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591214 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251215 '--test-launcher-bot-mode',
1216 '--asan=%d' % asan,
1217 '--msan=%d' % msan,
1218 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411219 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471220 ]
dpranke6abd8652015-08-28 03:21:111221 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241222 cmdline = []
1223 # If we're testing a CrOS simplechrome build, assume we need to launch a
1224 # VM first. So prepend the command to run with the VM launcher.
1225 # TODO(bpastene): Differentiate between CrOS VM and hardware tests.
1226 if is_simplechrome:
1227 cmdline = [os.path.join('bin', 'launch_cros_vm')]
1228 cmdline += [
dpranke6abd8652015-08-28 03:21:111229 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141230 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591231 ]
John Budorick56bfa782019-04-10 02:36:211232 elif test_type == 'generated_script':
1233 cmdline = [
1234 '../../testing/test_env.py',
1235 isolate_map[target]['script'],
1236 ]
Dirk Prankef24e6b22018-03-27 20:12:301237 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471238 cmdline = [
1239 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591240 ]
dprankea55584f12015-07-22 00:52:471241 else:
1242 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1243 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251244
Abhishek Arya2f5f7342018-06-13 16:59:441245 if is_win and asan:
Alexander Dunaev384ba482018-03-21 17:56:121246 # Sandbox is not yet supported by ASAN for Windows.
1247 # Perhaps this is only needed for tests that use the sandbox?
1248 cmdline.append('--no-sandbox')
1249
dprankecb4a2e242016-09-19 01:13:141250 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591251
dpranked8113582015-06-05 20:08:251252 return cmdline, extra_files
1253
dpranke74559b52015-06-10 21:20:391254 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331255 return self.PathJoin(self.chromium_src_dir,
1256 self.ToSrcRelPath(build_path),
1257 *comps)
dpranked8113582015-06-05 20:08:251258
dprankeee5b51f62015-04-09 00:03:221259 def ToSrcRelPath(self, path):
1260 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501261 if path.startswith('//'):
1262 return path[2:].replace('/', self.sep)
1263 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351264
Dirk Pranke0fd41bcd2015-06-19 00:05:501265 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141266 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501267 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001268 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501269 if ret:
1270 return ret
1271
Dirk Prankef24e6b22018-03-27 20:12:301272 build_path = self.args.path
1273 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141274 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301275 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141276 gn_output_path = output_path + '.gn'
1277
dpranke7837fc362015-11-19 03:54:161278 inp = self.ReadInputJSON(['files', 'test_targets',
1279 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321280 if self.args.verbose:
1281 self.Print()
1282 self.Print('analyze input:')
1283 self.PrintJSON(inp)
1284 self.Print()
1285
dpranke76734662015-04-16 02:17:501286
dpranke7c5f614d2015-07-22 23:43:391287 # This shouldn't normally happen, but could due to unusual race conditions,
1288 # like a try job that gets scheduled before a patch lands but runs after
1289 # the patch has landed.
1290 if not inp['files']:
1291 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161292 self.WriteJSON({
1293 'status': 'No dependency',
1294 'compile_targets': [],
1295 'test_targets': [],
1296 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391297 return 0
1298
dprankecb4a2e242016-09-19 01:13:141299 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161300 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561301
dprankecb4a2e242016-09-19 01:13:141302 isolate_map = self.ReadIsolateMap()
1303 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1304 isolate_map, inp['additional_compile_targets'])
1305 if err:
1306 raise MBErr(err)
1307
1308 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1309 isolate_map, inp['test_targets'])
1310 if err:
1311 raise MBErr(err)
1312 labels_to_targets = {}
1313 for i, label in enumerate(gn_inp['test_targets']):
1314 labels_to_targets[label] = inp['test_targets'][i]
1315
dprankef61de2f2015-05-14 04:09:561316 try:
dprankecb4a2e242016-09-19 01:13:141317 self.WriteJSON(gn_inp, gn_input_path)
1318 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
1319 ret, _, _ = self.Run(cmd, force_verbose=True)
1320 if ret:
1321 return ret
dpranke067d0142015-05-14 22:52:451322
dprankecb4a2e242016-09-19 01:13:141323 gn_outp_str = self.ReadFile(gn_output_path)
1324 try:
1325 gn_outp = json.loads(gn_outp_str)
1326 except Exception as e:
1327 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1328 % (repr(gn_outp_str), str(e)))
1329 raise
1330
1331 outp = {}
1332 if 'status' in gn_outp:
1333 outp['status'] = gn_outp['status']
1334 if 'error' in gn_outp:
1335 outp['error'] = gn_outp['error']
1336 if 'invalid_targets' in gn_outp:
1337 outp['invalid_targets'] = gn_outp['invalid_targets']
1338 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491339 all_input_compile_targets = sorted(
1340 set(inp['test_targets'] + inp['additional_compile_targets']))
1341
1342 # If we're building 'all', we can throw away the rest of the targets
1343 # since they're redundant.
dpranke385a3102016-09-20 22:04:081344 if 'all' in gn_outp['compile_targets']:
1345 outp['compile_targets'] = ['all']
1346 else:
Dirk Pranke45165072017-11-08 04:57:491347 outp['compile_targets'] = gn_outp['compile_targets']
1348
1349 # crbug.com/736215: When GN returns targets back, for targets in
1350 # the default toolchain, GN will have generated a phony ninja
1351 # target matching the label, and so we can safely (and easily)
1352 # transform any GN label into the matching ninja target. For
1353 # targets in other toolchains, though, GN doesn't generate the
1354 # phony targets, and we don't know how to turn the labels into
1355 # compile targets. In this case, we also conservatively give up
1356 # and build everything. Probably the right thing to do here is
1357 # to have GN return the compile targets directly.
1358 if any("(" in target for target in outp['compile_targets']):
1359 self.Print('WARNING: targets with non-default toolchains were '
1360 'found, building everything instead.')
1361 outp['compile_targets'] = all_input_compile_targets
1362 else:
dpranke385a3102016-09-20 22:04:081363 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491364 label.replace('//', '') for label in outp['compile_targets']]
1365
1366 # Windows has a maximum command line length of 8k; even Linux
1367 # maxes out at 128k; if analyze returns a *really long* list of
1368 # targets, we just give up and conservatively build everything instead.
1369 # Probably the right thing here is for ninja to support response
1370 # files as input on the command line
1371 # (see https://github.com/ninja-build/ninja/issues/1355).
1372 if len(' '.join(outp['compile_targets'])) > 7*1024:
1373 self.Print('WARNING: Too many compile targets were affected.')
1374 self.Print('WARNING: Building everything instead to avoid '
1375 'command-line length issues.')
1376 outp['compile_targets'] = all_input_compile_targets
1377
1378
dprankecb4a2e242016-09-19 01:13:141379 if 'test_targets' in gn_outp:
1380 outp['test_targets'] = [
1381 labels_to_targets[label] for label in gn_outp['test_targets']]
1382
1383 if self.args.verbose:
1384 self.Print()
1385 self.Print('analyze output:')
1386 self.PrintJSON(outp)
1387 self.Print()
1388
1389 self.WriteJSON(outp, output_path)
1390
dprankef61de2f2015-05-14 04:09:561391 finally:
dprankecb4a2e242016-09-19 01:13:141392 if self.Exists(gn_input_path):
1393 self.RemoveFile(gn_input_path)
1394 if self.Exists(gn_output_path):
1395 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351396
1397 return 0
1398
dpranked8113582015-06-05 20:08:251399 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301400 path = self.args.input_path
1401 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351402 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321403 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351404
1405 try:
1406 inp = json.loads(self.ReadFile(path))
1407 except Exception as e:
1408 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321409 (path, e), output_path)
dpranked8113582015-06-05 20:08:251410
1411 for k in required_keys:
1412 if not k in inp:
1413 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1414 output_path)
dprankefe4602312015-04-08 16:20:351415
1416 return inp
1417
dpranked5b2b9432015-06-23 16:55:301418 def WriteFailureAndRaise(self, msg, output_path):
1419 if output_path:
dprankee0547cd2015-09-15 01:27:401420 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351421 raise MBErr(msg)
1422
dprankee0547cd2015-09-15 01:27:401423 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321424 try:
dprankee0547cd2015-09-15 01:27:401425 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1426 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321427 except Exception as e:
1428 raise MBErr('Error %s writing to the output path "%s"' %
1429 (e, path))
dprankefe4602312015-04-08 16:20:351430
aneeshmde50f472016-04-01 01:13:101431 def CheckCompile(self, master, builder):
1432 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1433 url = urllib2.quote(url_template.format(master=master, builder=builder),
1434 safe=':/()?=')
1435 try:
1436 builds = json.loads(self.Fetch(url))
1437 except Exception as e:
1438 return str(e)
1439 successes = sorted(
1440 [int(x) for x in builds.keys() if "text" in builds[x] and
1441 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1442 reverse=True)
1443 if not successes:
1444 return "no successful builds"
1445 build = builds[str(successes[0])]
1446 step_names = set([step["name"] for step in build["steps"]])
1447 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1448 if compile_indicators & step_names:
1449 return "compiles"
1450 return "does not compile"
1451
dpranke3cec199c2015-09-22 23:29:021452 def PrintCmd(self, cmd, env):
1453 if self.platform == 'win32':
1454 env_prefix = 'set '
1455 env_quoter = QuoteForSet
1456 shell_quoter = QuoteForCmd
1457 else:
1458 env_prefix = ''
1459 env_quoter = pipes.quote
1460 shell_quoter = pipes.quote
1461
1462 def print_env(var):
1463 if env and var in env:
1464 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1465
dprankeec079262016-06-07 02:21:201466 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021467
dpranke8c2cfd32015-09-17 20:12:331468 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351469 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021470 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351471
dprankecda00332015-04-11 04:18:321472 def PrintJSON(self, obj):
1473 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1474
dpranke751516a2015-10-03 01:11:341475 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301476 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381477 if self.platform == 'win32':
1478 # On Windows use the batch script since there is no exe
1479 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1480 else:
1481 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341482 if self.args.jobs:
1483 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1484 ninja_cmd.append(target)
1485 ret, _, _ = self.Run(ninja_cmd, force_verbose=False, buffer_output=False)
1486 return ret
1487
1488 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351489 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401490 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021491 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351492 if self.args.dryrun:
1493 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401494
dpranke751516a2015-10-03 01:11:341495 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401496 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341497 if ret:
1498 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351499 if out:
dprankeee5b51f62015-04-09 00:03:221500 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351501 if err:
dprankeee5b51f62015-04-09 00:03:221502 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351503 return ret, out, err
1504
dpranke751516a2015-10-03 01:11:341505 def Call(self, cmd, env=None, buffer_output=True):
1506 if buffer_output:
1507 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1508 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1509 env=env)
1510 out, err = p.communicate()
1511 else:
1512 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1513 env=env)
1514 p.wait()
1515 out = err = ''
dprankefe4602312015-04-08 16:20:351516 return p.returncode, out, err
1517
1518 def ExpandUser(self, path):
1519 # This function largely exists so it can be overridden for testing.
1520 return os.path.expanduser(path)
1521
1522 def Exists(self, path):
1523 # This function largely exists so it can be overridden for testing.
1524 return os.path.exists(path)
1525
dpranke867bcf4a2016-03-14 22:28:321526 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501527 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321528 f = urllib2.urlopen(url)
1529 contents = f.read()
1530 f.close()
1531 return contents
1532
dprankec3441d12015-06-23 23:01:351533 def MaybeMakeDirectory(self, path):
1534 try:
1535 os.makedirs(path)
1536 except OSError, e:
1537 if e.errno != errno.EEXIST:
1538 raise
1539
dpranke8c2cfd32015-09-17 20:12:331540 def PathJoin(self, *comps):
1541 # This function largely exists so it can be overriden for testing.
1542 return os.path.join(*comps)
1543
dpranke030d7a6d2016-03-26 17:23:501544 def Print(self, *args, **kwargs):
1545 # This function largely exists so it can be overridden for testing.
1546 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101547 if kwargs.get('stream', sys.stdout) == sys.stdout:
1548 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501549
dprankefe4602312015-04-08 16:20:351550 def ReadFile(self, path):
1551 # This function largely exists so it can be overriden for testing.
1552 with open(path) as fp:
1553 return fp.read()
1554
dpranke030d7a6d2016-03-26 17:23:501555 def RelPath(self, path, start='.'):
1556 # This function largely exists so it can be overriden for testing.
1557 return os.path.relpath(path, start)
1558
dprankef61de2f2015-05-14 04:09:561559 def RemoveFile(self, path):
1560 # This function largely exists so it can be overriden for testing.
1561 os.remove(path)
1562
dprankec161aa92015-09-14 20:21:131563 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331564 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131565 # In other places in chromium, we often have to retry this command
1566 # because we're worried about other processes still holding on to
1567 # file handles, but when MB is invoked, it will be early enough in the
1568 # build that their should be no other processes to interfere. We
1569 # can change this if need be.
1570 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1571 else:
1572 shutil.rmtree(abs_path, ignore_errors=True)
1573
Dirk Prankef24e6b22018-03-27 20:12:301574 def TempDir(self):
1575 # This function largely exists so it can be overriden for testing.
1576 return tempfile.mkdtemp(prefix='mb_')
1577
dprankef61de2f2015-05-14 04:09:561578 def TempFile(self, mode='w'):
1579 # This function largely exists so it can be overriden for testing.
1580 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1581
dprankee0547cd2015-09-15 01:27:401582 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351583 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401584 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301585 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351586 with open(path, 'w') as fp:
1587 return fp.write(contents)
1588
dprankef61de2f2015-05-14 04:09:561589
dprankefe4602312015-04-08 16:20:351590class MBErr(Exception):
1591 pass
1592
1593
dpranke3cec199c2015-09-22 23:29:021594# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1595# details of this next section, which handles escaping command lines
1596# so that they can be copied and pasted into a cmd window.
1597UNSAFE_FOR_SET = set('^<>&|')
1598UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1599ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1600
1601
1602def QuoteForSet(arg):
1603 if any(a in UNSAFE_FOR_SET for a in arg):
1604 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1605 return arg
1606
1607
1608def QuoteForCmd(arg):
1609 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021610 if arg == '' or ' ' in arg or '"' in arg:
1611 quote_re = re.compile(r'(\\*)"')
1612 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1613
1614 # Then check to see if the arg contains any metacharacters other than
1615 # double quotes; if it does, quote everything (including the double
1616 # quotes) for safety.
1617 if any(a in UNSAFE_FOR_CMD for a in arg):
1618 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1619 return arg
1620
1621
dprankefe4602312015-04-08 16:20:351622if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591623 sys.exit(main(sys.argv[1:]))