blob: e436d03f443e59b73b13e9bb1b02b32db8ace4b5 [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)
Erik Chen42df41d2018-08-21 17:13:31957 elif android:
jbudoricke3c4f95e2016-04-28 23:17:38958 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:02959 # will result in runtime_deps associated with the stamp file, while the
960 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:44961 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:25962 rpaths = [
dprankecb4a2e242016-09-19 01:13:14963 target + '.runtime_deps',
dpranke48ccf8f2016-03-28 23:58:28964 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
Dirk Pranke26de05aec2019-04-03 19:18:38965 elif ios or fuchsia:
966 # iOS and Fuchsia targets end up as groups.
Abhishek Arya2f5f7342018-06-13 16:59:44967 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:25968 rpaths = ['obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
dprankecb4a2e242016-09-19 01:13:14969 elif (isolate_map[target]['type'] == 'script' or
Roberto Carrillo1460da852018-12-14 17:10:39970 isolate_map[target]['type'] == 'fuzzer' or
dprankecb4a2e242016-09-19 01:13:14971 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:11972 # For script targets, the build target is usually a group,
973 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:49974 # for the label, which lives under the obj/ directory, but it may
975 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:44976 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:25977 rpaths = ['obj/%s.stamp.runtime_deps' % label.replace(':', '/')]
Nico Weberd94b71a2018-02-22 22:00:30978 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:25979 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:49980 else:
Dirk Pranke7a7e9b62019-02-17 01:46:25981 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:30982 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:25983 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:52984 else:
Dirk Pranke7a7e9b62019-02-17 01:46:25985 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:02986
Dirk Pranke7a7e9b62019-02-17 01:46:25987 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:41988
Dirk Pranke7a7e9b62019-02-17 01:46:25989 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:34990
991 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:30992 target = self.args.target
dprankecb4a2e242016-09-19 01:13:14993 isolate_map = self.ReadIsolateMap()
994 err, labels = self.MapTargetsToLabels(isolate_map, [target])
995 if err:
996 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:25997
dprankecb4a2e242016-09-19 01:13:14998 label = labels[0]
dpranke751516a2015-10-03 01:11:34999
Dirk Prankef24e6b22018-03-27 20:12:301000 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:141001 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:341002
dprankeeca4a782016-04-14 01:42:381003 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:201004 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:341005 if ret:
dpranke030d7a6d2016-03-26 17:23:501006 if out:
1007 self.Print(out)
dpranke751516a2015-10-03 01:11:341008 return ret
1009
1010 runtime_deps = out.splitlines()
1011
1012 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
1013 extra_files)
1014
1015 ret, _, _ = self.Run([
1016 self.executable,
1017 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1018 'check',
1019 '-i',
1020 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1021 '-s',
1022 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1023 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301024
dprankefe4602312015-04-08 16:20:351025 return ret
1026
dpranke751516a2015-10-03 01:11:341027 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps,
1028 extra_files):
1029 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
1030 self.WriteFile(isolate_path,
1031 pprint.pformat({
1032 'variables': {
1033 'command': command,
1034 'files': sorted(runtime_deps + extra_files),
1035 }
1036 }) + '\n')
1037
1038 self.WriteJSON(
1039 {
1040 'args': [
1041 '--isolated',
1042 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1043 '--isolate',
1044 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1045 ],
1046 'dir': self.chromium_src_dir,
1047 'version': 1,
1048 },
1049 isolate_path + 'd.gen.json',
1050 )
1051
dprankecb4a2e242016-09-19 01:13:141052 def MapTargetsToLabels(self, isolate_map, targets):
1053 labels = []
1054 err = ''
1055
dprankecb4a2e242016-09-19 01:13:141056 for target in targets:
1057 if target == 'all':
1058 labels.append(target)
1059 elif target.startswith('//'):
1060 labels.append(target)
1061 else:
1062 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421063 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141064 err += ('test target "%s" type is unknown\n' % target)
1065 else:
thakis024d6f32017-05-16 23:21:421066 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141067 else:
1068 err += ('target "%s" not found in '
1069 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1070
1071 return err, labels
1072
dprankeeca4a782016-04-14 01:42:381073 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461074 if self.platform == 'linux2':
1075 subdir, exe = 'linux64', 'gn'
1076 elif self.platform == 'darwin':
1077 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251078 elif self.platform == 'aix6':
1079 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461080 else:
1081 subdir, exe = 'win', 'gn.exe'
1082
1083 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081084 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521085
dprankecb4a2e242016-09-19 01:13:141086
dprankeeca4a782016-04-14 01:42:381087 def GNArgs(self, vals):
dpranke73ed0d62016-04-25 19:18:341088 if vals['cros_passthrough']:
1089 if not 'GN_ARGS' in os.environ:
1090 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1091 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161092 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301093 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341094 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351095 if vals['gn_args']:
1096 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341097 else:
1098 gn_args = vals['gn_args']
1099
dpranked0c138b2016-04-13 18:28:471100 if self.args.goma_dir:
1101 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381102
agrieve41d21a72016-04-14 18:02:261103 android_version_code = self.args.android_version_code
1104 if android_version_code:
1105 gn_args += ' android_default_version_code="%s"' % android_version_code
1106
1107 android_version_name = self.args.android_version_name
1108 if android_version_name:
1109 gn_args += ' android_default_version_name="%s"' % android_version_name
1110
dprankeeca4a782016-04-14 01:42:381111 # Canonicalize the arg string into a sorted, newline-separated list
1112 # of key-value pairs, and de-dup the keys if need be so that only
1113 # the last instance of each arg is listed.
1114 gn_args = gn_helpers.ToGNString(gn_helpers.FromGNArgs(gn_args))
1115
Ben Pastene65ccf6132018-11-08 00:47:591116 # If we're using the Simple Chrome SDK, add a comment at the top that
1117 # points to the doc. This must happen after the gn_helpers.ToGNString()
1118 # call above since gn_helpers strips comments.
1119 if vals['cros_passthrough']:
1120 simplechrome_comment = [
1121 '# These args are generated via the Simple Chrome SDK. See the link',
1122 '# below for more details:',
1123 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
1124 ]
1125 gn_args = '%s\n%s' % ('\n'.join(simplechrome_comment), gn_args)
1126
dpranke9dd5e252016-04-14 04:23:091127 args_file = vals.get('args_file', None)
1128 if args_file:
1129 gn_args = ('import("%s")\n' % vals['args_file']) + gn_args
dprankeeca4a782016-04-14 01:42:381130 return gn_args
dprankefe4602312015-04-08 16:20:351131
dprankecb4a2e242016-09-19 01:13:141132 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071133 isolate_map = self.ReadIsolateMap()
1134
Scott Graham3be4b4162017-09-12 00:41:411135 is_android = 'target_os="android"' in vals['gn_args']
Benjamin Pastene3bce864e2018-04-14 01:16:321136 is_simplechrome = vals.get('cros_passthrough', False)
Scott Graham3be4b4162017-09-12 00:41:411137 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:301138 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061139
kylechar39705682017-01-19 14:37:231140 # This should be true if tests with type='windowed_test_launcher' are
1141 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081142 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1143 # backends. Currently, tests are executed for the headless and X11 backends
1144 # and both can run under Xvfb.
1145 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1146 # backend.
Scott Graham3be4b4162017-09-12 00:41:411147 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251148
1149 asan = 'is_asan=true' in vals['gn_args']
1150 msan = 'is_msan=true' in vals['gn_args']
1151 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411152 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251153
dprankecb4a2e242016-09-19 01:13:141154 test_type = isolate_map[target]['type']
dprankefe0d35e2016-02-05 02:43:591155
dprankecb4a2e242016-09-19 01:13:141156 executable = isolate_map[target].get('executable', target)
Nico Weberd94b71a2018-02-22 22:00:301157 executable_suffix = '.exe' if is_win else ''
dprankefe0d35e2016-02-05 02:43:591158
dprankea55584f12015-07-22 00:52:471159 cmdline = []
Andrii Shyshkalovc158e0102018-01-10 05:52:001160 extra_files = [
1161 '../../.vpython',
1162 '../../testing/test_env.py',
1163 ]
dpranked8113582015-06-05 20:08:251164
dprankecb4a2e242016-09-19 01:13:141165 if test_type == 'nontest':
1166 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1167 output_path=None)
1168
Roberto Carrillo1460da852018-12-14 17:10:391169 if test_type == 'fuzzer':
1170 cmdline = [
1171 '../../testing/test_env.py',
1172 '../../tools/code_coverage/run_fuzz_target.py',
1173 '--fuzzer', './' + target,
1174 '--output-dir', '${ISOLATED_OUTDIR}',
1175 '--timeout', '3600']
1176 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011177 cmdline = []
1178 if asan:
1179 cmdline += [os.path.join('bin', 'run_with_asan')]
1180 cmdline += [
John Budorickfb97a852017-12-20 20:10:191181 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041182 '../../build/android/test_wrapper/logdog_wrapper.py',
1183 '--target', target,
hzl9ae14452017-04-04 23:38:021184 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481185 '--store-tombstones']
Scott Graham3be4b4162017-09-12 00:41:411186 elif is_fuchsia and test_type != 'script':
John Budorickfb97a852017-12-20 20:10:191187 cmdline = [
1188 '../../testing/test_env.py',
1189 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441190 '--test-launcher-bot-mode',
John Budorickfb97a852017-12-20 20:10:191191 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321192 elif is_simplechrome and test_type != 'script':
1193 cmdline = [
1194 '../../testing/test_env.py',
1195 os.path.join('bin', 'run_%s' % target),
1196 ]
kylechar39705682017-01-19 14:37:231197 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001198 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471199 cmdline = [
dprankefe0d35e2016-02-05 02:43:591200 '../../testing/xvfb.py',
dprankefe0d35e2016-02-05 02:43:591201 './' + str(executable) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591202 '--test-launcher-bot-mode',
1203 '--asan=%d' % asan,
1204 '--msan=%d' % msan,
1205 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411206 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471207 ]
1208 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471209 cmdline = [
1210 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591211 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251212 '--test-launcher-bot-mode',
1213 '--asan=%d' % asan,
1214 '--msan=%d' % msan,
1215 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411216 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471217 ]
dpranke6abd8652015-08-28 03:21:111218 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241219 cmdline = []
1220 # If we're testing a CrOS simplechrome build, assume we need to launch a
1221 # VM first. So prepend the command to run with the VM launcher.
1222 # TODO(bpastene): Differentiate between CrOS VM and hardware tests.
1223 if is_simplechrome:
1224 cmdline = [os.path.join('bin', 'launch_cros_vm')]
1225 cmdline += [
dpranke6abd8652015-08-28 03:21:111226 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141227 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591228 ]
Dirk Prankef24e6b22018-03-27 20:12:301229 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471230 cmdline = [
1231 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591232 ]
dprankea55584f12015-07-22 00:52:471233 else:
1234 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1235 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251236
Abhishek Arya2f5f7342018-06-13 16:59:441237 if is_win and asan:
Alexander Dunaev384ba482018-03-21 17:56:121238 # Sandbox is not yet supported by ASAN for Windows.
1239 # Perhaps this is only needed for tests that use the sandbox?
1240 cmdline.append('--no-sandbox')
1241
dprankecb4a2e242016-09-19 01:13:141242 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591243
dpranked8113582015-06-05 20:08:251244 return cmdline, extra_files
1245
dpranke74559b52015-06-10 21:20:391246 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331247 return self.PathJoin(self.chromium_src_dir,
1248 self.ToSrcRelPath(build_path),
1249 *comps)
dpranked8113582015-06-05 20:08:251250
dprankeee5b51f62015-04-09 00:03:221251 def ToSrcRelPath(self, path):
1252 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501253 if path.startswith('//'):
1254 return path[2:].replace('/', self.sep)
1255 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351256
Dirk Pranke0fd41bcd2015-06-19 00:05:501257 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141258 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501259 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001260 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501261 if ret:
1262 return ret
1263
Dirk Prankef24e6b22018-03-27 20:12:301264 build_path = self.args.path
1265 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141266 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301267 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141268 gn_output_path = output_path + '.gn'
1269
dpranke7837fc362015-11-19 03:54:161270 inp = self.ReadInputJSON(['files', 'test_targets',
1271 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321272 if self.args.verbose:
1273 self.Print()
1274 self.Print('analyze input:')
1275 self.PrintJSON(inp)
1276 self.Print()
1277
dpranke76734662015-04-16 02:17:501278
dpranke7c5f614d2015-07-22 23:43:391279 # This shouldn't normally happen, but could due to unusual race conditions,
1280 # like a try job that gets scheduled before a patch lands but runs after
1281 # the patch has landed.
1282 if not inp['files']:
1283 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161284 self.WriteJSON({
1285 'status': 'No dependency',
1286 'compile_targets': [],
1287 'test_targets': [],
1288 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391289 return 0
1290
dprankecb4a2e242016-09-19 01:13:141291 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161292 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561293
dprankecb4a2e242016-09-19 01:13:141294 isolate_map = self.ReadIsolateMap()
1295 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1296 isolate_map, inp['additional_compile_targets'])
1297 if err:
1298 raise MBErr(err)
1299
1300 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1301 isolate_map, inp['test_targets'])
1302 if err:
1303 raise MBErr(err)
1304 labels_to_targets = {}
1305 for i, label in enumerate(gn_inp['test_targets']):
1306 labels_to_targets[label] = inp['test_targets'][i]
1307
dprankef61de2f2015-05-14 04:09:561308 try:
dprankecb4a2e242016-09-19 01:13:141309 self.WriteJSON(gn_inp, gn_input_path)
1310 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
1311 ret, _, _ = self.Run(cmd, force_verbose=True)
1312 if ret:
1313 return ret
dpranke067d0142015-05-14 22:52:451314
dprankecb4a2e242016-09-19 01:13:141315 gn_outp_str = self.ReadFile(gn_output_path)
1316 try:
1317 gn_outp = json.loads(gn_outp_str)
1318 except Exception as e:
1319 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1320 % (repr(gn_outp_str), str(e)))
1321 raise
1322
1323 outp = {}
1324 if 'status' in gn_outp:
1325 outp['status'] = gn_outp['status']
1326 if 'error' in gn_outp:
1327 outp['error'] = gn_outp['error']
1328 if 'invalid_targets' in gn_outp:
1329 outp['invalid_targets'] = gn_outp['invalid_targets']
1330 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491331 all_input_compile_targets = sorted(
1332 set(inp['test_targets'] + inp['additional_compile_targets']))
1333
1334 # If we're building 'all', we can throw away the rest of the targets
1335 # since they're redundant.
dpranke385a3102016-09-20 22:04:081336 if 'all' in gn_outp['compile_targets']:
1337 outp['compile_targets'] = ['all']
1338 else:
Dirk Pranke45165072017-11-08 04:57:491339 outp['compile_targets'] = gn_outp['compile_targets']
1340
1341 # crbug.com/736215: When GN returns targets back, for targets in
1342 # the default toolchain, GN will have generated a phony ninja
1343 # target matching the label, and so we can safely (and easily)
1344 # transform any GN label into the matching ninja target. For
1345 # targets in other toolchains, though, GN doesn't generate the
1346 # phony targets, and we don't know how to turn the labels into
1347 # compile targets. In this case, we also conservatively give up
1348 # and build everything. Probably the right thing to do here is
1349 # to have GN return the compile targets directly.
1350 if any("(" in target for target in outp['compile_targets']):
1351 self.Print('WARNING: targets with non-default toolchains were '
1352 'found, building everything instead.')
1353 outp['compile_targets'] = all_input_compile_targets
1354 else:
dpranke385a3102016-09-20 22:04:081355 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491356 label.replace('//', '') for label in outp['compile_targets']]
1357
1358 # Windows has a maximum command line length of 8k; even Linux
1359 # maxes out at 128k; if analyze returns a *really long* list of
1360 # targets, we just give up and conservatively build everything instead.
1361 # Probably the right thing here is for ninja to support response
1362 # files as input on the command line
1363 # (see https://github.com/ninja-build/ninja/issues/1355).
1364 if len(' '.join(outp['compile_targets'])) > 7*1024:
1365 self.Print('WARNING: Too many compile targets were affected.')
1366 self.Print('WARNING: Building everything instead to avoid '
1367 'command-line length issues.')
1368 outp['compile_targets'] = all_input_compile_targets
1369
1370
dprankecb4a2e242016-09-19 01:13:141371 if 'test_targets' in gn_outp:
1372 outp['test_targets'] = [
1373 labels_to_targets[label] for label in gn_outp['test_targets']]
1374
1375 if self.args.verbose:
1376 self.Print()
1377 self.Print('analyze output:')
1378 self.PrintJSON(outp)
1379 self.Print()
1380
1381 self.WriteJSON(outp, output_path)
1382
dprankef61de2f2015-05-14 04:09:561383 finally:
dprankecb4a2e242016-09-19 01:13:141384 if self.Exists(gn_input_path):
1385 self.RemoveFile(gn_input_path)
1386 if self.Exists(gn_output_path):
1387 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351388
1389 return 0
1390
dpranked8113582015-06-05 20:08:251391 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301392 path = self.args.input_path
1393 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351394 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321395 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351396
1397 try:
1398 inp = json.loads(self.ReadFile(path))
1399 except Exception as e:
1400 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321401 (path, e), output_path)
dpranked8113582015-06-05 20:08:251402
1403 for k in required_keys:
1404 if not k in inp:
1405 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1406 output_path)
dprankefe4602312015-04-08 16:20:351407
1408 return inp
1409
dpranked5b2b9432015-06-23 16:55:301410 def WriteFailureAndRaise(self, msg, output_path):
1411 if output_path:
dprankee0547cd2015-09-15 01:27:401412 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351413 raise MBErr(msg)
1414
dprankee0547cd2015-09-15 01:27:401415 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321416 try:
dprankee0547cd2015-09-15 01:27:401417 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1418 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321419 except Exception as e:
1420 raise MBErr('Error %s writing to the output path "%s"' %
1421 (e, path))
dprankefe4602312015-04-08 16:20:351422
aneeshmde50f472016-04-01 01:13:101423 def CheckCompile(self, master, builder):
1424 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1425 url = urllib2.quote(url_template.format(master=master, builder=builder),
1426 safe=':/()?=')
1427 try:
1428 builds = json.loads(self.Fetch(url))
1429 except Exception as e:
1430 return str(e)
1431 successes = sorted(
1432 [int(x) for x in builds.keys() if "text" in builds[x] and
1433 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1434 reverse=True)
1435 if not successes:
1436 return "no successful builds"
1437 build = builds[str(successes[0])]
1438 step_names = set([step["name"] for step in build["steps"]])
1439 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1440 if compile_indicators & step_names:
1441 return "compiles"
1442 return "does not compile"
1443
dpranke3cec199c2015-09-22 23:29:021444 def PrintCmd(self, cmd, env):
1445 if self.platform == 'win32':
1446 env_prefix = 'set '
1447 env_quoter = QuoteForSet
1448 shell_quoter = QuoteForCmd
1449 else:
1450 env_prefix = ''
1451 env_quoter = pipes.quote
1452 shell_quoter = pipes.quote
1453
1454 def print_env(var):
1455 if env and var in env:
1456 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1457
dprankeec079262016-06-07 02:21:201458 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021459
dpranke8c2cfd32015-09-17 20:12:331460 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351461 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021462 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351463
dprankecda00332015-04-11 04:18:321464 def PrintJSON(self, obj):
1465 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1466
dpranke751516a2015-10-03 01:11:341467 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301468 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381469 if self.platform == 'win32':
1470 # On Windows use the batch script since there is no exe
1471 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1472 else:
1473 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341474 if self.args.jobs:
1475 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1476 ninja_cmd.append(target)
1477 ret, _, _ = self.Run(ninja_cmd, force_verbose=False, buffer_output=False)
1478 return ret
1479
1480 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351481 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401482 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021483 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351484 if self.args.dryrun:
1485 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401486
dpranke751516a2015-10-03 01:11:341487 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401488 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341489 if ret:
1490 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351491 if out:
dprankeee5b51f62015-04-09 00:03:221492 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351493 if err:
dprankeee5b51f62015-04-09 00:03:221494 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351495 return ret, out, err
1496
dpranke751516a2015-10-03 01:11:341497 def Call(self, cmd, env=None, buffer_output=True):
1498 if buffer_output:
1499 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1500 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1501 env=env)
1502 out, err = p.communicate()
1503 else:
1504 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1505 env=env)
1506 p.wait()
1507 out = err = ''
dprankefe4602312015-04-08 16:20:351508 return p.returncode, out, err
1509
1510 def ExpandUser(self, path):
1511 # This function largely exists so it can be overridden for testing.
1512 return os.path.expanduser(path)
1513
1514 def Exists(self, path):
1515 # This function largely exists so it can be overridden for testing.
1516 return os.path.exists(path)
1517
dpranke867bcf4a2016-03-14 22:28:321518 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501519 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321520 f = urllib2.urlopen(url)
1521 contents = f.read()
1522 f.close()
1523 return contents
1524
dprankec3441d12015-06-23 23:01:351525 def MaybeMakeDirectory(self, path):
1526 try:
1527 os.makedirs(path)
1528 except OSError, e:
1529 if e.errno != errno.EEXIST:
1530 raise
1531
dpranke8c2cfd32015-09-17 20:12:331532 def PathJoin(self, *comps):
1533 # This function largely exists so it can be overriden for testing.
1534 return os.path.join(*comps)
1535
dpranke030d7a6d2016-03-26 17:23:501536 def Print(self, *args, **kwargs):
1537 # This function largely exists so it can be overridden for testing.
1538 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101539 if kwargs.get('stream', sys.stdout) == sys.stdout:
1540 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501541
dprankefe4602312015-04-08 16:20:351542 def ReadFile(self, path):
1543 # This function largely exists so it can be overriden for testing.
1544 with open(path) as fp:
1545 return fp.read()
1546
dpranke030d7a6d2016-03-26 17:23:501547 def RelPath(self, path, start='.'):
1548 # This function largely exists so it can be overriden for testing.
1549 return os.path.relpath(path, start)
1550
dprankef61de2f2015-05-14 04:09:561551 def RemoveFile(self, path):
1552 # This function largely exists so it can be overriden for testing.
1553 os.remove(path)
1554
dprankec161aa92015-09-14 20:21:131555 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331556 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131557 # In other places in chromium, we often have to retry this command
1558 # because we're worried about other processes still holding on to
1559 # file handles, but when MB is invoked, it will be early enough in the
1560 # build that their should be no other processes to interfere. We
1561 # can change this if need be.
1562 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1563 else:
1564 shutil.rmtree(abs_path, ignore_errors=True)
1565
Dirk Prankef24e6b22018-03-27 20:12:301566 def TempDir(self):
1567 # This function largely exists so it can be overriden for testing.
1568 return tempfile.mkdtemp(prefix='mb_')
1569
dprankef61de2f2015-05-14 04:09:561570 def TempFile(self, mode='w'):
1571 # This function largely exists so it can be overriden for testing.
1572 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1573
dprankee0547cd2015-09-15 01:27:401574 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351575 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401576 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301577 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351578 with open(path, 'w') as fp:
1579 return fp.write(contents)
1580
dprankef61de2f2015-05-14 04:09:561581
dprankefe4602312015-04-08 16:20:351582class MBErr(Exception):
1583 pass
1584
1585
dpranke3cec199c2015-09-22 23:29:021586# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1587# details of this next section, which handles escaping command lines
1588# so that they can be copied and pasted into a cmd window.
1589UNSAFE_FOR_SET = set('^<>&|')
1590UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1591ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1592
1593
1594def QuoteForSet(arg):
1595 if any(a in UNSAFE_FOR_SET for a in arg):
1596 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1597 return arg
1598
1599
1600def QuoteForCmd(arg):
1601 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021602 if arg == '' or ' ' in arg or '"' in arg:
1603 quote_re = re.compile(r'(\\*)"')
1604 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1605
1606 # Then check to see if the arg contains any metacharacters other than
1607 # double quotes; if it does, quote everything (including the double
1608 # quotes) for safety.
1609 if any(a in UNSAFE_FOR_CMD for a in arg):
1610 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1611 return arg
1612
1613
dprankefe4602312015-04-08 16:20:351614if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591615 sys.exit(main(sys.argv[1:]))