blob: db31cb4caef4c635bba0ac5df9b4a782acc10a80 [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 = {}
57 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.')
Debrian Figueroaae582232019-07-17 01:54:45126 subp.add_argument('--json_output',
127 help='Write errors to json.output')
dprankefe4602312015-04-08 16:20:35128 subp.set_defaults(func=self.CmdAnalyze)
129
dprankef37aebb92016-09-23 01:14:49130 subp = subps.add_parser('export',
131 help='print out the expanded configuration for'
132 'each builder as a JSON object')
133 subp.add_argument('-f', '--config-file', metavar='PATH',
134 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50135 help='path to config file (default is %(default)s)')
dprankef37aebb92016-09-23 01:14:49136 subp.add_argument('-g', '--goma-dir',
137 help='path to goma directory')
138 subp.set_defaults(func=self.CmdExport)
139
dprankefe4602312015-04-08 16:20:35140 subp = subps.add_parser('gen',
141 help='generate a new set of build files')
142 AddCommonOptions(subp)
dpranke74559b52015-06-10 21:20:39143 subp.add_argument('--swarming-targets-file',
144 help='save runtime dependencies for targets listed '
145 'in file.')
Debrian Figueroaae582232019-07-17 01:54:45146 subp.add_argument('--json_output',
147 help='Write errors to json.output')
Dirk Prankef24e6b22018-03-27 20:12:30148 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35149 help='path to generate build into')
150 subp.set_defaults(func=self.CmdGen)
151
Erik Chen42df41d2018-08-21 17:13:31152 subp = subps.add_parser('isolate-everything',
153 help='generates a .isolate for all targets. '
154 'Requires that mb.py gen has already been '
155 'run.')
156 AddCommonOptions(subp)
157 subp.set_defaults(func=self.CmdIsolateEverything)
158 subp.add_argument('path',
159 help='path build was generated into')
160
dpranke751516a2015-10-03 01:11:34161 subp = subps.add_parser('isolate',
162 help='generate the .isolate files for a given'
163 'binary')
164 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30165 subp.add_argument('--no-build', dest='build', default=True,
166 action='store_false',
167 help='Do not build, just isolate')
168 subp.add_argument('-j', '--jobs', type=int,
169 help='Number of jobs to pass to ninja')
170 subp.add_argument('path',
dpranke751516a2015-10-03 01:11:34171 help='path build was generated into')
Dirk Prankef24e6b22018-03-27 20:12:30172 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34173 help='ninja target to generate the isolate for')
174 subp.set_defaults(func=self.CmdIsolate)
175
dprankefe4602312015-04-08 16:20:35176 subp = subps.add_parser('lookup',
177 help='look up the command for a given config or '
178 'builder')
179 AddCommonOptions(subp)
Garrett Beatyb6cee042019-04-22 18:42:09180 subp.add_argument('--quiet', default=False, action='store_true',
181 help='Print out just the arguments, '
182 'do not emulate the output of the gen subcommand.')
183 subp.add_argument('--recursive', default=False, action='store_true',
184 help='Lookup arguments from imported files, '
185 'implies --quiet')
dprankefe4602312015-04-08 16:20:35186 subp.set_defaults(func=self.CmdLookup)
187
dpranke030d7a6d2016-03-26 17:23:50188 subp = subps.add_parser(
189 'run',
190 help='build and run the isolated version of a '
191 'binary',
192 formatter_class=argparse.RawDescriptionHelpFormatter)
193 subp.description = (
194 'Build, isolate, and run the given binary with the command line\n'
195 'listed in the isolate. You may pass extra arguments after the\n'
196 'target; use "--" if the extra arguments need to include switches.\n'
197 '\n'
198 'Examples:\n'
199 '\n'
200 ' % tools/mb/mb.py run -m chromium.linux -b "Linux Builder" \\\n'
201 ' //out/Default content_browsertests\n'
202 '\n'
203 ' % tools/mb/mb.py run out/Default content_browsertests\n'
204 '\n'
205 ' % tools/mb/mb.py run out/Default content_browsertests -- \\\n'
206 ' --test-launcher-retry-limit=0'
207 '\n'
208 )
dpranke751516a2015-10-03 01:11:34209 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30210 subp.add_argument('-j', '--jobs', type=int,
dpranke751516a2015-10-03 01:11:34211 help='Number of jobs to pass to ninja')
212 subp.add_argument('--no-build', dest='build', default=True,
213 action='store_false',
214 help='Do not build, just isolate and run')
Dirk Prankef24e6b22018-03-27 20:12:30215 subp.add_argument('path',
dpranke030d7a6d2016-03-26 17:23:50216 help=('path to generate build into (or use).'
217 ' This can be either a regular path or a '
218 'GN-style source-relative path like '
219 '//out/Default.'))
Dirk Pranke8cb6aa782017-12-16 02:31:33220 subp.add_argument('-s', '--swarmed', action='store_true',
221 help='Run under swarming with the default dimensions')
222 subp.add_argument('-d', '--dimension', default=[], action='append', nargs=2,
223 dest='dimensions', metavar='FOO bar',
224 help='dimension to filter on')
225 subp.add_argument('--no-default-dimensions', action='store_false',
226 dest='default_dimensions', default=True,
227 help='Do not automatically add dimensions to the task')
Dirk Prankef24e6b22018-03-27 20:12:30228 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34229 help='ninja target to build and run')
dpranke030d7a6d2016-03-26 17:23:50230 subp.add_argument('extra_args', nargs='*',
231 help=('extra args to pass to the isolate to run. Use '
232 '"--" as the first arg if you need to pass '
233 'switches'))
dpranke751516a2015-10-03 01:11:34234 subp.set_defaults(func=self.CmdRun)
235
dprankefe4602312015-04-08 16:20:35236 subp = subps.add_parser('validate',
237 help='validate the config file')
dprankea5a77ca2015-07-16 23:24:17238 subp.add_argument('-f', '--config-file', metavar='PATH',
239 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50240 help='path to config file (default is %(default)s)')
dprankefe4602312015-04-08 16:20:35241 subp.set_defaults(func=self.CmdValidate)
242
Dirk Prankef24e6b22018-03-27 20:12:30243 subp = subps.add_parser('zip',
244 help='generate a .zip containing the files needed '
245 'for a given binary')
246 AddCommonOptions(subp)
247 subp.add_argument('--no-build', dest='build', default=True,
248 action='store_false',
249 help='Do not build, just isolate')
250 subp.add_argument('-j', '--jobs', type=int,
251 help='Number of jobs to pass to ninja')
252 subp.add_argument('path',
253 help='path build was generated into')
254 subp.add_argument('target',
255 help='ninja target to generate the isolate for')
256 subp.add_argument('zip_path',
257 help='path to zip file to create')
258 subp.set_defaults(func=self.CmdZip)
259
dprankefe4602312015-04-08 16:20:35260 subp = subps.add_parser('help',
261 help='Get help on a subcommand.')
262 subp.add_argument(nargs='?', action='store', dest='subcommand',
263 help='The command to get help for.')
264 subp.set_defaults(func=self.CmdHelp)
265
266 self.args = parser.parse_args(argv)
267
dprankeb2be10a2016-02-22 17:11:00268 def DumpInputFiles(self):
269
dprankef7b7eb7a2016-03-28 22:42:59270 def DumpContentsOfFilePassedTo(arg_name, path):
dprankeb2be10a2016-02-22 17:11:00271 if path and self.Exists(path):
dprankef7b7eb7a2016-03-28 22:42:59272 self.Print("\n# To recreate the file passed to %s:" % arg_name)
dprankecb4a2e242016-09-19 01:13:14273 self.Print("%% cat > %s <<EOF" % path)
dprankeb2be10a2016-02-22 17:11:00274 contents = self.ReadFile(path)
dprankef7b7eb7a2016-03-28 22:42:59275 self.Print(contents)
276 self.Print("EOF\n%\n")
dprankeb2be10a2016-02-22 17:11:00277
dprankef7b7eb7a2016-03-28 22:42:59278 if getattr(self.args, 'input_path', None):
279 DumpContentsOfFilePassedTo(
Dirk Prankef24e6b22018-03-27 20:12:30280 'argv[0] (input_path)', self.args.input_path)
dprankef7b7eb7a2016-03-28 22:42:59281 if getattr(self.args, 'swarming_targets_file', None):
282 DumpContentsOfFilePassedTo(
283 '--swarming-targets-file', self.args.swarming_targets_file)
dprankeb2be10a2016-02-22 17:11:00284
dprankefe4602312015-04-08 16:20:35285 def CmdAnalyze(self):
dpranke751516a2015-10-03 01:11:34286 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11287 return self.RunGNAnalyze(vals)
dprankefe4602312015-04-08 16:20:35288
dprankef37aebb92016-09-23 01:14:49289 def CmdExport(self):
290 self.ReadConfigFile()
291 obj = {}
292 for master, builders in self.masters.items():
293 obj[master] = {}
294 for builder in builders:
295 config = self.masters[master][builder]
296 if not config:
297 continue
298
shenghuazhang804b21542016-10-11 02:06:49299 if isinstance(config, dict):
300 args = {k: self.FlattenConfig(v)['gn_args']
301 for k, v in config.items()}
dprankef37aebb92016-09-23 01:14:49302 elif config.startswith('//'):
303 args = config
304 else:
305 args = self.FlattenConfig(config)['gn_args']
306 if 'error' in args:
307 continue
308
309 obj[master][builder] = args
310
311 # Dump object and trim trailing whitespace.
312 s = '\n'.join(l.rstrip() for l in
313 json.dumps(obj, sort_keys=True, indent=2).splitlines())
314 self.Print(s)
315 return 0
316
dprankefe4602312015-04-08 16:20:35317 def CmdGen(self):
dpranke751516a2015-10-03 01:11:34318 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11319 return self.RunGNGen(vals)
dprankefe4602312015-04-08 16:20:35320
Erik Chen42df41d2018-08-21 17:13:31321 def CmdIsolateEverything(self):
322 vals = self.Lookup()
323 return self.RunGNGenAllIsolates(vals)
324
dprankefe4602312015-04-08 16:20:35325 def CmdHelp(self):
326 if self.args.subcommand:
327 self.ParseArgs([self.args.subcommand, '--help'])
328 else:
329 self.ParseArgs(['--help'])
330
dpranke751516a2015-10-03 01:11:34331 def CmdIsolate(self):
332 vals = self.GetConfig()
333 if not vals:
334 return 1
Dirk Prankef24e6b22018-03-27 20:12:30335 if self.args.build:
336 ret = self.Build(self.args.target)
337 if ret:
338 return ret
Dirk Pranked181a1a2017-12-14 01:47:11339 return self.RunGNIsolate(vals)
dpranke751516a2015-10-03 01:11:34340
341 def CmdLookup(self):
342 vals = self.Lookup()
Garrett Beatyb6cee042019-04-22 18:42:09343 gn_args = self.GNArgs(vals, expand_imports=self.args.recursive)
344 if self.args.quiet or self.args.recursive:
345 self.Print(gn_args, end='')
346 else:
347 cmd = self.GNCmd('gen', '_path_')
348 self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args)
349 env = None
dpranke751516a2015-10-03 01:11:34350
Garrett Beatyb6cee042019-04-22 18:42:09351 self.PrintCmd(cmd, env)
dpranke751516a2015-10-03 01:11:34352 return 0
353
354 def CmdRun(self):
355 vals = self.GetConfig()
356 if not vals:
357 return 1
Dirk Pranked181a1a2017-12-14 01:47:11358 if self.args.build:
Dirk Pranke5f22a822019-05-23 22:55:25359 self.Print('')
Dirk Prankef24e6b22018-03-27 20:12:30360 ret = self.Build(self.args.target)
dpranke751516a2015-10-03 01:11:34361 if ret:
362 return ret
Dirk Pranke5f22a822019-05-23 22:55:25363
364 self.Print('')
Dirk Pranked181a1a2017-12-14 01:47:11365 ret = self.RunGNIsolate(vals)
366 if ret:
367 return ret
dpranke751516a2015-10-03 01:11:34368
Dirk Pranke5f22a822019-05-23 22:55:25369 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33370 if self.args.swarmed:
Dirk Prankef24e6b22018-03-27 20:12:30371 return self._RunUnderSwarming(self.args.path, self.args.target)
Dirk Pranke8cb6aa782017-12-16 02:31:33372 else:
Dirk Prankef24e6b22018-03-27 20:12:30373 return self._RunLocallyIsolated(self.args.path, self.args.target)
374
375 def CmdZip(self):
376 ret = self.CmdIsolate()
377 if ret:
378 return ret
379
380 zip_dir = None
381 try:
382 zip_dir = self.TempDir()
383 remap_cmd = [
384 self.executable,
385 self.PathJoin(self.chromium_src_dir, 'tools', 'swarming_client',
386 'isolate.py'),
387 'remap',
Kenneth Russell2e75e2f2018-11-15 22:37:28388 '--collapse_symlinks',
Dirk Prankef24e6b22018-03-27 20:12:30389 '-s', self.PathJoin(self.args.path, self.args.target + '.isolated'),
390 '-o', zip_dir
391 ]
392 self.Run(remap_cmd)
393
394 zip_path = self.args.zip_path
395 with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as fp:
396 for root, _, files in os.walk(zip_dir):
397 for filename in files:
398 path = self.PathJoin(root, filename)
399 fp.write(path, self.RelPath(path, zip_dir))
400 finally:
401 if zip_dir:
402 self.RemoveDirectory(zip_dir)
Dirk Pranke8cb6aa782017-12-16 02:31:33403
Robert Iannucci5a9d75f62018-03-02 05:28:20404 @staticmethod
405 def _AddBaseSoftware(cmd):
406 # HACK(iannucci): These packages SHOULD NOT BE HERE.
407 # Remove method once Swarming Pool Task Templates are implemented.
408 # crbug.com/812428
409
410 # Add in required base software. This should be kept in sync with the
John Budorick9d9175372019-04-01 19:04:24411 # `chromium_swarming` recipe module in build.git. All references to
412 # `swarming_module` below are purely due to this.
Robert Iannucci5a9d75f62018-03-02 05:28:20413 cipd_packages = [
414 ('infra/python/cpython/${platform}',
smut22dcd68e2019-06-25 23:33:27415 'version:2.7.15.chromium14'),
Robert Iannucci5a9d75f62018-03-02 05:28:20416 ('infra/tools/luci/logdog/butler/${platform}',
417 'git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c'),
418 ('infra/tools/luci/vpython-native/${platform}',
Marc-Antoine Ruele2b07a32019-04-15 17:52:09419 'git_revision:cc09450f1c27c0034ec08b1f6d63bbc298294763'),
Robert Iannucci5a9d75f62018-03-02 05:28:20420 ('infra/tools/luci/vpython/${platform}',
Marc-Antoine Ruele2b07a32019-04-15 17:52:09421 'git_revision:cc09450f1c27c0034ec08b1f6d63bbc298294763'),
Robert Iannucci5a9d75f62018-03-02 05:28:20422 ]
423 for pkg, vers in cipd_packages:
424 cmd.append('--cipd-package=.swarming_module:%s:%s' % (pkg, vers))
425
426 # Add packages to $PATH
427 cmd.extend([
428 '--env-prefix=PATH', '.swarming_module',
429 '--env-prefix=PATH', '.swarming_module/bin',
430 ])
431
432 # Add cache directives for vpython.
433 vpython_cache_path = '.swarming_module_cache/vpython'
434 cmd.extend([
435 '--named-cache=swarming_module_cache_vpython', vpython_cache_path,
436 '--env-prefix=VPYTHON_VIRTUALENV_ROOT', vpython_cache_path,
437 ])
438
Dirk Pranke8cb6aa782017-12-16 02:31:33439 def _RunUnderSwarming(self, build_dir, target):
Marc-Antoine Ruel559cc4732019-03-19 22:20:46440 isolate_server = 'isolateserver.appspot.com'
441 namespace = 'default-gzip'
442 swarming_server = 'chromium-swarm.appspot.com'
Dirk Pranke8cb6aa782017-12-16 02:31:33443 # TODO(dpranke): Look up the information for the target in
444 # the //testing/buildbot.json file, if possible, so that we
445 # can determine the isolate target, command line, and additional
446 # swarming parameters, if possible.
447 #
448 # TODO(dpranke): Also, add support for sharding and merging results.
449 dimensions = []
450 for k, v in self._DefaultDimensions() + self.args.dimensions:
451 dimensions += ['-d', k, v]
452
453 cmd = [
454 self.executable,
455 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
456 'archive',
Marc-Antoine Ruel559cc4732019-03-19 22:20:46457 '-s', self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
458 '-I', isolate_server,
459 '--namespace', namespace,
Dirk Pranke8cb6aa782017-12-16 02:31:33460 ]
Dirk Pranke5f22a822019-05-23 22:55:25461
462 # Talking to the isolateserver may fail because we're not logged in.
463 # We trap the command explicitly and rewrite the error output so that
464 # the error message is actually correct for a Chromium check out.
465 self.PrintCmd(cmd, env=None)
466 ret, out, err = self.Run(cmd, force_verbose=False)
Dirk Pranke8cb6aa782017-12-16 02:31:33467 if ret:
Dirk Pranke5f22a822019-05-23 22:55:25468 self.Print(' -> returned %d' % ret)
469 if out:
470 self.Print(out, end='')
471 if err:
472 # The swarming client will return an exit code of 2 (via
473 # argparse.ArgumentParser.error()) and print a message to indicate
474 # that auth failed, so we have to parse the message to check.
475 if (ret == 2 and 'Please login to' in err):
476 err = err.replace(' auth.py', ' tools/swarming_client/auth.py')
477 self.Print(err, end='', file=sys.stderr)
478
Dirk Pranke8cb6aa782017-12-16 02:31:33479 return ret
480
481 isolated_hash = out.splitlines()[0].split()[0]
482 cmd = [
483 self.executable,
484 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
485 'run',
486 '-s', isolated_hash,
Marc-Antoine Ruel559cc4732019-03-19 22:20:46487 '-I', isolate_server,
488 '--namespace', namespace,
489 '-S', swarming_server,
Dirk Pranke8cb6aa782017-12-16 02:31:33490 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20491 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33492 if self.args.extra_args:
493 cmd += ['--'] + self.args.extra_args
Dirk Pranke5f22a822019-05-23 22:55:25494 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33495 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
496 return ret
497
498 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50499 cmd = [
dpranke751516a2015-10-03 01:11:34500 self.executable,
501 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
502 'run',
503 '-s',
dpranke030d7a6d2016-03-26 17:23:50504 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33505 ]
dpranke030d7a6d2016-03-26 17:23:50506 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33507 cmd += ['--'] + self.args.extra_args
508 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34509 return ret
510
Dirk Pranke8cb6aa782017-12-16 02:31:33511 def _DefaultDimensions(self):
512 if not self.args.default_dimensions:
513 return []
514
515 # This code is naive and just picks reasonable defaults per platform.
516 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40517 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33518 elif self.platform.startswith('linux'):
519 os_dim = ('os', 'Ubuntu-14.04')
520 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40521 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33522 else:
523 raise MBErr('unrecognized platform string "%s"' % self.platform)
524
525 return [('pool', 'Chrome'),
526 ('cpu', 'x86-64'),
527 os_dim]
528
dpranke0cafc162016-03-19 00:41:10529 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35530 errs = []
531
532 # Read the file to make sure it parses.
533 self.ReadConfigFile()
534
dpranke3be00142016-03-17 22:46:04535 # Build a list of all of the configs referenced by builders.
dprankefe4602312015-04-08 16:20:35536 all_configs = {}
dprankefe4602312015-04-08 16:20:35537 for master in self.masters:
dpranke3be00142016-03-17 22:46:04538 for config in self.masters[master].values():
shenghuazhang804b21542016-10-11 02:06:49539 if isinstance(config, dict):
540 for c in config.values():
dprankeb9380a12016-07-21 21:44:09541 all_configs[c] = master
542 else:
543 all_configs[config] = master
dprankefe4602312015-04-08 16:20:35544
dpranke9dd5e252016-04-14 04:23:09545 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35546 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09547 if config.startswith('//'):
548 if not self.Exists(self.ToAbsPath(config)):
549 errs.append('Unknown args file "%s" referenced from "%s".' %
550 (config, loc))
551 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35552 errs.append('Unknown config "%s" referenced from "%s".' %
553 (config, loc))
554
555 # Check that every actual config is actually referenced.
556 for config in self.configs:
557 if not config in all_configs:
558 errs.append('Unused config "%s".' % config)
559
560 # Figure out the whole list of mixins, and check that every mixin
561 # listed by a config or another mixin actually exists.
562 referenced_mixins = set()
563 for config, mixins in self.configs.items():
564 for mixin in mixins:
565 if not mixin in self.mixins:
566 errs.append('Unknown mixin "%s" referenced by config "%s".' %
567 (mixin, config))
568 referenced_mixins.add(mixin)
569
570 for mixin in self.mixins:
571 for sub_mixin in self.mixins[mixin].get('mixins', []):
572 if not sub_mixin in self.mixins:
573 errs.append('Unknown mixin "%s" referenced by mixin "%s".' %
574 (sub_mixin, mixin))
575 referenced_mixins.add(sub_mixin)
576
577 # Check that every mixin defined is actually referenced somewhere.
578 for mixin in self.mixins:
579 if not mixin in referenced_mixins:
580 errs.append('Unreferenced mixin "%s".' % mixin)
581
dpranke255085e2016-03-16 05:23:59582 # If we're checking the Chromium config, check that the 'chromium' bots
583 # which build public artifacts do not include the chrome_with_codecs mixin.
584 if self.args.config_file == self.default_config:
585 if 'chromium' in self.masters:
586 for builder in self.masters['chromium']:
587 config = self.masters['chromium'][builder]
588 def RecurseMixins(current_mixin):
589 if current_mixin == 'chrome_with_codecs':
590 errs.append('Public artifact builder "%s" can not contain the '
591 '"chrome_with_codecs" mixin.' % builder)
592 return
593 if not 'mixins' in self.mixins[current_mixin]:
594 return
595 for mixin in self.mixins[current_mixin]['mixins']:
596 RecurseMixins(mixin)
dalecurtis56fd27e2016-03-09 23:06:41597
dpranke255085e2016-03-16 05:23:59598 for mixin in self.configs[config]:
599 RecurseMixins(mixin)
600 else:
601 errs.append('Missing "chromium" master. Please update this '
602 'proprietary codecs check with the name of the master '
603 'responsible for public build artifacts.')
dalecurtis56fd27e2016-03-09 23:06:41604
dprankefe4602312015-04-08 16:20:35605 if errs:
dpranke4323c80632015-08-10 22:53:54606 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17607 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35608
dpranke0cafc162016-03-19 00:41:10609 if print_ok:
610 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35611 return 0
612
613 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30614 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34615
dprankef37aebb92016-09-23 01:14:49616 vals = self.DefaultVals()
dpranke751516a2015-10-03 01:11:34617 if self.args.builder or self.args.master or self.args.config:
618 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11619 # Re-run gn gen in order to ensure the config is consistent with the
620 # build dir.
621 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34622 return vals
623
Dirk Pranked181a1a2017-12-14 01:47:11624 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
625 'toolchain.ninja')
626 if not self.Exists(toolchain_path):
627 self.Print('Must either specify a path to an existing GN build dir '
628 'or pass in a -m/-b pair or a -c flag to specify the '
629 'configuration')
630 return {}
dpranke751516a2015-10-03 01:11:34631
Dirk Pranked181a1a2017-12-14 01:47:11632 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34633 return vals
634
dprankef37aebb92016-09-23 01:14:49635 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58636 args_contents = ""
637 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
638 if self.Exists(gn_args_path):
639 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34640 gn_args = []
641 for l in args_contents.splitlines():
642 fields = l.split(' ')
643 name = fields[0]
644 val = ' '.join(fields[2:])
645 gn_args.append('%s=%s' % (name, val))
646
dprankef37aebb92016-09-23 01:14:49647 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34648
649 def Lookup(self):
Erik Chen238f4ac2019-04-12 19:02:50650 vals = self.ReadIOSBotConfig()
651 if not vals:
652 self.ReadConfigFile()
653 config = self.ConfigFromArgs()
654 if config.startswith('//'):
655 if not self.Exists(self.ToAbsPath(config)):
656 raise MBErr('args file "%s" not found' % config)
657 vals = self.DefaultVals()
658 vals['args_file'] = config
659 else:
660 if not config in self.configs:
661 raise MBErr('Config "%s" not found in %s' %
662 (config, self.args.config_file))
663 vals = self.FlattenConfig(config)
664 return vals
665
666 def ReadIOSBotConfig(self):
667 if not self.args.master or not self.args.builder:
668 return {}
669 path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
670 self.args.master, self.args.builder + '.json')
671 if not self.Exists(path):
672 return {}
673
674 contents = json.loads(self.ReadFile(path))
675 gn_args = ' '.join(contents.get('gn_args', []))
676
677 vals = self.DefaultVals()
678 vals['gn_args'] = gn_args
dprankef37aebb92016-09-23 01:14:49679 return vals
dprankee0f486f2015-11-19 23:42:00680
dprankefe4602312015-04-08 16:20:35681 def ReadConfigFile(self):
682 if not self.Exists(self.args.config_file):
683 raise MBErr('config file not found at %s' % self.args.config_file)
684
685 try:
686 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
687 except SyntaxError as e:
688 raise MBErr('Failed to parse config file "%s": %s' %
689 (self.args.config_file, e))
690
dprankefe4602312015-04-08 16:20:35691 self.configs = contents['configs']
692 self.masters = contents['masters']
693 self.mixins = contents['mixins']
dprankefe4602312015-04-08 16:20:35694
dprankecb4a2e242016-09-19 01:13:14695 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20696 if not self.args.isolate_map_files:
697 self.args.isolate_map_files = [self.default_isolate_map]
698
699 for f in self.args.isolate_map_files:
700 if not self.Exists(f):
701 raise MBErr('isolate map file not found at %s' % f)
702 isolate_maps = {}
703 for isolate_map in self.args.isolate_map_files:
704 try:
705 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
706 duplicates = set(isolate_map).intersection(isolate_maps)
707 if duplicates:
708 raise MBErr(
709 'Duplicate targets in isolate map files: %s.' %
710 ', '.join(duplicates))
711 isolate_maps.update(isolate_map)
712 except SyntaxError as e:
713 raise MBErr(
714 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
715 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14716
dprankefe4602312015-04-08 16:20:35717 def ConfigFromArgs(self):
718 if self.args.config:
719 if self.args.master or self.args.builder:
720 raise MBErr('Can not specific both -c/--config and -m/--master or '
721 '-b/--builder')
722
723 return self.args.config
724
725 if not self.args.master or not self.args.builder:
726 raise MBErr('Must specify either -c/--config or '
727 '(-m/--master and -b/--builder)')
728
729 if not self.args.master in self.masters:
730 raise MBErr('Master name "%s" not found in "%s"' %
731 (self.args.master, self.args.config_file))
732
733 if not self.args.builder in self.masters[self.args.master]:
734 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
735 (self.args.builder, self.args.master, self.args.config_file))
736
dprankeb9380a12016-07-21 21:44:09737 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49738 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09739 if self.args.phase is None:
740 raise MBErr('Must specify a build --phase for %s on %s' %
741 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49742 phase = str(self.args.phase)
743 if phase not in config:
744 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09745 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49746 return config[phase]
dprankeb9380a12016-07-21 21:44:09747
748 if self.args.phase is not None:
749 raise MBErr('Must not specify a build --phase for %s on %s' %
750 (self.args.builder, self.args.master))
751 return config
dprankefe4602312015-04-08 16:20:35752
753 def FlattenConfig(self, config):
754 mixins = self.configs[config]
dprankef37aebb92016-09-23 01:14:49755 vals = self.DefaultVals()
dprankefe4602312015-04-08 16:20:35756
757 visited = []
758 self.FlattenMixins(mixins, vals, visited)
759 return vals
760
dprankef37aebb92016-09-23 01:14:49761 def DefaultVals(self):
762 return {
763 'args_file': '',
764 'cros_passthrough': False,
765 'gn_args': '',
dprankef37aebb92016-09-23 01:14:49766 }
767
dprankefe4602312015-04-08 16:20:35768 def FlattenMixins(self, mixins, vals, visited):
769 for m in mixins:
770 if m not in self.mixins:
771 raise MBErr('Unknown mixin "%s"' % m)
dprankeee5b51f62015-04-09 00:03:22772
dprankefe4602312015-04-08 16:20:35773 visited.append(m)
774
775 mixin_vals = self.mixins[m]
dpranke73ed0d62016-04-25 19:18:34776
777 if 'cros_passthrough' in mixin_vals:
778 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
Dirk Pranke6b99f072017-04-05 00:58:30779 if 'args_file' in mixin_vals:
780 if vals['args_file']:
781 raise MBErr('args_file specified multiple times in mixins '
782 'for %s on %s' % (self.args.builder, self.args.master))
783 vals['args_file'] = mixin_vals['args_file']
dprankefe4602312015-04-08 16:20:35784 if 'gn_args' in mixin_vals:
785 if vals['gn_args']:
786 vals['gn_args'] += ' ' + mixin_vals['gn_args']
787 else:
788 vals['gn_args'] = mixin_vals['gn_args']
dpranke73ed0d62016-04-25 19:18:34789
dprankefe4602312015-04-08 16:20:35790 if 'mixins' in mixin_vals:
791 self.FlattenMixins(mixin_vals['mixins'], vals, visited)
792 return vals
793
Takuto Ikuta9dffd7e2018-09-05 01:04:00794 def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
Dirk Prankef24e6b22018-03-27 20:12:30795 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50796
Takuto Ikuta9dffd7e2018-09-05 01:04:00797 if check:
798 cmd = self.GNCmd('gen', build_dir, '--check')
799 else:
800 cmd = self.GNCmd('gen', build_dir)
dprankeeca4a782016-04-14 01:42:38801 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:09802 if compute_inputs_for_analyze:
803 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:38804
805 # Since GN hasn't run yet, the build directory may not even exist.
806 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
807
808 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:54809 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:39810
dpranke751516a2015-10-03 01:11:34811 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:39812 # We need GN to generate the list of runtime dependencies for
813 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:14814 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:39815 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:00816 path = self.args.swarming_targets_file
817 if not self.Exists(path):
818 self.WriteFailureAndRaise('"%s" does not exist' % path,
819 output_path=None)
820 contents = self.ReadFile(path)
Erik Chen42df41d2018-08-21 17:13:31821 isolate_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:00822
dprankecb4a2e242016-09-19 01:13:14823 isolate_map = self.ReadIsolateMap()
Dirk Pranke7a7e9b62019-02-17 01:46:25824 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
825 isolate_map, build_dir)
826
Erik Chen42df41d2018-08-21 17:13:31827 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:00828 if err:
Dirk Pranke7a7e9b62019-02-17 01:46:25829 raise MBErr(err)
dpranke74559b52015-06-10 21:20:39830
dpranke751516a2015-10-03 01:11:34831 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:14832 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:39833 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
834
Debrian Figueroaae582232019-07-17 01:54:45835 ret, output, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:40836 if ret:
Debrian Figueroaae582232019-07-17 01:54:45837 if getattr(self.args, 'json_output', None) and output:
838 # write errors to json.output
839 self.WriteJSON({'output': output}, self.args.json_output)
Dirk Pranke7a7e9b62019-02-17 01:46:25840 # If `gn gen` failed, we should exit early rather than trying to
841 # generate isolates. Run() will have already logged any error output.
842 self.Print('GN gen failed: %d' % ret)
843 return ret
dpranke74559b52015-06-10 21:20:39844
Erik Chen42df41d2018-08-21 17:13:31845 if getattr(self.args, 'swarming_targets_file', None):
Dirk Pranke7a7e9b62019-02-17 01:46:25846 self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
Erik Chen42df41d2018-08-21 17:13:31847
848 return 0
849
850 def RunGNGenAllIsolates(self, vals):
851 """
852 This command generates all .isolate files.
853
854 This command assumes that "mb.py gen" has already been run, as it relies on
855 "gn ls" to fetch all gn targets. If uses that output, combined with the
856 isolate_map, to determine all isolates that can be generated for the current
857 gn configuration.
858 """
859 build_dir = self.args.path
860 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
861 force_verbose=False)
862 if ret:
863 # If `gn ls` failed, we should exit early rather than trying to
864 # generate isolates.
865 self.Print('GN ls failed: %d' % ret)
866 return ret
867
868 # Create a reverse map from isolate label to isolate dict.
869 isolate_map = self.ReadIsolateMap()
870 isolate_dict_map = {}
871 for key, isolate_dict in isolate_map.iteritems():
872 isolate_dict_map[isolate_dict['label']] = isolate_dict
873 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
874
875 runtime_deps = []
876
877 isolate_targets = []
878 # For every GN target, look up the isolate dict.
879 for line in output.splitlines():
880 target = line.strip()
881 if target in isolate_dict_map:
882 if isolate_dict_map[target]['type'] == 'additional_compile_target':
883 # By definition, additional_compile_targets are not tests, so we
884 # shouldn't generate isolates for them.
885 continue
886
887 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
888 runtime_deps.append(target)
889
Dirk Pranke7a7e9b62019-02-17 01:46:25890 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
891 isolate_map, build_dir)
892
Erik Chen42df41d2018-08-21 17:13:31893 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
894 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
895 cmd = self.GNCmd('gen', build_dir)
896 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
897 self.Run(cmd)
898
899 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
900
Dirk Pranke7a7e9b62019-02-17 01:46:25901 def RemovePossiblyStaleRuntimeDepsFiles(self, vals, targets, isolate_map,
902 build_dir):
903 # TODO(crbug.com/932700): Because `gn gen --runtime-deps-list-file`
904 # puts the runtime_deps file in different locations based on the actual
905 # type of a target, we may end up with multiple possible runtime_deps
906 # files in a given build directory, where some of the entries might be
907 # stale (since we might be reusing an existing build directory).
908 #
909 # We need to be able to get the right one reliably; you might think
910 # we can just pick the newest file, but because GN won't update timestamps
911 # if the contents of the files change, an older runtime_deps
912 # file might actually be the one we should use over a newer one (see
913 # crbug.com/932387 for a more complete explanation and example).
914 #
915 # In order to avoid this, we need to delete any possible runtime_deps
916 # files *prior* to running GN. As long as the files aren't actually
917 # needed during the build, this hopefully will not cause unnecessary
918 # build work, and so it should be safe.
919 #
920 # Ultimately, we should just make sure we get the runtime_deps files
921 # in predictable locations so we don't have this issue at all, and
922 # that's what crbug.com/932700 is for.
923 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, targets, isolate_map)
924 for rpaths in possible_rpaths.values():
925 for rpath in rpaths:
926 path = self.ToAbsPath(build_dir, rpath)
927 if self.Exists(path):
928 self.RemoveFile(path)
929
Erik Chen42df41d2018-08-21 17:13:31930 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
931 """
932 Generates isolates for a list of ninja targets.
933
934 Ninja targets are transformed to GN targets via isolate_map.
935
936 This function assumes that a previous invocation of "mb.py gen" has
937 generated runtime deps for all targets.
938 """
Dirk Pranke7a7e9b62019-02-17 01:46:25939 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, ninja_targets,
940 isolate_map)
941
942 for target, rpaths in possible_rpaths.items():
943 # TODO(crbug.com/932700): We don't know where each .runtime_deps
944 # file might be, but assuming we called
945 # RemovePossiblyStaleRuntimeDepsFiles prior to calling `gn gen`,
946 # there should only be one file.
947 found_one = False
948 path_to_use = None
949 for r in rpaths:
950 path = self.ToAbsPath(build_dir, r)
951 if self.Exists(path):
952 if found_one:
953 raise MBErr('Found more than one of %s' % ', '.join(rpaths))
954 path_to_use = path
955 found_one = True
956
957 if not found_one:
958 raise MBErr('Did not find any of %s' % ', '.join(rpaths))
959
960 command, extra_files = self.GetIsolateCommand(target, vals)
961 runtime_deps = self.ReadFile(path_to_use).splitlines()
962
963 canonical_target = target.replace(':','_').replace('/','_')
964 self.WriteIsolateFiles(build_dir, command, canonical_target, runtime_deps,
965 extra_files)
966
967 def PossibleRuntimeDepsPaths(self, vals, ninja_targets, isolate_map):
968 """Returns a map of targets to possible .runtime_deps paths.
969
970 Each ninja target maps on to a GN label, but depending on the type
971 of the GN target, `gn gen --runtime-deps-list-file` will write
972 the .runtime_deps files into different locations. Unfortunately, in
973 some cases we don't actually know which of multiple locations will
974 actually be used, so we return all plausible candidates.
975
976 The paths that are returned are relative to the build directory.
977 """
978
jbudoricke3c4f95e2016-04-28 23:17:38979 android = 'target_os="android"' in vals['gn_args']
Dirk Pranke26de05aec2019-04-03 19:18:38980 ios = 'target_os="ios"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:42981 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:30982 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Dirk Pranke7a7e9b62019-02-17 01:46:25983 possible_runtime_deps_rpaths = {}
Erik Chen42df41d2018-08-21 17:13:31984 for target in ninja_targets:
John Budorick39f14962019-04-11 23:03:20985 target_type = isolate_map[target]['type']
986 label = isolate_map[target]['label']
987 stamp_runtime_deps = 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')
Erik Chen42df41d2018-08-21 17:13:31988 # TODO(https://crbug.com/876065): 'official_tests' use
989 # type='additional_compile_target' to isolate tests. This is not the
990 # intended use for 'additional_compile_target'.
John Budorick39f14962019-04-11 23:03:20991 if (target_type == 'additional_compile_target' and
Erik Chen42df41d2018-08-21 17:13:31992 target != 'official_tests'):
993 # By definition, additional_compile_targets are not tests, so we
994 # shouldn't generate isolates for them.
Dirk Pranke7a7e9b62019-02-17 01:46:25995 raise MBErr('Cannot generate isolate for %s since it is an '
996 'additional_compile_target.' % target)
John Budorick39f14962019-04-11 23:03:20997 elif fuchsia or ios or target_type == 'generated_script':
998 # iOS and Fuchsia targets end up as groups.
999 # generated_script targets are always actions.
1000 rpaths = [stamp_runtime_deps]
Erik Chen42df41d2018-08-21 17:13:311001 elif android:
jbudoricke3c4f95e2016-04-28 23:17:381002 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:021003 # will result in runtime_deps associated with the stamp file, while the
1004 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:441005 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:251006 rpaths = [
dprankecb4a2e242016-09-19 01:13:141007 target + '.runtime_deps',
John Budorick39f14962019-04-11 23:03:201008 stamp_runtime_deps]
1009 elif (target_type == 'script' or
1010 target_type == 'fuzzer' or
dprankecb4a2e242016-09-19 01:13:141011 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:111012 # For script targets, the build target is usually a group,
1013 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:491014 # for the label, which lives under the obj/ directory, but it may
1015 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:441016 label = isolate_map[target]['label']
John Budorick39f14962019-04-11 23:03:201017 rpaths = [stamp_runtime_deps]
Nico Weberd94b71a2018-02-22 22:00:301018 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:251019 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:491020 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251021 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:301022 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:251023 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:521024 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251025 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:021026
Dirk Pranke7a7e9b62019-02-17 01:46:251027 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:411028
Dirk Pranke7a7e9b62019-02-17 01:46:251029 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:341030
1031 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:301032 target = self.args.target
dprankecb4a2e242016-09-19 01:13:141033 isolate_map = self.ReadIsolateMap()
1034 err, labels = self.MapTargetsToLabels(isolate_map, [target])
1035 if err:
1036 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:251037
dprankecb4a2e242016-09-19 01:13:141038 label = labels[0]
dpranke751516a2015-10-03 01:11:341039
Dirk Prankef24e6b22018-03-27 20:12:301040 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:141041 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:341042
dprankeeca4a782016-04-14 01:42:381043 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:201044 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:341045 if ret:
dpranke030d7a6d2016-03-26 17:23:501046 if out:
1047 self.Print(out)
dpranke751516a2015-10-03 01:11:341048 return ret
1049
1050 runtime_deps = out.splitlines()
1051
1052 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
1053 extra_files)
1054
1055 ret, _, _ = self.Run([
1056 self.executable,
1057 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1058 'check',
1059 '-i',
1060 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1061 '-s',
1062 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1063 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301064
dprankefe4602312015-04-08 16:20:351065 return ret
1066
dpranke751516a2015-10-03 01:11:341067 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps,
1068 extra_files):
1069 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
1070 self.WriteFile(isolate_path,
1071 pprint.pformat({
1072 'variables': {
1073 'command': command,
Dirk Pranke8edeb682019-06-11 16:24:051074 'files': sorted(set(runtime_deps + extra_files)),
dpranke751516a2015-10-03 01:11:341075 }
1076 }) + '\n')
1077
1078 self.WriteJSON(
1079 {
1080 'args': [
1081 '--isolated',
1082 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1083 '--isolate',
1084 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1085 ],
1086 'dir': self.chromium_src_dir,
1087 'version': 1,
1088 },
1089 isolate_path + 'd.gen.json',
1090 )
1091
dprankecb4a2e242016-09-19 01:13:141092 def MapTargetsToLabels(self, isolate_map, targets):
1093 labels = []
1094 err = ''
1095
dprankecb4a2e242016-09-19 01:13:141096 for target in targets:
1097 if target == 'all':
1098 labels.append(target)
1099 elif target.startswith('//'):
1100 labels.append(target)
1101 else:
1102 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421103 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141104 err += ('test target "%s" type is unknown\n' % target)
1105 else:
thakis024d6f32017-05-16 23:21:421106 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141107 else:
1108 err += ('target "%s" not found in '
1109 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1110
1111 return err, labels
1112
dprankeeca4a782016-04-14 01:42:381113 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461114 if self.platform == 'linux2':
1115 subdir, exe = 'linux64', 'gn'
1116 elif self.platform == 'darwin':
1117 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251118 elif self.platform == 'aix6':
1119 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461120 else:
1121 subdir, exe = 'win', 'gn.exe'
1122
1123 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081124 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521125
dprankecb4a2e242016-09-19 01:13:141126
Garrett Beatyb6cee042019-04-22 18:42:091127 def GNArgs(self, vals, expand_imports=False):
dpranke73ed0d62016-04-25 19:18:341128 if vals['cros_passthrough']:
1129 if not 'GN_ARGS' in os.environ:
1130 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1131 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161132 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301133 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341134 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351135 if vals['gn_args']:
1136 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341137 else:
1138 gn_args = vals['gn_args']
1139
dpranked0c138b2016-04-13 18:28:471140 if self.args.goma_dir:
1141 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381142
agrieve41d21a72016-04-14 18:02:261143 android_version_code = self.args.android_version_code
1144 if android_version_code:
1145 gn_args += ' android_default_version_code="%s"' % android_version_code
1146
1147 android_version_name = self.args.android_version_name
1148 if android_version_name:
1149 gn_args += ' android_default_version_name="%s"' % android_version_name
1150
Garrett Beatyb6cee042019-04-22 18:42:091151 args_gn_lines = []
1152 parsed_gn_args = {}
dprankeeca4a782016-04-14 01:42:381153
Ben Pastene65ccf6132018-11-08 00:47:591154 # If we're using the Simple Chrome SDK, add a comment at the top that
1155 # points to the doc. This must happen after the gn_helpers.ToGNString()
1156 # call above since gn_helpers strips comments.
1157 if vals['cros_passthrough']:
Garrett Beatyb6cee042019-04-22 18:42:091158 args_gn_lines.extend([
Ben Pastene65ccf6132018-11-08 00:47:591159 '# These args are generated via the Simple Chrome SDK. See the link',
1160 '# below for more details:',
1161 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
Garrett Beatyb6cee042019-04-22 18:42:091162 ])
Ben Pastene65ccf6132018-11-08 00:47:591163
dpranke9dd5e252016-04-14 04:23:091164 args_file = vals.get('args_file', None)
1165 if args_file:
Garrett Beatyb6cee042019-04-22 18:42:091166 if expand_imports:
1167 content = self.ReadFile(self.ToAbsPath(args_file))
1168 parsed_gn_args = gn_helpers.FromGNArgs(content)
1169 else:
1170 args_gn_lines.append('import("%s")' % args_file)
1171
1172 # Canonicalize the arg string into a sorted, newline-separated list
1173 # of key-value pairs, and de-dup the keys if need be so that only
1174 # the last instance of each arg is listed.
1175 parsed_gn_args.update(gn_helpers.FromGNArgs(gn_args))
1176 args_gn_lines.append(gn_helpers.ToGNString(parsed_gn_args))
1177
1178 return '\n'.join(args_gn_lines)
dprankefe4602312015-04-08 16:20:351179
dprankecb4a2e242016-09-19 01:13:141180 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071181 isolate_map = self.ReadIsolateMap()
1182
Scott Graham3be4b4162017-09-12 00:41:411183 is_android = 'target_os="android"' in vals['gn_args']
1184 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Caleb Raittof983d102019-06-21 23:05:021185 is_cros = 'target_os="chromeos"' in vals['gn_args']
Nico Webera7bc1cb2019-06-15 17:42:391186 is_simplechrome = vals.get('cros_passthrough', False)
1187 is_mac = self.platform == 'darwin'
Nico Weberd94b71a2018-02-22 22:00:301188 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061189
kylechar39705682017-01-19 14:37:231190 # This should be true if tests with type='windowed_test_launcher' are
1191 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081192 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1193 # backends. Currently, tests are executed for the headless and X11 backends
1194 # and both can run under Xvfb.
1195 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1196 # backend.
Scott Graham3be4b4162017-09-12 00:41:411197 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251198
1199 asan = 'is_asan=true' in vals['gn_args']
1200 msan = 'is_msan=true' in vals['gn_args']
1201 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411202 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
Yun Liu7cef1072019-06-27 21:22:191203 java_coverage = 'jacoco_coverage=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251204
dprankecb4a2e242016-09-19 01:13:141205 test_type = isolate_map[target]['type']
dprankefe0d35e2016-02-05 02:43:591206
dprankecb4a2e242016-09-19 01:13:141207 executable = isolate_map[target].get('executable', target)
bsheedy9c16ed62019-04-10 20:32:111208 executable_suffix = isolate_map[target].get(
1209 'executable_suffix', '.exe' if is_win else '')
dprankefe0d35e2016-02-05 02:43:591210
dprankea55584f12015-07-22 00:52:471211 cmdline = []
Andrii Shyshkalovc158e0102018-01-10 05:52:001212 extra_files = [
1213 '../../.vpython',
1214 '../../testing/test_env.py',
1215 ]
dpranked8113582015-06-05 20:08:251216
dprankecb4a2e242016-09-19 01:13:141217 if test_type == 'nontest':
1218 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1219 output_path=None)
1220
John Budorick93e88ac82019-04-12 18:39:111221 if test_type == 'generated_script':
Ben Pastenecb0fb412019-06-11 02:31:541222 script = isolate_map[target]['script']
1223 if self.platform == 'win32':
1224 script += '.bat'
John Budorick93e88ac82019-04-12 18:39:111225 cmdline = [
1226 '../../testing/test_env.py',
Ben Pastenecb0fb412019-06-11 02:31:541227 script,
John Budorick93e88ac82019-04-12 18:39:111228 ]
1229 elif test_type == 'fuzzer':
Roberto Carrillo1460da852018-12-14 17:10:391230 cmdline = [
1231 '../../testing/test_env.py',
1232 '../../tools/code_coverage/run_fuzz_target.py',
1233 '--fuzzer', './' + target,
1234 '--output-dir', '${ISOLATED_OUTDIR}',
1235 '--timeout', '3600']
1236 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011237 cmdline = []
1238 if asan:
John Budorick31cdce62019-04-03 20:56:111239 cmdline += [os.path.join('bin', 'run_with_asan'), '--']
John Budorick8c4203042019-03-19 17:22:011240 cmdline += [
John Budorickfb97a852017-12-20 20:10:191241 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041242 '../../build/android/test_wrapper/logdog_wrapper.py',
1243 '--target', target,
hzl9ae14452017-04-04 23:38:021244 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481245 '--store-tombstones']
Yun Liu7cef1072019-06-27 21:22:191246 if java_coverage:
1247 cmdline += ['--coverage-dir', '${ISOLATED_OUTDIR}']
Scott Graham3be4b4162017-09-12 00:41:411248 elif is_fuchsia and test_type != 'script':
John Budorickfb97a852017-12-20 20:10:191249 cmdline = [
1250 '../../testing/test_env.py',
1251 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441252 '--test-launcher-bot-mode',
Sergey Ulanovd851243b2019-06-25 00:33:471253 '--system-log-file', '${ISOLATED_OUTDIR}/system_log'
John Budorickfb97a852017-12-20 20:10:191254 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321255 elif is_simplechrome and test_type != 'script':
1256 cmdline = [
1257 '../../testing/test_env.py',
1258 os.path.join('bin', 'run_%s' % target),
1259 ]
kylechar39705682017-01-19 14:37:231260 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001261 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471262 cmdline = [
Nico Webera7bc1cb2019-06-15 17:42:391263 '../../testing/xvfb.py',
1264 './' + str(executable) + executable_suffix,
1265 '--test-launcher-bot-mode',
1266 '--asan=%d' % asan,
1267 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1268 # supported.
1269 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021270 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1271 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
Nico Webera7bc1cb2019-06-15 17:42:391272 '--msan=%d' % msan,
1273 '--tsan=%d' % tsan,
1274 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471275 ]
1276 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471277 cmdline = [
1278 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591279 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251280 '--test-launcher-bot-mode',
1281 '--asan=%d' % asan,
Caleb Raitto1fb2cc9e2019-06-14 01:04:231282 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1283 # supported.
Nico Webera7bc1cb2019-06-15 17:42:391284 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021285 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1286 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
dpranked8113582015-06-05 20:08:251287 '--msan=%d' % msan,
1288 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411289 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471290 ]
dpranke6abd8652015-08-28 03:21:111291 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241292 cmdline = []
Ben Pastene4534c39e2019-07-08 22:55:341293 # If we're testing a CrOS simplechrome build, assume we need to prepare a
1294 # DUT for testing. So prepend the command to run with the test wrapper.
Ben Pastene8ab6954d2018-05-04 04:08:241295 if is_simplechrome:
Ben Pastene4534c39e2019-07-08 22:55:341296 cmdline = [os.path.join('bin', 'cros_test_wrapper')]
Ben Pastene8ab6954d2018-05-04 04:08:241297 cmdline += [
dpranke6abd8652015-08-28 03:21:111298 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141299 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591300 ]
Dirk Prankef24e6b22018-03-27 20:12:301301 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471302 cmdline = [
1303 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591304 ]
dprankea55584f12015-07-22 00:52:471305 else:
1306 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1307 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251308
Abhishek Arya2f5f7342018-06-13 16:59:441309 if is_win and asan:
Alexander Dunaev384ba482018-03-21 17:56:121310 # Sandbox is not yet supported by ASAN for Windows.
1311 # Perhaps this is only needed for tests that use the sandbox?
1312 cmdline.append('--no-sandbox')
1313
dprankecb4a2e242016-09-19 01:13:141314 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591315
dpranked8113582015-06-05 20:08:251316 return cmdline, extra_files
1317
dpranke74559b52015-06-10 21:20:391318 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331319 return self.PathJoin(self.chromium_src_dir,
1320 self.ToSrcRelPath(build_path),
1321 *comps)
dpranked8113582015-06-05 20:08:251322
dprankeee5b51f62015-04-09 00:03:221323 def ToSrcRelPath(self, path):
1324 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501325 if path.startswith('//'):
1326 return path[2:].replace('/', self.sep)
1327 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351328
Dirk Pranke0fd41bcd2015-06-19 00:05:501329 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141330 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501331 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001332 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501333 if ret:
1334 return ret
1335
Dirk Prankef24e6b22018-03-27 20:12:301336 build_path = self.args.path
1337 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141338 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301339 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141340 gn_output_path = output_path + '.gn'
1341
dpranke7837fc362015-11-19 03:54:161342 inp = self.ReadInputJSON(['files', 'test_targets',
1343 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321344 if self.args.verbose:
1345 self.Print()
1346 self.Print('analyze input:')
1347 self.PrintJSON(inp)
1348 self.Print()
1349
dpranke76734662015-04-16 02:17:501350
dpranke7c5f614d2015-07-22 23:43:391351 # This shouldn't normally happen, but could due to unusual race conditions,
1352 # like a try job that gets scheduled before a patch lands but runs after
1353 # the patch has landed.
1354 if not inp['files']:
1355 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161356 self.WriteJSON({
1357 'status': 'No dependency',
1358 'compile_targets': [],
1359 'test_targets': [],
1360 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391361 return 0
1362
dprankecb4a2e242016-09-19 01:13:141363 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161364 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561365
dprankecb4a2e242016-09-19 01:13:141366 isolate_map = self.ReadIsolateMap()
1367 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1368 isolate_map, inp['additional_compile_targets'])
1369 if err:
1370 raise MBErr(err)
1371
1372 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1373 isolate_map, inp['test_targets'])
1374 if err:
1375 raise MBErr(err)
1376 labels_to_targets = {}
1377 for i, label in enumerate(gn_inp['test_targets']):
1378 labels_to_targets[label] = inp['test_targets'][i]
1379
dprankef61de2f2015-05-14 04:09:561380 try:
dprankecb4a2e242016-09-19 01:13:141381 self.WriteJSON(gn_inp, gn_input_path)
1382 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
1383 ret, _, _ = self.Run(cmd, force_verbose=True)
1384 if ret:
1385 return ret
dpranke067d0142015-05-14 22:52:451386
dprankecb4a2e242016-09-19 01:13:141387 gn_outp_str = self.ReadFile(gn_output_path)
1388 try:
1389 gn_outp = json.loads(gn_outp_str)
1390 except Exception as e:
1391 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1392 % (repr(gn_outp_str), str(e)))
1393 raise
1394
1395 outp = {}
1396 if 'status' in gn_outp:
1397 outp['status'] = gn_outp['status']
1398 if 'error' in gn_outp:
1399 outp['error'] = gn_outp['error']
1400 if 'invalid_targets' in gn_outp:
1401 outp['invalid_targets'] = gn_outp['invalid_targets']
1402 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491403 all_input_compile_targets = sorted(
1404 set(inp['test_targets'] + inp['additional_compile_targets']))
1405
1406 # If we're building 'all', we can throw away the rest of the targets
1407 # since they're redundant.
dpranke385a3102016-09-20 22:04:081408 if 'all' in gn_outp['compile_targets']:
1409 outp['compile_targets'] = ['all']
1410 else:
Dirk Pranke45165072017-11-08 04:57:491411 outp['compile_targets'] = gn_outp['compile_targets']
1412
1413 # crbug.com/736215: When GN returns targets back, for targets in
1414 # the default toolchain, GN will have generated a phony ninja
1415 # target matching the label, and so we can safely (and easily)
1416 # transform any GN label into the matching ninja target. For
1417 # targets in other toolchains, though, GN doesn't generate the
1418 # phony targets, and we don't know how to turn the labels into
1419 # compile targets. In this case, we also conservatively give up
1420 # and build everything. Probably the right thing to do here is
1421 # to have GN return the compile targets directly.
1422 if any("(" in target for target in outp['compile_targets']):
1423 self.Print('WARNING: targets with non-default toolchains were '
1424 'found, building everything instead.')
1425 outp['compile_targets'] = all_input_compile_targets
1426 else:
dpranke385a3102016-09-20 22:04:081427 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491428 label.replace('//', '') for label in outp['compile_targets']]
1429
1430 # Windows has a maximum command line length of 8k; even Linux
1431 # maxes out at 128k; if analyze returns a *really long* list of
1432 # targets, we just give up and conservatively build everything instead.
1433 # Probably the right thing here is for ninja to support response
1434 # files as input on the command line
1435 # (see https://github.com/ninja-build/ninja/issues/1355).
1436 if len(' '.join(outp['compile_targets'])) > 7*1024:
1437 self.Print('WARNING: Too many compile targets were affected.')
1438 self.Print('WARNING: Building everything instead to avoid '
1439 'command-line length issues.')
1440 outp['compile_targets'] = all_input_compile_targets
1441
1442
dprankecb4a2e242016-09-19 01:13:141443 if 'test_targets' in gn_outp:
1444 outp['test_targets'] = [
1445 labels_to_targets[label] for label in gn_outp['test_targets']]
1446
1447 if self.args.verbose:
1448 self.Print()
1449 self.Print('analyze output:')
1450 self.PrintJSON(outp)
1451 self.Print()
1452
1453 self.WriteJSON(outp, output_path)
1454
dprankef61de2f2015-05-14 04:09:561455 finally:
dprankecb4a2e242016-09-19 01:13:141456 if self.Exists(gn_input_path):
1457 self.RemoveFile(gn_input_path)
1458 if self.Exists(gn_output_path):
1459 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351460
1461 return 0
1462
dpranked8113582015-06-05 20:08:251463 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301464 path = self.args.input_path
1465 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351466 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321467 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351468
1469 try:
1470 inp = json.loads(self.ReadFile(path))
1471 except Exception as e:
1472 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321473 (path, e), output_path)
dpranked8113582015-06-05 20:08:251474
1475 for k in required_keys:
1476 if not k in inp:
1477 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1478 output_path)
dprankefe4602312015-04-08 16:20:351479
1480 return inp
1481
dpranked5b2b9432015-06-23 16:55:301482 def WriteFailureAndRaise(self, msg, output_path):
1483 if output_path:
dprankee0547cd2015-09-15 01:27:401484 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351485 raise MBErr(msg)
1486
dprankee0547cd2015-09-15 01:27:401487 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321488 try:
dprankee0547cd2015-09-15 01:27:401489 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1490 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321491 except Exception as e:
1492 raise MBErr('Error %s writing to the output path "%s"' %
1493 (e, path))
dprankefe4602312015-04-08 16:20:351494
aneeshmde50f472016-04-01 01:13:101495 def CheckCompile(self, master, builder):
1496 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1497 url = urllib2.quote(url_template.format(master=master, builder=builder),
1498 safe=':/()?=')
1499 try:
1500 builds = json.loads(self.Fetch(url))
1501 except Exception as e:
1502 return str(e)
1503 successes = sorted(
1504 [int(x) for x in builds.keys() if "text" in builds[x] and
1505 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1506 reverse=True)
1507 if not successes:
1508 return "no successful builds"
1509 build = builds[str(successes[0])]
1510 step_names = set([step["name"] for step in build["steps"]])
1511 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1512 if compile_indicators & step_names:
1513 return "compiles"
1514 return "does not compile"
1515
dpranke3cec199c2015-09-22 23:29:021516 def PrintCmd(self, cmd, env):
1517 if self.platform == 'win32':
1518 env_prefix = 'set '
1519 env_quoter = QuoteForSet
1520 shell_quoter = QuoteForCmd
1521 else:
1522 env_prefix = ''
1523 env_quoter = pipes.quote
1524 shell_quoter = pipes.quote
1525
1526 def print_env(var):
1527 if env and var in env:
1528 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1529
dprankeec079262016-06-07 02:21:201530 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021531
dpranke8c2cfd32015-09-17 20:12:331532 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351533 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021534 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351535
dprankecda00332015-04-11 04:18:321536 def PrintJSON(self, obj):
1537 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1538
dpranke751516a2015-10-03 01:11:341539 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301540 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381541 if self.platform == 'win32':
1542 # On Windows use the batch script since there is no exe
1543 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1544 else:
1545 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341546 if self.args.jobs:
1547 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1548 ninja_cmd.append(target)
Dirk Pranke5f22a822019-05-23 22:55:251549 ret, _, _ = self.Run(ninja_cmd, buffer_output=False)
dpranke751516a2015-10-03 01:11:341550 return ret
1551
1552 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351553 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401554 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021555 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351556 if self.args.dryrun:
1557 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401558
dpranke751516a2015-10-03 01:11:341559 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401560 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341561 if ret:
1562 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351563 if out:
Debrian Figueroaae582232019-07-17 01:54:451564 # This is the error seen on the logs
dprankeee5b51f62015-04-09 00:03:221565 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351566 if err:
dprankeee5b51f62015-04-09 00:03:221567 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351568 return ret, out, err
1569
dpranke751516a2015-10-03 01:11:341570 def Call(self, cmd, env=None, buffer_output=True):
1571 if buffer_output:
1572 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1573 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1574 env=env)
1575 out, err = p.communicate()
1576 else:
1577 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1578 env=env)
1579 p.wait()
1580 out = err = ''
dprankefe4602312015-04-08 16:20:351581 return p.returncode, out, err
1582
1583 def ExpandUser(self, path):
1584 # This function largely exists so it can be overridden for testing.
1585 return os.path.expanduser(path)
1586
1587 def Exists(self, path):
1588 # This function largely exists so it can be overridden for testing.
1589 return os.path.exists(path)
1590
dpranke867bcf4a2016-03-14 22:28:321591 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501592 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321593 f = urllib2.urlopen(url)
1594 contents = f.read()
1595 f.close()
1596 return contents
1597
dprankec3441d12015-06-23 23:01:351598 def MaybeMakeDirectory(self, path):
1599 try:
1600 os.makedirs(path)
1601 except OSError, e:
1602 if e.errno != errno.EEXIST:
1603 raise
1604
dpranke8c2cfd32015-09-17 20:12:331605 def PathJoin(self, *comps):
1606 # This function largely exists so it can be overriden for testing.
1607 return os.path.join(*comps)
1608
dpranke030d7a6d2016-03-26 17:23:501609 def Print(self, *args, **kwargs):
1610 # This function largely exists so it can be overridden for testing.
1611 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101612 if kwargs.get('stream', sys.stdout) == sys.stdout:
1613 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501614
dprankefe4602312015-04-08 16:20:351615 def ReadFile(self, path):
1616 # This function largely exists so it can be overriden for testing.
1617 with open(path) as fp:
1618 return fp.read()
1619
dpranke030d7a6d2016-03-26 17:23:501620 def RelPath(self, path, start='.'):
1621 # This function largely exists so it can be overriden for testing.
1622 return os.path.relpath(path, start)
1623
dprankef61de2f2015-05-14 04:09:561624 def RemoveFile(self, path):
1625 # This function largely exists so it can be overriden for testing.
1626 os.remove(path)
1627
dprankec161aa92015-09-14 20:21:131628 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331629 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131630 # In other places in chromium, we often have to retry this command
1631 # because we're worried about other processes still holding on to
1632 # file handles, but when MB is invoked, it will be early enough in the
1633 # build that their should be no other processes to interfere. We
1634 # can change this if need be.
1635 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1636 else:
1637 shutil.rmtree(abs_path, ignore_errors=True)
1638
Dirk Prankef24e6b22018-03-27 20:12:301639 def TempDir(self):
1640 # This function largely exists so it can be overriden for testing.
1641 return tempfile.mkdtemp(prefix='mb_')
1642
dprankef61de2f2015-05-14 04:09:561643 def TempFile(self, mode='w'):
1644 # This function largely exists so it can be overriden for testing.
1645 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1646
dprankee0547cd2015-09-15 01:27:401647 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351648 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401649 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301650 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351651 with open(path, 'w') as fp:
1652 return fp.write(contents)
1653
dprankef61de2f2015-05-14 04:09:561654
dprankefe4602312015-04-08 16:20:351655class MBErr(Exception):
1656 pass
1657
1658
dpranke3cec199c2015-09-22 23:29:021659# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1660# details of this next section, which handles escaping command lines
1661# so that they can be copied and pasted into a cmd window.
1662UNSAFE_FOR_SET = set('^<>&|')
1663UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1664ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1665
1666
1667def QuoteForSet(arg):
1668 if any(a in UNSAFE_FOR_SET for a in arg):
1669 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1670 return arg
1671
1672
1673def QuoteForCmd(arg):
1674 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021675 if arg == '' or ' ' in arg or '"' in arg:
1676 quote_re = re.compile(r'(\\*)"')
1677 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1678
1679 # Then check to see if the arg contains any metacharacters other than
1680 # double quotes; if it does, quote everything (including the double
1681 # quotes) for safety.
1682 if any(a in UNSAFE_FOR_CMD for a in arg):
1683 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1684 return arg
1685
1686
dprankefe4602312015-04-08 16:20:351687if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591688 sys.exit(main(sys.argv[1:]))