blob: fcec84ebc4d613a449db72c37e8e09e2e0184010 [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)
Garrett Beatyb6cee042019-04-22 18:42:09176 subp.add_argument('--quiet', default=False, action='store_true',
177 help='Print out just the arguments, '
178 'do not emulate the output of the gen subcommand.')
179 subp.add_argument('--recursive', default=False, action='store_true',
180 help='Lookup arguments from imported files, '
181 'implies --quiet')
dprankefe4602312015-04-08 16:20:35182 subp.set_defaults(func=self.CmdLookup)
183
dpranke030d7a6d2016-03-26 17:23:50184 subp = subps.add_parser(
185 'run',
186 help='build and run the isolated version of a '
187 'binary',
188 formatter_class=argparse.RawDescriptionHelpFormatter)
189 subp.description = (
190 'Build, isolate, and run the given binary with the command line\n'
191 'listed in the isolate. You may pass extra arguments after the\n'
192 'target; use "--" if the extra arguments need to include switches.\n'
193 '\n'
194 'Examples:\n'
195 '\n'
196 ' % tools/mb/mb.py run -m chromium.linux -b "Linux Builder" \\\n'
197 ' //out/Default content_browsertests\n'
198 '\n'
199 ' % tools/mb/mb.py run out/Default content_browsertests\n'
200 '\n'
201 ' % tools/mb/mb.py run out/Default content_browsertests -- \\\n'
202 ' --test-launcher-retry-limit=0'
203 '\n'
204 )
dpranke751516a2015-10-03 01:11:34205 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30206 subp.add_argument('-j', '--jobs', type=int,
dpranke751516a2015-10-03 01:11:34207 help='Number of jobs to pass to ninja')
208 subp.add_argument('--no-build', dest='build', default=True,
209 action='store_false',
210 help='Do not build, just isolate and run')
Dirk Prankef24e6b22018-03-27 20:12:30211 subp.add_argument('path',
dpranke030d7a6d2016-03-26 17:23:50212 help=('path to generate build into (or use).'
213 ' This can be either a regular path or a '
214 'GN-style source-relative path like '
215 '//out/Default.'))
Dirk Pranke8cb6aa782017-12-16 02:31:33216 subp.add_argument('-s', '--swarmed', action='store_true',
217 help='Run under swarming with the default dimensions')
218 subp.add_argument('-d', '--dimension', default=[], action='append', nargs=2,
219 dest='dimensions', metavar='FOO bar',
220 help='dimension to filter on')
221 subp.add_argument('--no-default-dimensions', action='store_false',
222 dest='default_dimensions', default=True,
223 help='Do not automatically add dimensions to the task')
Dirk Prankef24e6b22018-03-27 20:12:30224 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34225 help='ninja target to build and run')
dpranke030d7a6d2016-03-26 17:23:50226 subp.add_argument('extra_args', nargs='*',
227 help=('extra args to pass to the isolate to run. Use '
228 '"--" as the first arg if you need to pass '
229 'switches'))
dpranke751516a2015-10-03 01:11:34230 subp.set_defaults(func=self.CmdRun)
231
dprankefe4602312015-04-08 16:20:35232 subp = subps.add_parser('validate',
233 help='validate the config file')
dprankea5a77ca2015-07-16 23:24:17234 subp.add_argument('-f', '--config-file', metavar='PATH',
235 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50236 help='path to config file (default is %(default)s)')
dprankefe4602312015-04-08 16:20:35237 subp.set_defaults(func=self.CmdValidate)
238
Dirk Prankef24e6b22018-03-27 20:12:30239 subp = subps.add_parser('zip',
240 help='generate a .zip containing the files needed '
241 'for a given binary')
242 AddCommonOptions(subp)
243 subp.add_argument('--no-build', dest='build', default=True,
244 action='store_false',
245 help='Do not build, just isolate')
246 subp.add_argument('-j', '--jobs', type=int,
247 help='Number of jobs to pass to ninja')
248 subp.add_argument('path',
249 help='path build was generated into')
250 subp.add_argument('target',
251 help='ninja target to generate the isolate for')
252 subp.add_argument('zip_path',
253 help='path to zip file to create')
254 subp.set_defaults(func=self.CmdZip)
255
dprankefe4602312015-04-08 16:20:35256 subp = subps.add_parser('help',
257 help='Get help on a subcommand.')
258 subp.add_argument(nargs='?', action='store', dest='subcommand',
259 help='The command to get help for.')
260 subp.set_defaults(func=self.CmdHelp)
261
262 self.args = parser.parse_args(argv)
263
dprankeb2be10a2016-02-22 17:11:00264 def DumpInputFiles(self):
265
dprankef7b7eb7a2016-03-28 22:42:59266 def DumpContentsOfFilePassedTo(arg_name, path):
dprankeb2be10a2016-02-22 17:11:00267 if path and self.Exists(path):
dprankef7b7eb7a2016-03-28 22:42:59268 self.Print("\n# To recreate the file passed to %s:" % arg_name)
dprankecb4a2e242016-09-19 01:13:14269 self.Print("%% cat > %s <<EOF" % path)
dprankeb2be10a2016-02-22 17:11:00270 contents = self.ReadFile(path)
dprankef7b7eb7a2016-03-28 22:42:59271 self.Print(contents)
272 self.Print("EOF\n%\n")
dprankeb2be10a2016-02-22 17:11:00273
dprankef7b7eb7a2016-03-28 22:42:59274 if getattr(self.args, 'input_path', None):
275 DumpContentsOfFilePassedTo(
Dirk Prankef24e6b22018-03-27 20:12:30276 'argv[0] (input_path)', self.args.input_path)
dprankef7b7eb7a2016-03-28 22:42:59277 if getattr(self.args, 'swarming_targets_file', None):
278 DumpContentsOfFilePassedTo(
279 '--swarming-targets-file', self.args.swarming_targets_file)
dprankeb2be10a2016-02-22 17:11:00280
dprankefe4602312015-04-08 16:20:35281 def CmdAnalyze(self):
dpranke751516a2015-10-03 01:11:34282 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11283 return self.RunGNAnalyze(vals)
dprankefe4602312015-04-08 16:20:35284
dprankef37aebb92016-09-23 01:14:49285 def CmdExport(self):
286 self.ReadConfigFile()
287 obj = {}
288 for master, builders in self.masters.items():
289 obj[master] = {}
290 for builder in builders:
291 config = self.masters[master][builder]
292 if not config:
293 continue
294
shenghuazhang804b21542016-10-11 02:06:49295 if isinstance(config, dict):
296 args = {k: self.FlattenConfig(v)['gn_args']
297 for k, v in config.items()}
dprankef37aebb92016-09-23 01:14:49298 elif config.startswith('//'):
299 args = config
300 else:
301 args = self.FlattenConfig(config)['gn_args']
302 if 'error' in args:
303 continue
304
305 obj[master][builder] = args
306
307 # Dump object and trim trailing whitespace.
308 s = '\n'.join(l.rstrip() for l in
309 json.dumps(obj, sort_keys=True, indent=2).splitlines())
310 self.Print(s)
311 return 0
312
dprankefe4602312015-04-08 16:20:35313 def CmdGen(self):
dpranke751516a2015-10-03 01:11:34314 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11315 return self.RunGNGen(vals)
dprankefe4602312015-04-08 16:20:35316
Erik Chen42df41d2018-08-21 17:13:31317 def CmdIsolateEverything(self):
318 vals = self.Lookup()
319 return self.RunGNGenAllIsolates(vals)
320
dprankefe4602312015-04-08 16:20:35321 def CmdHelp(self):
322 if self.args.subcommand:
323 self.ParseArgs([self.args.subcommand, '--help'])
324 else:
325 self.ParseArgs(['--help'])
326
dpranke751516a2015-10-03 01:11:34327 def CmdIsolate(self):
328 vals = self.GetConfig()
329 if not vals:
330 return 1
Dirk Prankef24e6b22018-03-27 20:12:30331 if self.args.build:
332 ret = self.Build(self.args.target)
333 if ret:
334 return ret
Dirk Pranked181a1a2017-12-14 01:47:11335 return self.RunGNIsolate(vals)
dpranke751516a2015-10-03 01:11:34336
337 def CmdLookup(self):
338 vals = self.Lookup()
Garrett Beatyb6cee042019-04-22 18:42:09339 gn_args = self.GNArgs(vals, expand_imports=self.args.recursive)
340 if self.args.quiet or self.args.recursive:
341 self.Print(gn_args, end='')
342 else:
343 cmd = self.GNCmd('gen', '_path_')
344 self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args)
345 env = None
dpranke751516a2015-10-03 01:11:34346
Garrett Beatyb6cee042019-04-22 18:42:09347 self.PrintCmd(cmd, env)
dpranke751516a2015-10-03 01:11:34348 return 0
349
350 def CmdRun(self):
351 vals = self.GetConfig()
352 if not vals:
353 return 1
Dirk Pranked181a1a2017-12-14 01:47:11354 if self.args.build:
Dirk Pranke5f22a822019-05-23 22:55:25355 self.Print('')
Dirk Prankef24e6b22018-03-27 20:12:30356 ret = self.Build(self.args.target)
dpranke751516a2015-10-03 01:11:34357 if ret:
358 return ret
Dirk Pranke5f22a822019-05-23 22:55:25359
360 self.Print('')
Dirk Pranked181a1a2017-12-14 01:47:11361 ret = self.RunGNIsolate(vals)
362 if ret:
363 return ret
dpranke751516a2015-10-03 01:11:34364
Dirk Pranke5f22a822019-05-23 22:55:25365 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33366 if self.args.swarmed:
Dirk Prankef24e6b22018-03-27 20:12:30367 return self._RunUnderSwarming(self.args.path, self.args.target)
Dirk Pranke8cb6aa782017-12-16 02:31:33368 else:
Dirk Prankef24e6b22018-03-27 20:12:30369 return self._RunLocallyIsolated(self.args.path, self.args.target)
370
371 def CmdZip(self):
372 ret = self.CmdIsolate()
373 if ret:
374 return ret
375
376 zip_dir = None
377 try:
378 zip_dir = self.TempDir()
379 remap_cmd = [
380 self.executable,
381 self.PathJoin(self.chromium_src_dir, 'tools', 'swarming_client',
382 'isolate.py'),
383 'remap',
Kenneth Russell2e75e2f2018-11-15 22:37:28384 '--collapse_symlinks',
Dirk Prankef24e6b22018-03-27 20:12:30385 '-s', self.PathJoin(self.args.path, self.args.target + '.isolated'),
386 '-o', zip_dir
387 ]
388 self.Run(remap_cmd)
389
390 zip_path = self.args.zip_path
391 with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as fp:
392 for root, _, files in os.walk(zip_dir):
393 for filename in files:
394 path = self.PathJoin(root, filename)
395 fp.write(path, self.RelPath(path, zip_dir))
396 finally:
397 if zip_dir:
398 self.RemoveDirectory(zip_dir)
Dirk Pranke8cb6aa782017-12-16 02:31:33399
Robert Iannucci5a9d75f62018-03-02 05:28:20400 @staticmethod
401 def _AddBaseSoftware(cmd):
402 # HACK(iannucci): These packages SHOULD NOT BE HERE.
403 # Remove method once Swarming Pool Task Templates are implemented.
404 # crbug.com/812428
405
406 # Add in required base software. This should be kept in sync with the
John Budorick9d9175372019-04-01 19:04:24407 # `chromium_swarming` recipe module in build.git. All references to
408 # `swarming_module` below are purely due to this.
Robert Iannucci5a9d75f62018-03-02 05:28:20409 cipd_packages = [
410 ('infra/python/cpython/${platform}',
411 'version:2.7.14.chromium14'),
412 ('infra/tools/luci/logdog/butler/${platform}',
413 'git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c'),
414 ('infra/tools/luci/vpython-native/${platform}',
Marc-Antoine Ruele2b07a32019-04-15 17:52:09415 'git_revision:cc09450f1c27c0034ec08b1f6d63bbc298294763'),
Robert Iannucci5a9d75f62018-03-02 05:28:20416 ('infra/tools/luci/vpython/${platform}',
Marc-Antoine Ruele2b07a32019-04-15 17:52:09417 'git_revision:cc09450f1c27c0034ec08b1f6d63bbc298294763'),
Robert Iannucci5a9d75f62018-03-02 05:28:20418 ]
419 for pkg, vers in cipd_packages:
420 cmd.append('--cipd-package=.swarming_module:%s:%s' % (pkg, vers))
421
422 # Add packages to $PATH
423 cmd.extend([
424 '--env-prefix=PATH', '.swarming_module',
425 '--env-prefix=PATH', '.swarming_module/bin',
426 ])
427
428 # Add cache directives for vpython.
429 vpython_cache_path = '.swarming_module_cache/vpython'
430 cmd.extend([
431 '--named-cache=swarming_module_cache_vpython', vpython_cache_path,
432 '--env-prefix=VPYTHON_VIRTUALENV_ROOT', vpython_cache_path,
433 ])
434
Dirk Pranke8cb6aa782017-12-16 02:31:33435 def _RunUnderSwarming(self, build_dir, target):
Marc-Antoine Ruel559cc4732019-03-19 22:20:46436 isolate_server = 'isolateserver.appspot.com'
437 namespace = 'default-gzip'
438 swarming_server = 'chromium-swarm.appspot.com'
Dirk Pranke8cb6aa782017-12-16 02:31:33439 # TODO(dpranke): Look up the information for the target in
440 # the //testing/buildbot.json file, if possible, so that we
441 # can determine the isolate target, command line, and additional
442 # swarming parameters, if possible.
443 #
444 # TODO(dpranke): Also, add support for sharding and merging results.
445 dimensions = []
446 for k, v in self._DefaultDimensions() + self.args.dimensions:
447 dimensions += ['-d', k, v]
448
449 cmd = [
450 self.executable,
451 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
452 'archive',
Marc-Antoine Ruel559cc4732019-03-19 22:20:46453 '-s', self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
454 '-I', isolate_server,
455 '--namespace', namespace,
Dirk Pranke8cb6aa782017-12-16 02:31:33456 ]
Dirk Pranke5f22a822019-05-23 22:55:25457
458 # Talking to the isolateserver may fail because we're not logged in.
459 # We trap the command explicitly and rewrite the error output so that
460 # the error message is actually correct for a Chromium check out.
461 self.PrintCmd(cmd, env=None)
462 ret, out, err = self.Run(cmd, force_verbose=False)
Dirk Pranke8cb6aa782017-12-16 02:31:33463 if ret:
Dirk Pranke5f22a822019-05-23 22:55:25464 self.Print(' -> returned %d' % ret)
465 if out:
466 self.Print(out, end='')
467 if err:
468 # The swarming client will return an exit code of 2 (via
469 # argparse.ArgumentParser.error()) and print a message to indicate
470 # that auth failed, so we have to parse the message to check.
471 if (ret == 2 and 'Please login to' in err):
472 err = err.replace(' auth.py', ' tools/swarming_client/auth.py')
473 self.Print(err, end='', file=sys.stderr)
474
Dirk Pranke8cb6aa782017-12-16 02:31:33475 return ret
476
477 isolated_hash = out.splitlines()[0].split()[0]
478 cmd = [
479 self.executable,
480 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
481 'run',
482 '-s', isolated_hash,
Marc-Antoine Ruel559cc4732019-03-19 22:20:46483 '-I', isolate_server,
484 '--namespace', namespace,
485 '-S', swarming_server,
Dirk Pranke8cb6aa782017-12-16 02:31:33486 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20487 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33488 if self.args.extra_args:
489 cmd += ['--'] + self.args.extra_args
Dirk Pranke5f22a822019-05-23 22:55:25490 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33491 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
492 return ret
493
494 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50495 cmd = [
dpranke751516a2015-10-03 01:11:34496 self.executable,
497 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
498 'run',
499 '-s',
dpranke030d7a6d2016-03-26 17:23:50500 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33501 ]
dpranke030d7a6d2016-03-26 17:23:50502 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33503 cmd += ['--'] + self.args.extra_args
504 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34505 return ret
506
Dirk Pranke8cb6aa782017-12-16 02:31:33507 def _DefaultDimensions(self):
508 if not self.args.default_dimensions:
509 return []
510
511 # This code is naive and just picks reasonable defaults per platform.
512 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40513 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33514 elif self.platform.startswith('linux'):
515 os_dim = ('os', 'Ubuntu-14.04')
516 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40517 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33518 else:
519 raise MBErr('unrecognized platform string "%s"' % self.platform)
520
521 return [('pool', 'Chrome'),
522 ('cpu', 'x86-64'),
523 os_dim]
524
dpranke0cafc162016-03-19 00:41:10525 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35526 errs = []
527
528 # Read the file to make sure it parses.
529 self.ReadConfigFile()
530
dpranke3be00142016-03-17 22:46:04531 # Build a list of all of the configs referenced by builders.
dprankefe4602312015-04-08 16:20:35532 all_configs = {}
dprankefe4602312015-04-08 16:20:35533 for master in self.masters:
dpranke3be00142016-03-17 22:46:04534 for config in self.masters[master].values():
shenghuazhang804b21542016-10-11 02:06:49535 if isinstance(config, dict):
536 for c in config.values():
dprankeb9380a12016-07-21 21:44:09537 all_configs[c] = master
538 else:
539 all_configs[config] = master
dprankefe4602312015-04-08 16:20:35540
dpranke9dd5e252016-04-14 04:23:09541 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35542 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09543 if config.startswith('//'):
544 if not self.Exists(self.ToAbsPath(config)):
545 errs.append('Unknown args file "%s" referenced from "%s".' %
546 (config, loc))
547 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35548 errs.append('Unknown config "%s" referenced from "%s".' %
549 (config, loc))
550
551 # Check that every actual config is actually referenced.
552 for config in self.configs:
553 if not config in all_configs:
554 errs.append('Unused config "%s".' % config)
555
556 # Figure out the whole list of mixins, and check that every mixin
557 # listed by a config or another mixin actually exists.
558 referenced_mixins = set()
559 for config, mixins in self.configs.items():
560 for mixin in mixins:
561 if not mixin in self.mixins:
562 errs.append('Unknown mixin "%s" referenced by config "%s".' %
563 (mixin, config))
564 referenced_mixins.add(mixin)
565
566 for mixin in self.mixins:
567 for sub_mixin in self.mixins[mixin].get('mixins', []):
568 if not sub_mixin in self.mixins:
569 errs.append('Unknown mixin "%s" referenced by mixin "%s".' %
570 (sub_mixin, mixin))
571 referenced_mixins.add(sub_mixin)
572
573 # Check that every mixin defined is actually referenced somewhere.
574 for mixin in self.mixins:
575 if not mixin in referenced_mixins:
576 errs.append('Unreferenced mixin "%s".' % mixin)
577
dpranke255085e2016-03-16 05:23:59578 # If we're checking the Chromium config, check that the 'chromium' bots
579 # which build public artifacts do not include the chrome_with_codecs mixin.
580 if self.args.config_file == self.default_config:
581 if 'chromium' in self.masters:
582 for builder in self.masters['chromium']:
583 config = self.masters['chromium'][builder]
584 def RecurseMixins(current_mixin):
585 if current_mixin == 'chrome_with_codecs':
586 errs.append('Public artifact builder "%s" can not contain the '
587 '"chrome_with_codecs" mixin.' % builder)
588 return
589 if not 'mixins' in self.mixins[current_mixin]:
590 return
591 for mixin in self.mixins[current_mixin]['mixins']:
592 RecurseMixins(mixin)
dalecurtis56fd27e2016-03-09 23:06:41593
dpranke255085e2016-03-16 05:23:59594 for mixin in self.configs[config]:
595 RecurseMixins(mixin)
596 else:
597 errs.append('Missing "chromium" master. Please update this '
598 'proprietary codecs check with the name of the master '
599 'responsible for public build artifacts.')
dalecurtis56fd27e2016-03-09 23:06:41600
dprankefe4602312015-04-08 16:20:35601 if errs:
dpranke4323c80632015-08-10 22:53:54602 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17603 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35604
dpranke0cafc162016-03-19 00:41:10605 if print_ok:
606 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35607 return 0
608
609 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30610 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34611
dprankef37aebb92016-09-23 01:14:49612 vals = self.DefaultVals()
dpranke751516a2015-10-03 01:11:34613 if self.args.builder or self.args.master or self.args.config:
614 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11615 # Re-run gn gen in order to ensure the config is consistent with the
616 # build dir.
617 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34618 return vals
619
Dirk Pranked181a1a2017-12-14 01:47:11620 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
621 'toolchain.ninja')
622 if not self.Exists(toolchain_path):
623 self.Print('Must either specify a path to an existing GN build dir '
624 'or pass in a -m/-b pair or a -c flag to specify the '
625 'configuration')
626 return {}
dpranke751516a2015-10-03 01:11:34627
Dirk Pranked181a1a2017-12-14 01:47:11628 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34629 return vals
630
dprankef37aebb92016-09-23 01:14:49631 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58632 args_contents = ""
633 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
634 if self.Exists(gn_args_path):
635 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34636 gn_args = []
637 for l in args_contents.splitlines():
638 fields = l.split(' ')
639 name = fields[0]
640 val = ' '.join(fields[2:])
641 gn_args.append('%s=%s' % (name, val))
642
dprankef37aebb92016-09-23 01:14:49643 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34644
645 def Lookup(self):
Erik Chen238f4ac2019-04-12 19:02:50646 vals = self.ReadIOSBotConfig()
647 if not vals:
648 self.ReadConfigFile()
649 config = self.ConfigFromArgs()
650 if config.startswith('//'):
651 if not self.Exists(self.ToAbsPath(config)):
652 raise MBErr('args file "%s" not found' % config)
653 vals = self.DefaultVals()
654 vals['args_file'] = config
655 else:
656 if not config in self.configs:
657 raise MBErr('Config "%s" not found in %s' %
658 (config, self.args.config_file))
659 vals = self.FlattenConfig(config)
660 return vals
661
662 def ReadIOSBotConfig(self):
663 if not self.args.master or not self.args.builder:
664 return {}
665 path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
666 self.args.master, self.args.builder + '.json')
667 if not self.Exists(path):
668 return {}
669
670 contents = json.loads(self.ReadFile(path))
671 gn_args = ' '.join(contents.get('gn_args', []))
672
673 vals = self.DefaultVals()
674 vals['gn_args'] = gn_args
dprankef37aebb92016-09-23 01:14:49675 return vals
dprankee0f486f2015-11-19 23:42:00676
dprankefe4602312015-04-08 16:20:35677 def ReadConfigFile(self):
678 if not self.Exists(self.args.config_file):
679 raise MBErr('config file not found at %s' % self.args.config_file)
680
681 try:
682 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
683 except SyntaxError as e:
684 raise MBErr('Failed to parse config file "%s": %s' %
685 (self.args.config_file, e))
686
dprankefe4602312015-04-08 16:20:35687 self.configs = contents['configs']
dprankefe4602312015-04-08 16:20:35688 self.masters = contents['masters']
689 self.mixins = contents['mixins']
dprankefe4602312015-04-08 16:20:35690
dprankecb4a2e242016-09-19 01:13:14691 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20692 if not self.args.isolate_map_files:
693 self.args.isolate_map_files = [self.default_isolate_map]
694
695 for f in self.args.isolate_map_files:
696 if not self.Exists(f):
697 raise MBErr('isolate map file not found at %s' % f)
698 isolate_maps = {}
699 for isolate_map in self.args.isolate_map_files:
700 try:
701 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
702 duplicates = set(isolate_map).intersection(isolate_maps)
703 if duplicates:
704 raise MBErr(
705 'Duplicate targets in isolate map files: %s.' %
706 ', '.join(duplicates))
707 isolate_maps.update(isolate_map)
708 except SyntaxError as e:
709 raise MBErr(
710 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
711 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14712
dprankefe4602312015-04-08 16:20:35713 def ConfigFromArgs(self):
714 if self.args.config:
715 if self.args.master or self.args.builder:
716 raise MBErr('Can not specific both -c/--config and -m/--master or '
717 '-b/--builder')
718
719 return self.args.config
720
721 if not self.args.master or not self.args.builder:
722 raise MBErr('Must specify either -c/--config or '
723 '(-m/--master and -b/--builder)')
724
725 if not self.args.master in self.masters:
726 raise MBErr('Master name "%s" not found in "%s"' %
727 (self.args.master, self.args.config_file))
728
729 if not self.args.builder in self.masters[self.args.master]:
730 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
731 (self.args.builder, self.args.master, self.args.config_file))
732
dprankeb9380a12016-07-21 21:44:09733 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49734 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09735 if self.args.phase is None:
736 raise MBErr('Must specify a build --phase for %s on %s' %
737 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49738 phase = str(self.args.phase)
739 if phase not in config:
740 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09741 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49742 return config[phase]
dprankeb9380a12016-07-21 21:44:09743
744 if self.args.phase is not None:
745 raise MBErr('Must not specify a build --phase for %s on %s' %
746 (self.args.builder, self.args.master))
747 return config
dprankefe4602312015-04-08 16:20:35748
749 def FlattenConfig(self, config):
750 mixins = self.configs[config]
dprankef37aebb92016-09-23 01:14:49751 vals = self.DefaultVals()
dprankefe4602312015-04-08 16:20:35752
753 visited = []
754 self.FlattenMixins(mixins, vals, visited)
755 return vals
756
dprankef37aebb92016-09-23 01:14:49757 def DefaultVals(self):
758 return {
759 'args_file': '',
760 'cros_passthrough': False,
761 'gn_args': '',
dprankef37aebb92016-09-23 01:14:49762 }
763
dprankefe4602312015-04-08 16:20:35764 def FlattenMixins(self, mixins, vals, visited):
765 for m in mixins:
766 if m not in self.mixins:
767 raise MBErr('Unknown mixin "%s"' % m)
dprankeee5b51f62015-04-09 00:03:22768
dprankefe4602312015-04-08 16:20:35769 visited.append(m)
770
771 mixin_vals = self.mixins[m]
dpranke73ed0d62016-04-25 19:18:34772
773 if 'cros_passthrough' in mixin_vals:
774 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
Dirk Pranke6b99f072017-04-05 00:58:30775 if 'args_file' in mixin_vals:
776 if vals['args_file']:
777 raise MBErr('args_file specified multiple times in mixins '
778 'for %s on %s' % (self.args.builder, self.args.master))
779 vals['args_file'] = mixin_vals['args_file']
dprankefe4602312015-04-08 16:20:35780 if 'gn_args' in mixin_vals:
781 if vals['gn_args']:
782 vals['gn_args'] += ' ' + mixin_vals['gn_args']
783 else:
784 vals['gn_args'] = mixin_vals['gn_args']
dpranke73ed0d62016-04-25 19:18:34785
dprankefe4602312015-04-08 16:20:35786 if 'mixins' in mixin_vals:
787 self.FlattenMixins(mixin_vals['mixins'], vals, visited)
788 return vals
789
Takuto Ikuta9dffd7e2018-09-05 01:04:00790 def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
Dirk Prankef24e6b22018-03-27 20:12:30791 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50792
Takuto Ikuta9dffd7e2018-09-05 01:04:00793 if check:
794 cmd = self.GNCmd('gen', build_dir, '--check')
795 else:
796 cmd = self.GNCmd('gen', build_dir)
dprankeeca4a782016-04-14 01:42:38797 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:09798 if compute_inputs_for_analyze:
799 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:38800
801 # Since GN hasn't run yet, the build directory may not even exist.
802 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
803
804 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:54805 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:39806
dpranke751516a2015-10-03 01:11:34807 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:39808 # We need GN to generate the list of runtime dependencies for
809 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:14810 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:39811 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:00812 path = self.args.swarming_targets_file
813 if not self.Exists(path):
814 self.WriteFailureAndRaise('"%s" does not exist' % path,
815 output_path=None)
816 contents = self.ReadFile(path)
Erik Chen42df41d2018-08-21 17:13:31817 isolate_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:00818
dprankecb4a2e242016-09-19 01:13:14819 isolate_map = self.ReadIsolateMap()
Dirk Pranke7a7e9b62019-02-17 01:46:25820 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
821 isolate_map, build_dir)
822
Erik Chen42df41d2018-08-21 17:13:31823 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:00824 if err:
Dirk Pranke7a7e9b62019-02-17 01:46:25825 raise MBErr(err)
dpranke74559b52015-06-10 21:20:39826
dpranke751516a2015-10-03 01:11:34827 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:14828 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:39829 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
830
dprankefe4602312015-04-08 16:20:35831 ret, _, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:40832 if ret:
Dirk Pranke7a7e9b62019-02-17 01:46:25833 # If `gn gen` failed, we should exit early rather than trying to
834 # generate isolates. Run() will have already logged any error output.
835 self.Print('GN gen failed: %d' % ret)
836 return ret
dpranke74559b52015-06-10 21:20:39837
Erik Chen42df41d2018-08-21 17:13:31838 if getattr(self.args, 'swarming_targets_file', None):
Dirk Pranke7a7e9b62019-02-17 01:46:25839 self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
Erik Chen42df41d2018-08-21 17:13:31840
841 return 0
842
843 def RunGNGenAllIsolates(self, vals):
844 """
845 This command generates all .isolate files.
846
847 This command assumes that "mb.py gen" has already been run, as it relies on
848 "gn ls" to fetch all gn targets. If uses that output, combined with the
849 isolate_map, to determine all isolates that can be generated for the current
850 gn configuration.
851 """
852 build_dir = self.args.path
853 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
854 force_verbose=False)
855 if ret:
856 # If `gn ls` failed, we should exit early rather than trying to
857 # generate isolates.
858 self.Print('GN ls failed: %d' % ret)
859 return ret
860
861 # Create a reverse map from isolate label to isolate dict.
862 isolate_map = self.ReadIsolateMap()
863 isolate_dict_map = {}
864 for key, isolate_dict in isolate_map.iteritems():
865 isolate_dict_map[isolate_dict['label']] = isolate_dict
866 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
867
868 runtime_deps = []
869
870 isolate_targets = []
871 # For every GN target, look up the isolate dict.
872 for line in output.splitlines():
873 target = line.strip()
874 if target in isolate_dict_map:
875 if isolate_dict_map[target]['type'] == 'additional_compile_target':
876 # By definition, additional_compile_targets are not tests, so we
877 # shouldn't generate isolates for them.
878 continue
879
880 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
881 runtime_deps.append(target)
882
Dirk Pranke7a7e9b62019-02-17 01:46:25883 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
884 isolate_map, build_dir)
885
Erik Chen42df41d2018-08-21 17:13:31886 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
887 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
888 cmd = self.GNCmd('gen', build_dir)
889 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
890 self.Run(cmd)
891
892 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
893
Dirk Pranke7a7e9b62019-02-17 01:46:25894 def RemovePossiblyStaleRuntimeDepsFiles(self, vals, targets, isolate_map,
895 build_dir):
896 # TODO(crbug.com/932700): Because `gn gen --runtime-deps-list-file`
897 # puts the runtime_deps file in different locations based on the actual
898 # type of a target, we may end up with multiple possible runtime_deps
899 # files in a given build directory, where some of the entries might be
900 # stale (since we might be reusing an existing build directory).
901 #
902 # We need to be able to get the right one reliably; you might think
903 # we can just pick the newest file, but because GN won't update timestamps
904 # if the contents of the files change, an older runtime_deps
905 # file might actually be the one we should use over a newer one (see
906 # crbug.com/932387 for a more complete explanation and example).
907 #
908 # In order to avoid this, we need to delete any possible runtime_deps
909 # files *prior* to running GN. As long as the files aren't actually
910 # needed during the build, this hopefully will not cause unnecessary
911 # build work, and so it should be safe.
912 #
913 # Ultimately, we should just make sure we get the runtime_deps files
914 # in predictable locations so we don't have this issue at all, and
915 # that's what crbug.com/932700 is for.
916 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, targets, isolate_map)
917 for rpaths in possible_rpaths.values():
918 for rpath in rpaths:
919 path = self.ToAbsPath(build_dir, rpath)
920 if self.Exists(path):
921 self.RemoveFile(path)
922
Erik Chen42df41d2018-08-21 17:13:31923 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
924 """
925 Generates isolates for a list of ninja targets.
926
927 Ninja targets are transformed to GN targets via isolate_map.
928
929 This function assumes that a previous invocation of "mb.py gen" has
930 generated runtime deps for all targets.
931 """
Dirk Pranke7a7e9b62019-02-17 01:46:25932 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, ninja_targets,
933 isolate_map)
934
935 for target, rpaths in possible_rpaths.items():
936 # TODO(crbug.com/932700): We don't know where each .runtime_deps
937 # file might be, but assuming we called
938 # RemovePossiblyStaleRuntimeDepsFiles prior to calling `gn gen`,
939 # there should only be one file.
940 found_one = False
941 path_to_use = None
942 for r in rpaths:
943 path = self.ToAbsPath(build_dir, r)
944 if self.Exists(path):
945 if found_one:
946 raise MBErr('Found more than one of %s' % ', '.join(rpaths))
947 path_to_use = path
948 found_one = True
949
950 if not found_one:
951 raise MBErr('Did not find any of %s' % ', '.join(rpaths))
952
953 command, extra_files = self.GetIsolateCommand(target, vals)
954 runtime_deps = self.ReadFile(path_to_use).splitlines()
955
956 canonical_target = target.replace(':','_').replace('/','_')
957 self.WriteIsolateFiles(build_dir, command, canonical_target, runtime_deps,
958 extra_files)
959
960 def PossibleRuntimeDepsPaths(self, vals, ninja_targets, isolate_map):
961 """Returns a map of targets to possible .runtime_deps paths.
962
963 Each ninja target maps on to a GN label, but depending on the type
964 of the GN target, `gn gen --runtime-deps-list-file` will write
965 the .runtime_deps files into different locations. Unfortunately, in
966 some cases we don't actually know which of multiple locations will
967 actually be used, so we return all plausible candidates.
968
969 The paths that are returned are relative to the build directory.
970 """
971
jbudoricke3c4f95e2016-04-28 23:17:38972 android = 'target_os="android"' in vals['gn_args']
Dirk Pranke26de05aec2019-04-03 19:18:38973 ios = 'target_os="ios"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:42974 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:30975 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Dirk Pranke7a7e9b62019-02-17 01:46:25976 possible_runtime_deps_rpaths = {}
Erik Chen42df41d2018-08-21 17:13:31977 for target in ninja_targets:
John Budorick39f14962019-04-11 23:03:20978 target_type = isolate_map[target]['type']
979 label = isolate_map[target]['label']
980 stamp_runtime_deps = 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')
Erik Chen42df41d2018-08-21 17:13:31981 # TODO(https://crbug.com/876065): 'official_tests' use
982 # type='additional_compile_target' to isolate tests. This is not the
983 # intended use for 'additional_compile_target'.
John Budorick39f14962019-04-11 23:03:20984 if (target_type == 'additional_compile_target' and
Erik Chen42df41d2018-08-21 17:13:31985 target != 'official_tests'):
986 # By definition, additional_compile_targets are not tests, so we
987 # shouldn't generate isolates for them.
Dirk Pranke7a7e9b62019-02-17 01:46:25988 raise MBErr('Cannot generate isolate for %s since it is an '
989 'additional_compile_target.' % target)
John Budorick39f14962019-04-11 23:03:20990 elif fuchsia or ios or target_type == 'generated_script':
991 # iOS and Fuchsia targets end up as groups.
992 # generated_script targets are always actions.
993 rpaths = [stamp_runtime_deps]
Erik Chen42df41d2018-08-21 17:13:31994 elif android:
jbudoricke3c4f95e2016-04-28 23:17:38995 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:02996 # will result in runtime_deps associated with the stamp file, while the
997 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:44998 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:25999 rpaths = [
dprankecb4a2e242016-09-19 01:13:141000 target + '.runtime_deps',
John Budorick39f14962019-04-11 23:03:201001 stamp_runtime_deps]
1002 elif (target_type == 'script' or
1003 target_type == 'fuzzer' or
dprankecb4a2e242016-09-19 01:13:141004 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:111005 # For script targets, the build target is usually a group,
1006 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:491007 # for the label, which lives under the obj/ directory, but it may
1008 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:441009 label = isolate_map[target]['label']
John Budorick39f14962019-04-11 23:03:201010 rpaths = [stamp_runtime_deps]
Nico Weberd94b71a2018-02-22 22:00:301011 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:251012 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:491013 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251014 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:301015 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:251016 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:521017 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251018 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:021019
Dirk Pranke7a7e9b62019-02-17 01:46:251020 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:411021
Dirk Pranke7a7e9b62019-02-17 01:46:251022 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:341023
1024 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:301025 target = self.args.target
dprankecb4a2e242016-09-19 01:13:141026 isolate_map = self.ReadIsolateMap()
1027 err, labels = self.MapTargetsToLabels(isolate_map, [target])
1028 if err:
1029 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:251030
dprankecb4a2e242016-09-19 01:13:141031 label = labels[0]
dpranke751516a2015-10-03 01:11:341032
Dirk Prankef24e6b22018-03-27 20:12:301033 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:141034 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:341035
dprankeeca4a782016-04-14 01:42:381036 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:201037 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:341038 if ret:
dpranke030d7a6d2016-03-26 17:23:501039 if out:
1040 self.Print(out)
dpranke751516a2015-10-03 01:11:341041 return ret
1042
1043 runtime_deps = out.splitlines()
1044
1045 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
1046 extra_files)
1047
1048 ret, _, _ = self.Run([
1049 self.executable,
1050 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1051 'check',
1052 '-i',
1053 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1054 '-s',
1055 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1056 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301057
dprankefe4602312015-04-08 16:20:351058 return ret
1059
dpranke751516a2015-10-03 01:11:341060 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps,
1061 extra_files):
1062 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
1063 self.WriteFile(isolate_path,
1064 pprint.pformat({
1065 'variables': {
1066 'command': command,
Dirk Pranke8edeb682019-06-11 16:24:051067 'files': sorted(set(runtime_deps + extra_files)),
dpranke751516a2015-10-03 01:11:341068 }
1069 }) + '\n')
1070
1071 self.WriteJSON(
1072 {
1073 'args': [
1074 '--isolated',
1075 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1076 '--isolate',
1077 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1078 ],
1079 'dir': self.chromium_src_dir,
1080 'version': 1,
1081 },
1082 isolate_path + 'd.gen.json',
1083 )
1084
dprankecb4a2e242016-09-19 01:13:141085 def MapTargetsToLabels(self, isolate_map, targets):
1086 labels = []
1087 err = ''
1088
dprankecb4a2e242016-09-19 01:13:141089 for target in targets:
1090 if target == 'all':
1091 labels.append(target)
1092 elif target.startswith('//'):
1093 labels.append(target)
1094 else:
1095 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421096 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141097 err += ('test target "%s" type is unknown\n' % target)
1098 else:
thakis024d6f32017-05-16 23:21:421099 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141100 else:
1101 err += ('target "%s" not found in '
1102 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1103
1104 return err, labels
1105
dprankeeca4a782016-04-14 01:42:381106 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461107 if self.platform == 'linux2':
1108 subdir, exe = 'linux64', 'gn'
1109 elif self.platform == 'darwin':
1110 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251111 elif self.platform == 'aix6':
1112 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461113 else:
1114 subdir, exe = 'win', 'gn.exe'
1115
1116 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081117 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521118
dprankecb4a2e242016-09-19 01:13:141119
Garrett Beatyb6cee042019-04-22 18:42:091120 def GNArgs(self, vals, expand_imports=False):
dpranke73ed0d62016-04-25 19:18:341121 if vals['cros_passthrough']:
1122 if not 'GN_ARGS' in os.environ:
1123 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1124 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161125 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301126 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341127 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351128 if vals['gn_args']:
1129 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341130 else:
1131 gn_args = vals['gn_args']
1132
dpranked0c138b2016-04-13 18:28:471133 if self.args.goma_dir:
1134 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381135
agrieve41d21a72016-04-14 18:02:261136 android_version_code = self.args.android_version_code
1137 if android_version_code:
1138 gn_args += ' android_default_version_code="%s"' % android_version_code
1139
1140 android_version_name = self.args.android_version_name
1141 if android_version_name:
1142 gn_args += ' android_default_version_name="%s"' % android_version_name
1143
Garrett Beatyb6cee042019-04-22 18:42:091144 args_gn_lines = []
1145 parsed_gn_args = {}
dprankeeca4a782016-04-14 01:42:381146
Ben Pastene65ccf6132018-11-08 00:47:591147 # If we're using the Simple Chrome SDK, add a comment at the top that
1148 # points to the doc. This must happen after the gn_helpers.ToGNString()
1149 # call above since gn_helpers strips comments.
1150 if vals['cros_passthrough']:
Garrett Beatyb6cee042019-04-22 18:42:091151 args_gn_lines.extend([
Ben Pastene65ccf6132018-11-08 00:47:591152 '# These args are generated via the Simple Chrome SDK. See the link',
1153 '# below for more details:',
1154 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
Garrett Beatyb6cee042019-04-22 18:42:091155 ])
Ben Pastene65ccf6132018-11-08 00:47:591156
dpranke9dd5e252016-04-14 04:23:091157 args_file = vals.get('args_file', None)
1158 if args_file:
Garrett Beatyb6cee042019-04-22 18:42:091159 if expand_imports:
1160 content = self.ReadFile(self.ToAbsPath(args_file))
1161 parsed_gn_args = gn_helpers.FromGNArgs(content)
1162 else:
1163 args_gn_lines.append('import("%s")' % args_file)
1164
1165 # Canonicalize the arg string into a sorted, newline-separated list
1166 # of key-value pairs, and de-dup the keys if need be so that only
1167 # the last instance of each arg is listed.
1168 parsed_gn_args.update(gn_helpers.FromGNArgs(gn_args))
1169 args_gn_lines.append(gn_helpers.ToGNString(parsed_gn_args))
1170
1171 return '\n'.join(args_gn_lines)
dprankefe4602312015-04-08 16:20:351172
dprankecb4a2e242016-09-19 01:13:141173 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071174 isolate_map = self.ReadIsolateMap()
1175
Scott Graham3be4b4162017-09-12 00:41:411176 is_android = 'target_os="android"' in vals['gn_args']
Scott Graham3be4b4162017-09-12 00:41:411177 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Caleb Raittof983d102019-06-21 23:05:021178 is_cros = 'target_os="chromeos"' in vals['gn_args']
Nico Webera7bc1cb2019-06-15 17:42:391179 is_simplechrome = vals.get('cros_passthrough', False)
1180 is_mac = self.platform == 'darwin'
Nico Weberd94b71a2018-02-22 22:00:301181 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061182
kylechar39705682017-01-19 14:37:231183 # This should be true if tests with type='windowed_test_launcher' are
1184 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081185 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1186 # backends. Currently, tests are executed for the headless and X11 backends
1187 # and both can run under Xvfb.
1188 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1189 # backend.
Scott Graham3be4b4162017-09-12 00:41:411190 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251191
1192 asan = 'is_asan=true' in vals['gn_args']
1193 msan = 'is_msan=true' in vals['gn_args']
1194 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411195 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251196
dprankecb4a2e242016-09-19 01:13:141197 test_type = isolate_map[target]['type']
dprankefe0d35e2016-02-05 02:43:591198
dprankecb4a2e242016-09-19 01:13:141199 executable = isolate_map[target].get('executable', target)
bsheedy9c16ed62019-04-10 20:32:111200 executable_suffix = isolate_map[target].get(
1201 'executable_suffix', '.exe' if is_win else '')
dprankefe0d35e2016-02-05 02:43:591202
dprankea55584f12015-07-22 00:52:471203 cmdline = []
Andrii Shyshkalovc158e0102018-01-10 05:52:001204 extra_files = [
1205 '../../.vpython',
1206 '../../testing/test_env.py',
1207 ]
dpranked8113582015-06-05 20:08:251208
dprankecb4a2e242016-09-19 01:13:141209 if test_type == 'nontest':
1210 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1211 output_path=None)
1212
John Budorick93e88ac82019-04-12 18:39:111213 if test_type == 'generated_script':
Ben Pastenecb0fb412019-06-11 02:31:541214 script = isolate_map[target]['script']
1215 if self.platform == 'win32':
1216 script += '.bat'
John Budorick93e88ac82019-04-12 18:39:111217 cmdline = [
1218 '../../testing/test_env.py',
Ben Pastenecb0fb412019-06-11 02:31:541219 script,
John Budorick93e88ac82019-04-12 18:39:111220 ]
1221 elif test_type == 'fuzzer':
Roberto Carrillo1460da852018-12-14 17:10:391222 cmdline = [
1223 '../../testing/test_env.py',
1224 '../../tools/code_coverage/run_fuzz_target.py',
1225 '--fuzzer', './' + target,
1226 '--output-dir', '${ISOLATED_OUTDIR}',
1227 '--timeout', '3600']
1228 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011229 cmdline = []
1230 if asan:
John Budorick31cdce62019-04-03 20:56:111231 cmdline += [os.path.join('bin', 'run_with_asan'), '--']
John Budorick8c4203042019-03-19 17:22:011232 cmdline += [
John Budorickfb97a852017-12-20 20:10:191233 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041234 '../../build/android/test_wrapper/logdog_wrapper.py',
1235 '--target', target,
hzl9ae14452017-04-04 23:38:021236 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481237 '--store-tombstones']
Scott Graham3be4b4162017-09-12 00:41:411238 elif is_fuchsia and test_type != 'script':
John Budorickfb97a852017-12-20 20:10:191239 cmdline = [
1240 '../../testing/test_env.py',
1241 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441242 '--test-launcher-bot-mode',
John Budorickfb97a852017-12-20 20:10:191243 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321244 elif is_simplechrome and test_type != 'script':
1245 cmdline = [
1246 '../../testing/test_env.py',
1247 os.path.join('bin', 'run_%s' % target),
1248 ]
kylechar39705682017-01-19 14:37:231249 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001250 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471251 cmdline = [
Nico Webera7bc1cb2019-06-15 17:42:391252 '../../testing/xvfb.py',
1253 './' + str(executable) + executable_suffix,
1254 '--test-launcher-bot-mode',
1255 '--asan=%d' % asan,
1256 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1257 # supported.
1258 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021259 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1260 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
Nico Webera7bc1cb2019-06-15 17:42:391261 '--msan=%d' % msan,
1262 '--tsan=%d' % tsan,
1263 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471264 ]
1265 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471266 cmdline = [
1267 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591268 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251269 '--test-launcher-bot-mode',
1270 '--asan=%d' % asan,
Caleb Raitto1fb2cc9e2019-06-14 01:04:231271 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1272 # supported.
Nico Webera7bc1cb2019-06-15 17:42:391273 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021274 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1275 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
dpranked8113582015-06-05 20:08:251276 '--msan=%d' % msan,
1277 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411278 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471279 ]
dpranke6abd8652015-08-28 03:21:111280 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241281 cmdline = []
1282 # If we're testing a CrOS simplechrome build, assume we need to launch a
1283 # VM first. So prepend the command to run with the VM launcher.
1284 # TODO(bpastene): Differentiate between CrOS VM and hardware tests.
1285 if is_simplechrome:
1286 cmdline = [os.path.join('bin', 'launch_cros_vm')]
1287 cmdline += [
dpranke6abd8652015-08-28 03:21:111288 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141289 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591290 ]
Dirk Prankef24e6b22018-03-27 20:12:301291 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471292 cmdline = [
1293 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591294 ]
dprankea55584f12015-07-22 00:52:471295 else:
1296 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1297 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251298
Abhishek Arya2f5f7342018-06-13 16:59:441299 if is_win and asan:
Alexander Dunaev384ba482018-03-21 17:56:121300 # Sandbox is not yet supported by ASAN for Windows.
1301 # Perhaps this is only needed for tests that use the sandbox?
1302 cmdline.append('--no-sandbox')
1303
dprankecb4a2e242016-09-19 01:13:141304 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591305
dpranked8113582015-06-05 20:08:251306 return cmdline, extra_files
1307
dpranke74559b52015-06-10 21:20:391308 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331309 return self.PathJoin(self.chromium_src_dir,
1310 self.ToSrcRelPath(build_path),
1311 *comps)
dpranked8113582015-06-05 20:08:251312
dprankeee5b51f62015-04-09 00:03:221313 def ToSrcRelPath(self, path):
1314 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501315 if path.startswith('//'):
1316 return path[2:].replace('/', self.sep)
1317 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351318
Dirk Pranke0fd41bcd2015-06-19 00:05:501319 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141320 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501321 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001322 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501323 if ret:
1324 return ret
1325
Dirk Prankef24e6b22018-03-27 20:12:301326 build_path = self.args.path
1327 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141328 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301329 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141330 gn_output_path = output_path + '.gn'
1331
dpranke7837fc362015-11-19 03:54:161332 inp = self.ReadInputJSON(['files', 'test_targets',
1333 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321334 if self.args.verbose:
1335 self.Print()
1336 self.Print('analyze input:')
1337 self.PrintJSON(inp)
1338 self.Print()
1339
dpranke76734662015-04-16 02:17:501340
dpranke7c5f614d2015-07-22 23:43:391341 # This shouldn't normally happen, but could due to unusual race conditions,
1342 # like a try job that gets scheduled before a patch lands but runs after
1343 # the patch has landed.
1344 if not inp['files']:
1345 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161346 self.WriteJSON({
1347 'status': 'No dependency',
1348 'compile_targets': [],
1349 'test_targets': [],
1350 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391351 return 0
1352
dprankecb4a2e242016-09-19 01:13:141353 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161354 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561355
dprankecb4a2e242016-09-19 01:13:141356 isolate_map = self.ReadIsolateMap()
1357 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1358 isolate_map, inp['additional_compile_targets'])
1359 if err:
1360 raise MBErr(err)
1361
1362 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1363 isolate_map, inp['test_targets'])
1364 if err:
1365 raise MBErr(err)
1366 labels_to_targets = {}
1367 for i, label in enumerate(gn_inp['test_targets']):
1368 labels_to_targets[label] = inp['test_targets'][i]
1369
dprankef61de2f2015-05-14 04:09:561370 try:
dprankecb4a2e242016-09-19 01:13:141371 self.WriteJSON(gn_inp, gn_input_path)
1372 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
1373 ret, _, _ = self.Run(cmd, force_verbose=True)
1374 if ret:
1375 return ret
dpranke067d0142015-05-14 22:52:451376
dprankecb4a2e242016-09-19 01:13:141377 gn_outp_str = self.ReadFile(gn_output_path)
1378 try:
1379 gn_outp = json.loads(gn_outp_str)
1380 except Exception as e:
1381 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1382 % (repr(gn_outp_str), str(e)))
1383 raise
1384
1385 outp = {}
1386 if 'status' in gn_outp:
1387 outp['status'] = gn_outp['status']
1388 if 'error' in gn_outp:
1389 outp['error'] = gn_outp['error']
1390 if 'invalid_targets' in gn_outp:
1391 outp['invalid_targets'] = gn_outp['invalid_targets']
1392 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491393 all_input_compile_targets = sorted(
1394 set(inp['test_targets'] + inp['additional_compile_targets']))
1395
1396 # If we're building 'all', we can throw away the rest of the targets
1397 # since they're redundant.
dpranke385a3102016-09-20 22:04:081398 if 'all' in gn_outp['compile_targets']:
1399 outp['compile_targets'] = ['all']
1400 else:
Dirk Pranke45165072017-11-08 04:57:491401 outp['compile_targets'] = gn_outp['compile_targets']
1402
1403 # crbug.com/736215: When GN returns targets back, for targets in
1404 # the default toolchain, GN will have generated a phony ninja
1405 # target matching the label, and so we can safely (and easily)
1406 # transform any GN label into the matching ninja target. For
1407 # targets in other toolchains, though, GN doesn't generate the
1408 # phony targets, and we don't know how to turn the labels into
1409 # compile targets. In this case, we also conservatively give up
1410 # and build everything. Probably the right thing to do here is
1411 # to have GN return the compile targets directly.
1412 if any("(" in target for target in outp['compile_targets']):
1413 self.Print('WARNING: targets with non-default toolchains were '
1414 'found, building everything instead.')
1415 outp['compile_targets'] = all_input_compile_targets
1416 else:
dpranke385a3102016-09-20 22:04:081417 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491418 label.replace('//', '') for label in outp['compile_targets']]
1419
1420 # Windows has a maximum command line length of 8k; even Linux
1421 # maxes out at 128k; if analyze returns a *really long* list of
1422 # targets, we just give up and conservatively build everything instead.
1423 # Probably the right thing here is for ninja to support response
1424 # files as input on the command line
1425 # (see https://github.com/ninja-build/ninja/issues/1355).
1426 if len(' '.join(outp['compile_targets'])) > 7*1024:
1427 self.Print('WARNING: Too many compile targets were affected.')
1428 self.Print('WARNING: Building everything instead to avoid '
1429 'command-line length issues.')
1430 outp['compile_targets'] = all_input_compile_targets
1431
1432
dprankecb4a2e242016-09-19 01:13:141433 if 'test_targets' in gn_outp:
1434 outp['test_targets'] = [
1435 labels_to_targets[label] for label in gn_outp['test_targets']]
1436
1437 if self.args.verbose:
1438 self.Print()
1439 self.Print('analyze output:')
1440 self.PrintJSON(outp)
1441 self.Print()
1442
1443 self.WriteJSON(outp, output_path)
1444
dprankef61de2f2015-05-14 04:09:561445 finally:
dprankecb4a2e242016-09-19 01:13:141446 if self.Exists(gn_input_path):
1447 self.RemoveFile(gn_input_path)
1448 if self.Exists(gn_output_path):
1449 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351450
1451 return 0
1452
dpranked8113582015-06-05 20:08:251453 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301454 path = self.args.input_path
1455 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351456 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321457 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351458
1459 try:
1460 inp = json.loads(self.ReadFile(path))
1461 except Exception as e:
1462 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321463 (path, e), output_path)
dpranked8113582015-06-05 20:08:251464
1465 for k in required_keys:
1466 if not k in inp:
1467 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1468 output_path)
dprankefe4602312015-04-08 16:20:351469
1470 return inp
1471
dpranked5b2b9432015-06-23 16:55:301472 def WriteFailureAndRaise(self, msg, output_path):
1473 if output_path:
dprankee0547cd2015-09-15 01:27:401474 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351475 raise MBErr(msg)
1476
dprankee0547cd2015-09-15 01:27:401477 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321478 try:
dprankee0547cd2015-09-15 01:27:401479 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1480 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321481 except Exception as e:
1482 raise MBErr('Error %s writing to the output path "%s"' %
1483 (e, path))
dprankefe4602312015-04-08 16:20:351484
aneeshmde50f472016-04-01 01:13:101485 def CheckCompile(self, master, builder):
1486 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1487 url = urllib2.quote(url_template.format(master=master, builder=builder),
1488 safe=':/()?=')
1489 try:
1490 builds = json.loads(self.Fetch(url))
1491 except Exception as e:
1492 return str(e)
1493 successes = sorted(
1494 [int(x) for x in builds.keys() if "text" in builds[x] and
1495 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1496 reverse=True)
1497 if not successes:
1498 return "no successful builds"
1499 build = builds[str(successes[0])]
1500 step_names = set([step["name"] for step in build["steps"]])
1501 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1502 if compile_indicators & step_names:
1503 return "compiles"
1504 return "does not compile"
1505
dpranke3cec199c2015-09-22 23:29:021506 def PrintCmd(self, cmd, env):
1507 if self.platform == 'win32':
1508 env_prefix = 'set '
1509 env_quoter = QuoteForSet
1510 shell_quoter = QuoteForCmd
1511 else:
1512 env_prefix = ''
1513 env_quoter = pipes.quote
1514 shell_quoter = pipes.quote
1515
1516 def print_env(var):
1517 if env and var in env:
1518 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1519
dprankeec079262016-06-07 02:21:201520 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021521
dpranke8c2cfd32015-09-17 20:12:331522 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351523 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021524 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351525
dprankecda00332015-04-11 04:18:321526 def PrintJSON(self, obj):
1527 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1528
dpranke751516a2015-10-03 01:11:341529 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301530 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381531 if self.platform == 'win32':
1532 # On Windows use the batch script since there is no exe
1533 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1534 else:
1535 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341536 if self.args.jobs:
1537 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1538 ninja_cmd.append(target)
Dirk Pranke5f22a822019-05-23 22:55:251539 ret, _, _ = self.Run(ninja_cmd, buffer_output=False)
dpranke751516a2015-10-03 01:11:341540 return ret
1541
1542 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351543 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401544 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021545 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351546 if self.args.dryrun:
1547 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401548
dpranke751516a2015-10-03 01:11:341549 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401550 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341551 if ret:
1552 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351553 if out:
dprankeee5b51f62015-04-09 00:03:221554 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351555 if err:
dprankeee5b51f62015-04-09 00:03:221556 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351557 return ret, out, err
1558
dpranke751516a2015-10-03 01:11:341559 def Call(self, cmd, env=None, buffer_output=True):
1560 if buffer_output:
1561 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1562 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1563 env=env)
1564 out, err = p.communicate()
1565 else:
1566 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1567 env=env)
1568 p.wait()
1569 out = err = ''
dprankefe4602312015-04-08 16:20:351570 return p.returncode, out, err
1571
1572 def ExpandUser(self, path):
1573 # This function largely exists so it can be overridden for testing.
1574 return os.path.expanduser(path)
1575
1576 def Exists(self, path):
1577 # This function largely exists so it can be overridden for testing.
1578 return os.path.exists(path)
1579
dpranke867bcf4a2016-03-14 22:28:321580 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501581 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321582 f = urllib2.urlopen(url)
1583 contents = f.read()
1584 f.close()
1585 return contents
1586
dprankec3441d12015-06-23 23:01:351587 def MaybeMakeDirectory(self, path):
1588 try:
1589 os.makedirs(path)
1590 except OSError, e:
1591 if e.errno != errno.EEXIST:
1592 raise
1593
dpranke8c2cfd32015-09-17 20:12:331594 def PathJoin(self, *comps):
1595 # This function largely exists so it can be overriden for testing.
1596 return os.path.join(*comps)
1597
dpranke030d7a6d2016-03-26 17:23:501598 def Print(self, *args, **kwargs):
1599 # This function largely exists so it can be overridden for testing.
1600 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101601 if kwargs.get('stream', sys.stdout) == sys.stdout:
1602 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501603
dprankefe4602312015-04-08 16:20:351604 def ReadFile(self, path):
1605 # This function largely exists so it can be overriden for testing.
1606 with open(path) as fp:
1607 return fp.read()
1608
dpranke030d7a6d2016-03-26 17:23:501609 def RelPath(self, path, start='.'):
1610 # This function largely exists so it can be overriden for testing.
1611 return os.path.relpath(path, start)
1612
dprankef61de2f2015-05-14 04:09:561613 def RemoveFile(self, path):
1614 # This function largely exists so it can be overriden for testing.
1615 os.remove(path)
1616
dprankec161aa92015-09-14 20:21:131617 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331618 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131619 # In other places in chromium, we often have to retry this command
1620 # because we're worried about other processes still holding on to
1621 # file handles, but when MB is invoked, it will be early enough in the
1622 # build that their should be no other processes to interfere. We
1623 # can change this if need be.
1624 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1625 else:
1626 shutil.rmtree(abs_path, ignore_errors=True)
1627
Dirk Prankef24e6b22018-03-27 20:12:301628 def TempDir(self):
1629 # This function largely exists so it can be overriden for testing.
1630 return tempfile.mkdtemp(prefix='mb_')
1631
dprankef61de2f2015-05-14 04:09:561632 def TempFile(self, mode='w'):
1633 # This function largely exists so it can be overriden for testing.
1634 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1635
dprankee0547cd2015-09-15 01:27:401636 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351637 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401638 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301639 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351640 with open(path, 'w') as fp:
1641 return fp.write(contents)
1642
dprankef61de2f2015-05-14 04:09:561643
dprankefe4602312015-04-08 16:20:351644class MBErr(Exception):
1645 pass
1646
1647
dpranke3cec199c2015-09-22 23:29:021648# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1649# details of this next section, which handles escaping command lines
1650# so that they can be copied and pasted into a cmd window.
1651UNSAFE_FOR_SET = set('^<>&|')
1652UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1653ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1654
1655
1656def QuoteForSet(arg):
1657 if any(a in UNSAFE_FOR_SET for a in arg):
1658 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1659 return arg
1660
1661
1662def QuoteForCmd(arg):
1663 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021664 if arg == '' or ' ' in arg or '"' in arg:
1665 quote_re = re.compile(r'(\\*)"')
1666 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1667
1668 # Then check to see if the arg contains any metacharacters other than
1669 # double quotes; if it does, quote everything (including the double
1670 # quotes) for safety.
1671 if any(a in UNSAFE_FOR_CMD for a in arg):
1672 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1673 return arg
1674
1675
dprankefe4602312015-04-08 16:20:351676if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591677 sys.exit(main(sys.argv[1:]))