blob: f1c828ae62819f357b875b0004e266599b99a481 [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',
Sergey Ulanovd851243b2019-06-25 00:33:471243 '--system-log-file', '${ISOLATED_OUTDIR}/system_log'
John Budorickfb97a852017-12-20 20:10:191244 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321245 elif is_simplechrome and test_type != 'script':
1246 cmdline = [
1247 '../../testing/test_env.py',
1248 os.path.join('bin', 'run_%s' % target),
1249 ]
kylechar39705682017-01-19 14:37:231250 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001251 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471252 cmdline = [
Nico Webera7bc1cb2019-06-15 17:42:391253 '../../testing/xvfb.py',
1254 './' + str(executable) + executable_suffix,
1255 '--test-launcher-bot-mode',
1256 '--asan=%d' % asan,
1257 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1258 # supported.
1259 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021260 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1261 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
Nico Webera7bc1cb2019-06-15 17:42:391262 '--msan=%d' % msan,
1263 '--tsan=%d' % tsan,
1264 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471265 ]
1266 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471267 cmdline = [
1268 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591269 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251270 '--test-launcher-bot-mode',
1271 '--asan=%d' % asan,
Caleb Raitto1fb2cc9e2019-06-14 01:04:231272 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1273 # supported.
Nico Webera7bc1cb2019-06-15 17:42:391274 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021275 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1276 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
dpranked8113582015-06-05 20:08:251277 '--msan=%d' % msan,
1278 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411279 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471280 ]
dpranke6abd8652015-08-28 03:21:111281 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241282 cmdline = []
1283 # If we're testing a CrOS simplechrome build, assume we need to launch a
1284 # VM first. So prepend the command to run with the VM launcher.
1285 # TODO(bpastene): Differentiate between CrOS VM and hardware tests.
1286 if is_simplechrome:
1287 cmdline = [os.path.join('bin', 'launch_cros_vm')]
1288 cmdline += [
dpranke6abd8652015-08-28 03:21:111289 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141290 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591291 ]
Dirk Prankef24e6b22018-03-27 20:12:301292 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471293 cmdline = [
1294 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591295 ]
dprankea55584f12015-07-22 00:52:471296 else:
1297 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1298 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251299
Abhishek Arya2f5f7342018-06-13 16:59:441300 if is_win and asan:
Alexander Dunaev384ba482018-03-21 17:56:121301 # Sandbox is not yet supported by ASAN for Windows.
1302 # Perhaps this is only needed for tests that use the sandbox?
1303 cmdline.append('--no-sandbox')
1304
dprankecb4a2e242016-09-19 01:13:141305 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591306
dpranked8113582015-06-05 20:08:251307 return cmdline, extra_files
1308
dpranke74559b52015-06-10 21:20:391309 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331310 return self.PathJoin(self.chromium_src_dir,
1311 self.ToSrcRelPath(build_path),
1312 *comps)
dpranked8113582015-06-05 20:08:251313
dprankeee5b51f62015-04-09 00:03:221314 def ToSrcRelPath(self, path):
1315 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501316 if path.startswith('//'):
1317 return path[2:].replace('/', self.sep)
1318 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351319
Dirk Pranke0fd41bcd2015-06-19 00:05:501320 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141321 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501322 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001323 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501324 if ret:
1325 return ret
1326
Dirk Prankef24e6b22018-03-27 20:12:301327 build_path = self.args.path
1328 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141329 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301330 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141331 gn_output_path = output_path + '.gn'
1332
dpranke7837fc362015-11-19 03:54:161333 inp = self.ReadInputJSON(['files', 'test_targets',
1334 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321335 if self.args.verbose:
1336 self.Print()
1337 self.Print('analyze input:')
1338 self.PrintJSON(inp)
1339 self.Print()
1340
dpranke76734662015-04-16 02:17:501341
dpranke7c5f614d2015-07-22 23:43:391342 # This shouldn't normally happen, but could due to unusual race conditions,
1343 # like a try job that gets scheduled before a patch lands but runs after
1344 # the patch has landed.
1345 if not inp['files']:
1346 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161347 self.WriteJSON({
1348 'status': 'No dependency',
1349 'compile_targets': [],
1350 'test_targets': [],
1351 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391352 return 0
1353
dprankecb4a2e242016-09-19 01:13:141354 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161355 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561356
dprankecb4a2e242016-09-19 01:13:141357 isolate_map = self.ReadIsolateMap()
1358 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1359 isolate_map, inp['additional_compile_targets'])
1360 if err:
1361 raise MBErr(err)
1362
1363 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1364 isolate_map, inp['test_targets'])
1365 if err:
1366 raise MBErr(err)
1367 labels_to_targets = {}
1368 for i, label in enumerate(gn_inp['test_targets']):
1369 labels_to_targets[label] = inp['test_targets'][i]
1370
dprankef61de2f2015-05-14 04:09:561371 try:
dprankecb4a2e242016-09-19 01:13:141372 self.WriteJSON(gn_inp, gn_input_path)
1373 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
1374 ret, _, _ = self.Run(cmd, force_verbose=True)
1375 if ret:
1376 return ret
dpranke067d0142015-05-14 22:52:451377
dprankecb4a2e242016-09-19 01:13:141378 gn_outp_str = self.ReadFile(gn_output_path)
1379 try:
1380 gn_outp = json.loads(gn_outp_str)
1381 except Exception as e:
1382 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1383 % (repr(gn_outp_str), str(e)))
1384 raise
1385
1386 outp = {}
1387 if 'status' in gn_outp:
1388 outp['status'] = gn_outp['status']
1389 if 'error' in gn_outp:
1390 outp['error'] = gn_outp['error']
1391 if 'invalid_targets' in gn_outp:
1392 outp['invalid_targets'] = gn_outp['invalid_targets']
1393 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491394 all_input_compile_targets = sorted(
1395 set(inp['test_targets'] + inp['additional_compile_targets']))
1396
1397 # If we're building 'all', we can throw away the rest of the targets
1398 # since they're redundant.
dpranke385a3102016-09-20 22:04:081399 if 'all' in gn_outp['compile_targets']:
1400 outp['compile_targets'] = ['all']
1401 else:
Dirk Pranke45165072017-11-08 04:57:491402 outp['compile_targets'] = gn_outp['compile_targets']
1403
1404 # crbug.com/736215: When GN returns targets back, for targets in
1405 # the default toolchain, GN will have generated a phony ninja
1406 # target matching the label, and so we can safely (and easily)
1407 # transform any GN label into the matching ninja target. For
1408 # targets in other toolchains, though, GN doesn't generate the
1409 # phony targets, and we don't know how to turn the labels into
1410 # compile targets. In this case, we also conservatively give up
1411 # and build everything. Probably the right thing to do here is
1412 # to have GN return the compile targets directly.
1413 if any("(" in target for target in outp['compile_targets']):
1414 self.Print('WARNING: targets with non-default toolchains were '
1415 'found, building everything instead.')
1416 outp['compile_targets'] = all_input_compile_targets
1417 else:
dpranke385a3102016-09-20 22:04:081418 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491419 label.replace('//', '') for label in outp['compile_targets']]
1420
1421 # Windows has a maximum command line length of 8k; even Linux
1422 # maxes out at 128k; if analyze returns a *really long* list of
1423 # targets, we just give up and conservatively build everything instead.
1424 # Probably the right thing here is for ninja to support response
1425 # files as input on the command line
1426 # (see https://github.com/ninja-build/ninja/issues/1355).
1427 if len(' '.join(outp['compile_targets'])) > 7*1024:
1428 self.Print('WARNING: Too many compile targets were affected.')
1429 self.Print('WARNING: Building everything instead to avoid '
1430 'command-line length issues.')
1431 outp['compile_targets'] = all_input_compile_targets
1432
1433
dprankecb4a2e242016-09-19 01:13:141434 if 'test_targets' in gn_outp:
1435 outp['test_targets'] = [
1436 labels_to_targets[label] for label in gn_outp['test_targets']]
1437
1438 if self.args.verbose:
1439 self.Print()
1440 self.Print('analyze output:')
1441 self.PrintJSON(outp)
1442 self.Print()
1443
1444 self.WriteJSON(outp, output_path)
1445
dprankef61de2f2015-05-14 04:09:561446 finally:
dprankecb4a2e242016-09-19 01:13:141447 if self.Exists(gn_input_path):
1448 self.RemoveFile(gn_input_path)
1449 if self.Exists(gn_output_path):
1450 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351451
1452 return 0
1453
dpranked8113582015-06-05 20:08:251454 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301455 path = self.args.input_path
1456 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351457 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321458 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351459
1460 try:
1461 inp = json.loads(self.ReadFile(path))
1462 except Exception as e:
1463 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321464 (path, e), output_path)
dpranked8113582015-06-05 20:08:251465
1466 for k in required_keys:
1467 if not k in inp:
1468 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1469 output_path)
dprankefe4602312015-04-08 16:20:351470
1471 return inp
1472
dpranked5b2b9432015-06-23 16:55:301473 def WriteFailureAndRaise(self, msg, output_path):
1474 if output_path:
dprankee0547cd2015-09-15 01:27:401475 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351476 raise MBErr(msg)
1477
dprankee0547cd2015-09-15 01:27:401478 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321479 try:
dprankee0547cd2015-09-15 01:27:401480 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1481 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321482 except Exception as e:
1483 raise MBErr('Error %s writing to the output path "%s"' %
1484 (e, path))
dprankefe4602312015-04-08 16:20:351485
aneeshmde50f472016-04-01 01:13:101486 def CheckCompile(self, master, builder):
1487 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1488 url = urllib2.quote(url_template.format(master=master, builder=builder),
1489 safe=':/()?=')
1490 try:
1491 builds = json.loads(self.Fetch(url))
1492 except Exception as e:
1493 return str(e)
1494 successes = sorted(
1495 [int(x) for x in builds.keys() if "text" in builds[x] and
1496 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1497 reverse=True)
1498 if not successes:
1499 return "no successful builds"
1500 build = builds[str(successes[0])]
1501 step_names = set([step["name"] for step in build["steps"]])
1502 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1503 if compile_indicators & step_names:
1504 return "compiles"
1505 return "does not compile"
1506
dpranke3cec199c2015-09-22 23:29:021507 def PrintCmd(self, cmd, env):
1508 if self.platform == 'win32':
1509 env_prefix = 'set '
1510 env_quoter = QuoteForSet
1511 shell_quoter = QuoteForCmd
1512 else:
1513 env_prefix = ''
1514 env_quoter = pipes.quote
1515 shell_quoter = pipes.quote
1516
1517 def print_env(var):
1518 if env and var in env:
1519 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1520
dprankeec079262016-06-07 02:21:201521 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021522
dpranke8c2cfd32015-09-17 20:12:331523 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351524 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021525 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351526
dprankecda00332015-04-11 04:18:321527 def PrintJSON(self, obj):
1528 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1529
dpranke751516a2015-10-03 01:11:341530 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301531 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381532 if self.platform == 'win32':
1533 # On Windows use the batch script since there is no exe
1534 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1535 else:
1536 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341537 if self.args.jobs:
1538 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1539 ninja_cmd.append(target)
Dirk Pranke5f22a822019-05-23 22:55:251540 ret, _, _ = self.Run(ninja_cmd, buffer_output=False)
dpranke751516a2015-10-03 01:11:341541 return ret
1542
1543 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351544 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401545 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021546 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351547 if self.args.dryrun:
1548 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401549
dpranke751516a2015-10-03 01:11:341550 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401551 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341552 if ret:
1553 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351554 if out:
dprankeee5b51f62015-04-09 00:03:221555 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351556 if err:
dprankeee5b51f62015-04-09 00:03:221557 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351558 return ret, out, err
1559
dpranke751516a2015-10-03 01:11:341560 def Call(self, cmd, env=None, buffer_output=True):
1561 if buffer_output:
1562 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1563 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1564 env=env)
1565 out, err = p.communicate()
1566 else:
1567 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1568 env=env)
1569 p.wait()
1570 out = err = ''
dprankefe4602312015-04-08 16:20:351571 return p.returncode, out, err
1572
1573 def ExpandUser(self, path):
1574 # This function largely exists so it can be overridden for testing.
1575 return os.path.expanduser(path)
1576
1577 def Exists(self, path):
1578 # This function largely exists so it can be overridden for testing.
1579 return os.path.exists(path)
1580
dpranke867bcf4a2016-03-14 22:28:321581 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501582 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321583 f = urllib2.urlopen(url)
1584 contents = f.read()
1585 f.close()
1586 return contents
1587
dprankec3441d12015-06-23 23:01:351588 def MaybeMakeDirectory(self, path):
1589 try:
1590 os.makedirs(path)
1591 except OSError, e:
1592 if e.errno != errno.EEXIST:
1593 raise
1594
dpranke8c2cfd32015-09-17 20:12:331595 def PathJoin(self, *comps):
1596 # This function largely exists so it can be overriden for testing.
1597 return os.path.join(*comps)
1598
dpranke030d7a6d2016-03-26 17:23:501599 def Print(self, *args, **kwargs):
1600 # This function largely exists so it can be overridden for testing.
1601 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101602 if kwargs.get('stream', sys.stdout) == sys.stdout:
1603 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501604
dprankefe4602312015-04-08 16:20:351605 def ReadFile(self, path):
1606 # This function largely exists so it can be overriden for testing.
1607 with open(path) as fp:
1608 return fp.read()
1609
dpranke030d7a6d2016-03-26 17:23:501610 def RelPath(self, path, start='.'):
1611 # This function largely exists so it can be overriden for testing.
1612 return os.path.relpath(path, start)
1613
dprankef61de2f2015-05-14 04:09:561614 def RemoveFile(self, path):
1615 # This function largely exists so it can be overriden for testing.
1616 os.remove(path)
1617
dprankec161aa92015-09-14 20:21:131618 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331619 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131620 # In other places in chromium, we often have to retry this command
1621 # because we're worried about other processes still holding on to
1622 # file handles, but when MB is invoked, it will be early enough in the
1623 # build that their should be no other processes to interfere. We
1624 # can change this if need be.
1625 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1626 else:
1627 shutil.rmtree(abs_path, ignore_errors=True)
1628
Dirk Prankef24e6b22018-03-27 20:12:301629 def TempDir(self):
1630 # This function largely exists so it can be overriden for testing.
1631 return tempfile.mkdtemp(prefix='mb_')
1632
dprankef61de2f2015-05-14 04:09:561633 def TempFile(self, mode='w'):
1634 # This function largely exists so it can be overriden for testing.
1635 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1636
dprankee0547cd2015-09-15 01:27:401637 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351638 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401639 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301640 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351641 with open(path, 'w') as fp:
1642 return fp.write(contents)
1643
dprankef61de2f2015-05-14 04:09:561644
dprankefe4602312015-04-08 16:20:351645class MBErr(Exception):
1646 pass
1647
1648
dpranke3cec199c2015-09-22 23:29:021649# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1650# details of this next section, which handles escaping command lines
1651# so that they can be copied and pasted into a cmd window.
1652UNSAFE_FOR_SET = set('^<>&|')
1653UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1654ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1655
1656
1657def QuoteForSet(arg):
1658 if any(a in UNSAFE_FOR_SET for a in arg):
1659 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1660 return arg
1661
1662
1663def QuoteForCmd(arg):
1664 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021665 if arg == '' or ' ' in arg or '"' in arg:
1666 quote_re = re.compile(r'(\\*)"')
1667 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1668
1669 # Then check to see if the arg contains any metacharacters other than
1670 # double quotes; if it does, quote everything (including the double
1671 # quotes) for safety.
1672 if any(a in UNSAFE_FOR_CMD for a in arg):
1673 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1674 return arg
1675
1676
dprankefe4602312015-04-08 16:20:351677if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591678 sys.exit(main(sys.argv[1:]))