blob: 810c9b95e1c17368ff0b6e47c3cb903f6edd25b0 [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']
Nico Webera7bc1cb2019-06-15 17:42:391178 is_simplechrome = vals.get('cros_passthrough', False)
1179 is_mac = self.platform == 'darwin'
Nico Weberd94b71a2018-02-22 22:00:301180 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061181
kylechar39705682017-01-19 14:37:231182 # This should be true if tests with type='windowed_test_launcher' are
1183 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081184 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1185 # backends. Currently, tests are executed for the headless and X11 backends
1186 # and both can run under Xvfb.
1187 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1188 # backend.
Scott Graham3be4b4162017-09-12 00:41:411189 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251190
1191 asan = 'is_asan=true' in vals['gn_args']
1192 msan = 'is_msan=true' in vals['gn_args']
1193 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411194 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251195
dprankecb4a2e242016-09-19 01:13:141196 test_type = isolate_map[target]['type']
dprankefe0d35e2016-02-05 02:43:591197
dprankecb4a2e242016-09-19 01:13:141198 executable = isolate_map[target].get('executable', target)
bsheedy9c16ed62019-04-10 20:32:111199 executable_suffix = isolate_map[target].get(
1200 'executable_suffix', '.exe' if is_win else '')
dprankefe0d35e2016-02-05 02:43:591201
dprankea55584f12015-07-22 00:52:471202 cmdline = []
Andrii Shyshkalovc158e0102018-01-10 05:52:001203 extra_files = [
1204 '../../.vpython',
1205 '../../testing/test_env.py',
1206 ]
dpranked8113582015-06-05 20:08:251207
dprankecb4a2e242016-09-19 01:13:141208 if test_type == 'nontest':
1209 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1210 output_path=None)
1211
John Budorick93e88ac82019-04-12 18:39:111212 if test_type == 'generated_script':
Ben Pastenecb0fb412019-06-11 02:31:541213 script = isolate_map[target]['script']
1214 if self.platform == 'win32':
1215 script += '.bat'
John Budorick93e88ac82019-04-12 18:39:111216 cmdline = [
1217 '../../testing/test_env.py',
Ben Pastenecb0fb412019-06-11 02:31:541218 script,
John Budorick93e88ac82019-04-12 18:39:111219 ]
1220 elif test_type == 'fuzzer':
Roberto Carrillo1460da852018-12-14 17:10:391221 cmdline = [
1222 '../../testing/test_env.py',
1223 '../../tools/code_coverage/run_fuzz_target.py',
1224 '--fuzzer', './' + target,
1225 '--output-dir', '${ISOLATED_OUTDIR}',
1226 '--timeout', '3600']
1227 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011228 cmdline = []
1229 if asan:
John Budorick31cdce62019-04-03 20:56:111230 cmdline += [os.path.join('bin', 'run_with_asan'), '--']
John Budorick8c4203042019-03-19 17:22:011231 cmdline += [
John Budorickfb97a852017-12-20 20:10:191232 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041233 '../../build/android/test_wrapper/logdog_wrapper.py',
1234 '--target', target,
hzl9ae14452017-04-04 23:38:021235 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481236 '--store-tombstones']
Scott Graham3be4b4162017-09-12 00:41:411237 elif is_fuchsia and test_type != 'script':
John Budorickfb97a852017-12-20 20:10:191238 cmdline = [
1239 '../../testing/test_env.py',
1240 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441241 '--test-launcher-bot-mode',
John Budorickfb97a852017-12-20 20:10:191242 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321243 elif is_simplechrome and test_type != 'script':
1244 cmdline = [
1245 '../../testing/test_env.py',
1246 os.path.join('bin', 'run_%s' % target),
1247 ]
kylechar39705682017-01-19 14:37:231248 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001249 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471250 cmdline = [
Nico Webera7bc1cb2019-06-15 17:42:391251 '../../testing/xvfb.py',
1252 './' + str(executable) + executable_suffix,
1253 '--test-launcher-bot-mode',
1254 '--asan=%d' % asan,
1255 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1256 # supported.
1257 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
1258 '--lsan=%d' % (asan and not is_mac and not is_win),
1259 '--msan=%d' % msan,
1260 '--tsan=%d' % tsan,
1261 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471262 ]
1263 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471264 cmdline = [
1265 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591266 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251267 '--test-launcher-bot-mode',
1268 '--asan=%d' % asan,
Caleb Raitto1fb2cc9e2019-06-14 01:04:231269 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1270 # supported.
Nico Webera7bc1cb2019-06-15 17:42:391271 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
1272 '--lsan=%d' % (asan and not is_mac and not is_win),
dpranked8113582015-06-05 20:08:251273 '--msan=%d' % msan,
1274 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411275 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471276 ]
dpranke6abd8652015-08-28 03:21:111277 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241278 cmdline = []
1279 # If we're testing a CrOS simplechrome build, assume we need to launch a
1280 # VM first. So prepend the command to run with the VM launcher.
1281 # TODO(bpastene): Differentiate between CrOS VM and hardware tests.
1282 if is_simplechrome:
1283 cmdline = [os.path.join('bin', 'launch_cros_vm')]
1284 cmdline += [
dpranke6abd8652015-08-28 03:21:111285 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141286 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591287 ]
Dirk Prankef24e6b22018-03-27 20:12:301288 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471289 cmdline = [
1290 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591291 ]
dprankea55584f12015-07-22 00:52:471292 else:
1293 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1294 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251295
Abhishek Arya2f5f7342018-06-13 16:59:441296 if is_win and asan:
Alexander Dunaev384ba482018-03-21 17:56:121297 # Sandbox is not yet supported by ASAN for Windows.
1298 # Perhaps this is only needed for tests that use the sandbox?
1299 cmdline.append('--no-sandbox')
1300
dprankecb4a2e242016-09-19 01:13:141301 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591302
dpranked8113582015-06-05 20:08:251303 return cmdline, extra_files
1304
dpranke74559b52015-06-10 21:20:391305 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331306 return self.PathJoin(self.chromium_src_dir,
1307 self.ToSrcRelPath(build_path),
1308 *comps)
dpranked8113582015-06-05 20:08:251309
dprankeee5b51f62015-04-09 00:03:221310 def ToSrcRelPath(self, path):
1311 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501312 if path.startswith('//'):
1313 return path[2:].replace('/', self.sep)
1314 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351315
Dirk Pranke0fd41bcd2015-06-19 00:05:501316 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141317 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501318 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001319 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501320 if ret:
1321 return ret
1322
Dirk Prankef24e6b22018-03-27 20:12:301323 build_path = self.args.path
1324 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141325 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301326 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141327 gn_output_path = output_path + '.gn'
1328
dpranke7837fc362015-11-19 03:54:161329 inp = self.ReadInputJSON(['files', 'test_targets',
1330 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321331 if self.args.verbose:
1332 self.Print()
1333 self.Print('analyze input:')
1334 self.PrintJSON(inp)
1335 self.Print()
1336
dpranke76734662015-04-16 02:17:501337
dpranke7c5f614d2015-07-22 23:43:391338 # This shouldn't normally happen, but could due to unusual race conditions,
1339 # like a try job that gets scheduled before a patch lands but runs after
1340 # the patch has landed.
1341 if not inp['files']:
1342 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161343 self.WriteJSON({
1344 'status': 'No dependency',
1345 'compile_targets': [],
1346 'test_targets': [],
1347 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391348 return 0
1349
dprankecb4a2e242016-09-19 01:13:141350 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161351 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561352
dprankecb4a2e242016-09-19 01:13:141353 isolate_map = self.ReadIsolateMap()
1354 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1355 isolate_map, inp['additional_compile_targets'])
1356 if err:
1357 raise MBErr(err)
1358
1359 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1360 isolate_map, inp['test_targets'])
1361 if err:
1362 raise MBErr(err)
1363 labels_to_targets = {}
1364 for i, label in enumerate(gn_inp['test_targets']):
1365 labels_to_targets[label] = inp['test_targets'][i]
1366
dprankef61de2f2015-05-14 04:09:561367 try:
dprankecb4a2e242016-09-19 01:13:141368 self.WriteJSON(gn_inp, gn_input_path)
1369 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
1370 ret, _, _ = self.Run(cmd, force_verbose=True)
1371 if ret:
1372 return ret
dpranke067d0142015-05-14 22:52:451373
dprankecb4a2e242016-09-19 01:13:141374 gn_outp_str = self.ReadFile(gn_output_path)
1375 try:
1376 gn_outp = json.loads(gn_outp_str)
1377 except Exception as e:
1378 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1379 % (repr(gn_outp_str), str(e)))
1380 raise
1381
1382 outp = {}
1383 if 'status' in gn_outp:
1384 outp['status'] = gn_outp['status']
1385 if 'error' in gn_outp:
1386 outp['error'] = gn_outp['error']
1387 if 'invalid_targets' in gn_outp:
1388 outp['invalid_targets'] = gn_outp['invalid_targets']
1389 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491390 all_input_compile_targets = sorted(
1391 set(inp['test_targets'] + inp['additional_compile_targets']))
1392
1393 # If we're building 'all', we can throw away the rest of the targets
1394 # since they're redundant.
dpranke385a3102016-09-20 22:04:081395 if 'all' in gn_outp['compile_targets']:
1396 outp['compile_targets'] = ['all']
1397 else:
Dirk Pranke45165072017-11-08 04:57:491398 outp['compile_targets'] = gn_outp['compile_targets']
1399
1400 # crbug.com/736215: When GN returns targets back, for targets in
1401 # the default toolchain, GN will have generated a phony ninja
1402 # target matching the label, and so we can safely (and easily)
1403 # transform any GN label into the matching ninja target. For
1404 # targets in other toolchains, though, GN doesn't generate the
1405 # phony targets, and we don't know how to turn the labels into
1406 # compile targets. In this case, we also conservatively give up
1407 # and build everything. Probably the right thing to do here is
1408 # to have GN return the compile targets directly.
1409 if any("(" in target for target in outp['compile_targets']):
1410 self.Print('WARNING: targets with non-default toolchains were '
1411 'found, building everything instead.')
1412 outp['compile_targets'] = all_input_compile_targets
1413 else:
dpranke385a3102016-09-20 22:04:081414 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491415 label.replace('//', '') for label in outp['compile_targets']]
1416
1417 # Windows has a maximum command line length of 8k; even Linux
1418 # maxes out at 128k; if analyze returns a *really long* list of
1419 # targets, we just give up and conservatively build everything instead.
1420 # Probably the right thing here is for ninja to support response
1421 # files as input on the command line
1422 # (see https://github.com/ninja-build/ninja/issues/1355).
1423 if len(' '.join(outp['compile_targets'])) > 7*1024:
1424 self.Print('WARNING: Too many compile targets were affected.')
1425 self.Print('WARNING: Building everything instead to avoid '
1426 'command-line length issues.')
1427 outp['compile_targets'] = all_input_compile_targets
1428
1429
dprankecb4a2e242016-09-19 01:13:141430 if 'test_targets' in gn_outp:
1431 outp['test_targets'] = [
1432 labels_to_targets[label] for label in gn_outp['test_targets']]
1433
1434 if self.args.verbose:
1435 self.Print()
1436 self.Print('analyze output:')
1437 self.PrintJSON(outp)
1438 self.Print()
1439
1440 self.WriteJSON(outp, output_path)
1441
dprankef61de2f2015-05-14 04:09:561442 finally:
dprankecb4a2e242016-09-19 01:13:141443 if self.Exists(gn_input_path):
1444 self.RemoveFile(gn_input_path)
1445 if self.Exists(gn_output_path):
1446 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351447
1448 return 0
1449
dpranked8113582015-06-05 20:08:251450 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301451 path = self.args.input_path
1452 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351453 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321454 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351455
1456 try:
1457 inp = json.loads(self.ReadFile(path))
1458 except Exception as e:
1459 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321460 (path, e), output_path)
dpranked8113582015-06-05 20:08:251461
1462 for k in required_keys:
1463 if not k in inp:
1464 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1465 output_path)
dprankefe4602312015-04-08 16:20:351466
1467 return inp
1468
dpranked5b2b9432015-06-23 16:55:301469 def WriteFailureAndRaise(self, msg, output_path):
1470 if output_path:
dprankee0547cd2015-09-15 01:27:401471 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351472 raise MBErr(msg)
1473
dprankee0547cd2015-09-15 01:27:401474 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321475 try:
dprankee0547cd2015-09-15 01:27:401476 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1477 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321478 except Exception as e:
1479 raise MBErr('Error %s writing to the output path "%s"' %
1480 (e, path))
dprankefe4602312015-04-08 16:20:351481
aneeshmde50f472016-04-01 01:13:101482 def CheckCompile(self, master, builder):
1483 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1484 url = urllib2.quote(url_template.format(master=master, builder=builder),
1485 safe=':/()?=')
1486 try:
1487 builds = json.loads(self.Fetch(url))
1488 except Exception as e:
1489 return str(e)
1490 successes = sorted(
1491 [int(x) for x in builds.keys() if "text" in builds[x] and
1492 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1493 reverse=True)
1494 if not successes:
1495 return "no successful builds"
1496 build = builds[str(successes[0])]
1497 step_names = set([step["name"] for step in build["steps"]])
1498 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1499 if compile_indicators & step_names:
1500 return "compiles"
1501 return "does not compile"
1502
dpranke3cec199c2015-09-22 23:29:021503 def PrintCmd(self, cmd, env):
1504 if self.platform == 'win32':
1505 env_prefix = 'set '
1506 env_quoter = QuoteForSet
1507 shell_quoter = QuoteForCmd
1508 else:
1509 env_prefix = ''
1510 env_quoter = pipes.quote
1511 shell_quoter = pipes.quote
1512
1513 def print_env(var):
1514 if env and var in env:
1515 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1516
dprankeec079262016-06-07 02:21:201517 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021518
dpranke8c2cfd32015-09-17 20:12:331519 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351520 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021521 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351522
dprankecda00332015-04-11 04:18:321523 def PrintJSON(self, obj):
1524 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1525
dpranke751516a2015-10-03 01:11:341526 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301527 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381528 if self.platform == 'win32':
1529 # On Windows use the batch script since there is no exe
1530 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1531 else:
1532 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341533 if self.args.jobs:
1534 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1535 ninja_cmd.append(target)
Dirk Pranke5f22a822019-05-23 22:55:251536 ret, _, _ = self.Run(ninja_cmd, buffer_output=False)
dpranke751516a2015-10-03 01:11:341537 return ret
1538
1539 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351540 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401541 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021542 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351543 if self.args.dryrun:
1544 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401545
dpranke751516a2015-10-03 01:11:341546 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401547 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341548 if ret:
1549 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351550 if out:
dprankeee5b51f62015-04-09 00:03:221551 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351552 if err:
dprankeee5b51f62015-04-09 00:03:221553 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351554 return ret, out, err
1555
dpranke751516a2015-10-03 01:11:341556 def Call(self, cmd, env=None, buffer_output=True):
1557 if buffer_output:
1558 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1559 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1560 env=env)
1561 out, err = p.communicate()
1562 else:
1563 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1564 env=env)
1565 p.wait()
1566 out = err = ''
dprankefe4602312015-04-08 16:20:351567 return p.returncode, out, err
1568
1569 def ExpandUser(self, path):
1570 # This function largely exists so it can be overridden for testing.
1571 return os.path.expanduser(path)
1572
1573 def Exists(self, path):
1574 # This function largely exists so it can be overridden for testing.
1575 return os.path.exists(path)
1576
dpranke867bcf4a2016-03-14 22:28:321577 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501578 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321579 f = urllib2.urlopen(url)
1580 contents = f.read()
1581 f.close()
1582 return contents
1583
dprankec3441d12015-06-23 23:01:351584 def MaybeMakeDirectory(self, path):
1585 try:
1586 os.makedirs(path)
1587 except OSError, e:
1588 if e.errno != errno.EEXIST:
1589 raise
1590
dpranke8c2cfd32015-09-17 20:12:331591 def PathJoin(self, *comps):
1592 # This function largely exists so it can be overriden for testing.
1593 return os.path.join(*comps)
1594
dpranke030d7a6d2016-03-26 17:23:501595 def Print(self, *args, **kwargs):
1596 # This function largely exists so it can be overridden for testing.
1597 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101598 if kwargs.get('stream', sys.stdout) == sys.stdout:
1599 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501600
dprankefe4602312015-04-08 16:20:351601 def ReadFile(self, path):
1602 # This function largely exists so it can be overriden for testing.
1603 with open(path) as fp:
1604 return fp.read()
1605
dpranke030d7a6d2016-03-26 17:23:501606 def RelPath(self, path, start='.'):
1607 # This function largely exists so it can be overriden for testing.
1608 return os.path.relpath(path, start)
1609
dprankef61de2f2015-05-14 04:09:561610 def RemoveFile(self, path):
1611 # This function largely exists so it can be overriden for testing.
1612 os.remove(path)
1613
dprankec161aa92015-09-14 20:21:131614 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331615 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131616 # In other places in chromium, we often have to retry this command
1617 # because we're worried about other processes still holding on to
1618 # file handles, but when MB is invoked, it will be early enough in the
1619 # build that their should be no other processes to interfere. We
1620 # can change this if need be.
1621 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1622 else:
1623 shutil.rmtree(abs_path, ignore_errors=True)
1624
Dirk Prankef24e6b22018-03-27 20:12:301625 def TempDir(self):
1626 # This function largely exists so it can be overriden for testing.
1627 return tempfile.mkdtemp(prefix='mb_')
1628
dprankef61de2f2015-05-14 04:09:561629 def TempFile(self, mode='w'):
1630 # This function largely exists so it can be overriden for testing.
1631 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1632
dprankee0547cd2015-09-15 01:27:401633 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351634 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401635 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301636 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351637 with open(path, 'w') as fp:
1638 return fp.write(contents)
1639
dprankef61de2f2015-05-14 04:09:561640
dprankefe4602312015-04-08 16:20:351641class MBErr(Exception):
1642 pass
1643
1644
dpranke3cec199c2015-09-22 23:29:021645# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1646# details of this next section, which handles escaping command lines
1647# so that they can be copied and pasted into a cmd window.
1648UNSAFE_FOR_SET = set('^<>&|')
1649UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1650ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1651
1652
1653def QuoteForSet(arg):
1654 if any(a in UNSAFE_FOR_SET for a in arg):
1655 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1656 return arg
1657
1658
1659def QuoteForCmd(arg):
1660 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021661 if arg == '' or ' ' in arg or '"' in arg:
1662 quote_re = re.compile(r'(\\*)"')
1663 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1664
1665 # Then check to see if the arg contains any metacharacters other than
1666 # double quotes; if it does, quote everything (including the double
1667 # quotes) for safety.
1668 if any(a in UNSAFE_FOR_CMD for a in arg):
1669 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1670 return arg
1671
1672
dprankefe4602312015-04-08 16:20:351673if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591674 sys.exit(main(sys.argv[1:]))