blob: 8af09fed321571cc089386cd25fc470d74c418d4 [file] [log] [blame]
dprankefe4602312015-04-08 16:20:351#!/usr/bin/env python
2# Copyright 2015 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Dirk Pranke8cb6aa782017-12-16 02:31:336"""MB - the Meta-Build wrapper around GN.
dprankefe4602312015-04-08 16:20:357
Dirk Pranked181a1a2017-12-14 01:47:118MB is a wrapper script for GN that can be used to generate build files
dprankefe4602312015-04-08 16:20:359for sets of canned configurations and analyze them.
10"""
11
12from __future__ import print_function
13
14import argparse
15import ast
dprankec3441d12015-06-23 23:01:3516import errno
dprankefe4602312015-04-08 16:20:3517import json
18import os
dpranke68d1cb182015-09-17 23:30:0019import pipes
Dirk Pranke8cb6aa782017-12-16 02:31:3320import platform
dpranked8113582015-06-05 20:08:2521import pprint
dpranke3cec199c2015-09-22 23:29:0222import re
dprankefe4602312015-04-08 16:20:3523import shutil
24import sys
25import subprocess
dprankef61de2f2015-05-14 04:09:5626import tempfile
dprankebbe6d4672016-04-19 06:56:5727import traceback
dpranke867bcf4a2016-03-14 22:28:3228import urllib2
Dirk Prankef24e6b22018-03-27 20:12:3029import zipfile
dpranke867bcf4a2016-03-14 22:28:3230
31from collections import OrderedDict
dprankefe4602312015-04-08 16:20:3532
dprankeeca4a782016-04-14 01:42:3833CHROMIUM_SRC_DIR = os.path.dirname(os.path.dirname(os.path.dirname(
34 os.path.abspath(__file__))))
35sys.path = [os.path.join(CHROMIUM_SRC_DIR, 'build')] + sys.path
36
37import gn_helpers
38
39
dprankefe4602312015-04-08 16:20:3540def main(args):
dprankeee5b51f62015-04-09 00:03:2241 mbw = MetaBuildWrapper()
dpranke255085e2016-03-16 05:23:5942 return mbw.Main(args)
dprankefe4602312015-04-08 16:20:3543
44
45class MetaBuildWrapper(object):
46 def __init__(self):
dprankeeca4a782016-04-14 01:42:3847 self.chromium_src_dir = CHROMIUM_SRC_DIR
48 self.default_config = os.path.join(self.chromium_src_dir, 'tools', 'mb',
49 'mb_config.pyl')
kjellander902bcb62016-10-26 06:20:5050 self.default_isolate_map = os.path.join(self.chromium_src_dir, 'testing',
51 'buildbot', 'gn_isolate_map.pyl')
dpranke8c2cfd32015-09-17 20:12:3352 self.executable = sys.executable
dpranked1fba482015-04-14 20:54:5153 self.platform = sys.platform
dpranke8c2cfd32015-09-17 20:12:3354 self.sep = os.sep
dprankefe4602312015-04-08 16:20:3555 self.args = argparse.Namespace()
56 self.configs = {}
dprankefe4602312015-04-08 16:20:3557 self.masters = {}
58 self.mixins = {}
dprankefe4602312015-04-08 16:20:3559
dpranke255085e2016-03-16 05:23:5960 def Main(self, args):
61 self.ParseArgs(args)
62 try:
63 ret = self.args.func()
64 if ret:
65 self.DumpInputFiles()
66 return ret
67 except KeyboardInterrupt:
dprankecb4a2e242016-09-19 01:13:1468 self.Print('interrupted, exiting')
dpranke255085e2016-03-16 05:23:5969 return 130
dprankebbe6d4672016-04-19 06:56:5770 except Exception:
dpranke255085e2016-03-16 05:23:5971 self.DumpInputFiles()
dprankebbe6d4672016-04-19 06:56:5772 s = traceback.format_exc()
73 for l in s.splitlines():
74 self.Print(l)
dpranke255085e2016-03-16 05:23:5975 return 1
76
dprankefe4602312015-04-08 16:20:3577 def ParseArgs(self, argv):
78 def AddCommonOptions(subp):
79 subp.add_argument('-b', '--builder',
80 help='builder name to look up config from')
81 subp.add_argument('-m', '--master',
82 help='master name to look up config from')
83 subp.add_argument('-c', '--config',
84 help='configuration to analyze')
shenghuazhang804b21542016-10-11 02:06:4985 subp.add_argument('--phase',
86 help='optional phase name (used when builders '
87 'do multiple compiles with different '
88 'arguments in a single build)')
dprankefe4602312015-04-08 16:20:3589 subp.add_argument('-f', '--config-file', metavar='PATH',
90 default=self.default_config,
91 help='path to config file '
kjellander902bcb62016-10-26 06:20:5092 '(default is %(default)s)')
93 subp.add_argument('-i', '--isolate-map-file', metavar='PATH',
kjellander902bcb62016-10-26 06:20:5094 help='path to isolate map file '
Zhiling Huang66958462018-02-03 00:28:2095 '(default is %(default)s)',
96 default=[],
97 action='append',
98 dest='isolate_map_files')
dpranked0c138b2016-04-13 18:28:4799 subp.add_argument('-g', '--goma-dir',
100 help='path to goma directory')
agrieve41d21a72016-04-14 18:02:26101 subp.add_argument('--android-version-code',
Dirk Pranked181a1a2017-12-14 01:47:11102 help='Sets GN arg android_default_version_code')
agrieve41d21a72016-04-14 18:02:26103 subp.add_argument('--android-version-name',
Dirk Pranked181a1a2017-12-14 01:47:11104 help='Sets GN arg android_default_version_name')
dprankefe4602312015-04-08 16:20:35105 subp.add_argument('-n', '--dryrun', action='store_true',
106 help='Do a dry run (i.e., do nothing, just print '
107 'the commands that will run)')
dprankee0547cd2015-09-15 01:27:40108 subp.add_argument('-v', '--verbose', action='store_true',
109 help='verbose logging')
dprankefe4602312015-04-08 16:20:35110
111 parser = argparse.ArgumentParser(prog='mb')
112 subps = parser.add_subparsers()
113
114 subp = subps.add_parser('analyze',
115 help='analyze whether changes to a set of files '
116 'will cause a set of binaries to be rebuilt.')
117 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30118 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35119 help='path build was generated into.')
Dirk Prankef24e6b22018-03-27 20:12:30120 subp.add_argument('input_path',
dprankefe4602312015-04-08 16:20:35121 help='path to a file containing the input arguments '
122 'as a JSON object.')
Dirk Prankef24e6b22018-03-27 20:12:30123 subp.add_argument('output_path',
dprankefe4602312015-04-08 16:20:35124 help='path to a file containing the output arguments '
125 'as a JSON object.')
126 subp.set_defaults(func=self.CmdAnalyze)
127
dprankef37aebb92016-09-23 01:14:49128 subp = subps.add_parser('export',
129 help='print out the expanded configuration for'
130 'each builder as a JSON object')
131 subp.add_argument('-f', '--config-file', metavar='PATH',
132 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50133 help='path to config file (default is %(default)s)')
dprankef37aebb92016-09-23 01:14:49134 subp.add_argument('-g', '--goma-dir',
135 help='path to goma directory')
136 subp.set_defaults(func=self.CmdExport)
137
dprankefe4602312015-04-08 16:20:35138 subp = subps.add_parser('gen',
139 help='generate a new set of build files')
140 AddCommonOptions(subp)
dpranke74559b52015-06-10 21:20:39141 subp.add_argument('--swarming-targets-file',
142 help='save runtime dependencies for targets listed '
143 'in file.')
Dirk Prankef24e6b22018-03-27 20:12:30144 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35145 help='path to generate build into')
146 subp.set_defaults(func=self.CmdGen)
147
Erik Chen42df41d2018-08-21 17:13:31148 subp = subps.add_parser('isolate-everything',
149 help='generates a .isolate for all targets. '
150 'Requires that mb.py gen has already been '
151 'run.')
152 AddCommonOptions(subp)
153 subp.set_defaults(func=self.CmdIsolateEverything)
154 subp.add_argument('path',
155 help='path build was generated into')
156
dpranke751516a2015-10-03 01:11:34157 subp = subps.add_parser('isolate',
158 help='generate the .isolate files for a given'
159 'binary')
160 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30161 subp.add_argument('--no-build', dest='build', default=True,
162 action='store_false',
163 help='Do not build, just isolate')
164 subp.add_argument('-j', '--jobs', type=int,
165 help='Number of jobs to pass to ninja')
166 subp.add_argument('path',
dpranke751516a2015-10-03 01:11:34167 help='path build was generated into')
Dirk Prankef24e6b22018-03-27 20:12:30168 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34169 help='ninja target to generate the isolate for')
170 subp.set_defaults(func=self.CmdIsolate)
171
dprankefe4602312015-04-08 16:20:35172 subp = subps.add_parser('lookup',
173 help='look up the command for a given config or '
174 'builder')
175 AddCommonOptions(subp)
176 subp.set_defaults(func=self.CmdLookup)
177
dpranke030d7a6d2016-03-26 17:23:50178 subp = subps.add_parser(
179 'run',
180 help='build and run the isolated version of a '
181 'binary',
182 formatter_class=argparse.RawDescriptionHelpFormatter)
183 subp.description = (
184 'Build, isolate, and run the given binary with the command line\n'
185 'listed in the isolate. You may pass extra arguments after the\n'
186 'target; use "--" if the extra arguments need to include switches.\n'
187 '\n'
188 'Examples:\n'
189 '\n'
190 ' % tools/mb/mb.py run -m chromium.linux -b "Linux Builder" \\\n'
191 ' //out/Default content_browsertests\n'
192 '\n'
193 ' % tools/mb/mb.py run out/Default content_browsertests\n'
194 '\n'
195 ' % tools/mb/mb.py run out/Default content_browsertests -- \\\n'
196 ' --test-launcher-retry-limit=0'
197 '\n'
198 )
dpranke751516a2015-10-03 01:11:34199 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30200 subp.add_argument('-j', '--jobs', type=int,
dpranke751516a2015-10-03 01:11:34201 help='Number of jobs to pass to ninja')
202 subp.add_argument('--no-build', dest='build', default=True,
203 action='store_false',
204 help='Do not build, just isolate and run')
Dirk Prankef24e6b22018-03-27 20:12:30205 subp.add_argument('path',
dpranke030d7a6d2016-03-26 17:23:50206 help=('path to generate build into (or use).'
207 ' This can be either a regular path or a '
208 'GN-style source-relative path like '
209 '//out/Default.'))
Dirk Pranke8cb6aa782017-12-16 02:31:33210 subp.add_argument('-s', '--swarmed', action='store_true',
211 help='Run under swarming with the default dimensions')
212 subp.add_argument('-d', '--dimension', default=[], action='append', nargs=2,
213 dest='dimensions', metavar='FOO bar',
214 help='dimension to filter on')
215 subp.add_argument('--no-default-dimensions', action='store_false',
216 dest='default_dimensions', default=True,
217 help='Do not automatically add dimensions to the task')
Dirk Prankef24e6b22018-03-27 20:12:30218 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34219 help='ninja target to build and run')
dpranke030d7a6d2016-03-26 17:23:50220 subp.add_argument('extra_args', nargs='*',
221 help=('extra args to pass to the isolate to run. Use '
222 '"--" as the first arg if you need to pass '
223 'switches'))
dpranke751516a2015-10-03 01:11:34224 subp.set_defaults(func=self.CmdRun)
225
dprankefe4602312015-04-08 16:20:35226 subp = subps.add_parser('validate',
227 help='validate the config file')
dprankea5a77ca2015-07-16 23:24:17228 subp.add_argument('-f', '--config-file', metavar='PATH',
229 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50230 help='path to config file (default is %(default)s)')
dprankefe4602312015-04-08 16:20:35231 subp.set_defaults(func=self.CmdValidate)
232
Dirk Prankef24e6b22018-03-27 20:12:30233 subp = subps.add_parser('zip',
234 help='generate a .zip containing the files needed '
235 'for a given binary')
236 AddCommonOptions(subp)
237 subp.add_argument('--no-build', dest='build', default=True,
238 action='store_false',
239 help='Do not build, just isolate')
240 subp.add_argument('-j', '--jobs', type=int,
241 help='Number of jobs to pass to ninja')
242 subp.add_argument('path',
243 help='path build was generated into')
244 subp.add_argument('target',
245 help='ninja target to generate the isolate for')
246 subp.add_argument('zip_path',
247 help='path to zip file to create')
248 subp.set_defaults(func=self.CmdZip)
249
dprankefe4602312015-04-08 16:20:35250 subp = subps.add_parser('help',
251 help='Get help on a subcommand.')
252 subp.add_argument(nargs='?', action='store', dest='subcommand',
253 help='The command to get help for.')
254 subp.set_defaults(func=self.CmdHelp)
255
256 self.args = parser.parse_args(argv)
257
dprankeb2be10a2016-02-22 17:11:00258 def DumpInputFiles(self):
259
dprankef7b7eb7a2016-03-28 22:42:59260 def DumpContentsOfFilePassedTo(arg_name, path):
dprankeb2be10a2016-02-22 17:11:00261 if path and self.Exists(path):
dprankef7b7eb7a2016-03-28 22:42:59262 self.Print("\n# To recreate the file passed to %s:" % arg_name)
dprankecb4a2e242016-09-19 01:13:14263 self.Print("%% cat > %s <<EOF" % path)
dprankeb2be10a2016-02-22 17:11:00264 contents = self.ReadFile(path)
dprankef7b7eb7a2016-03-28 22:42:59265 self.Print(contents)
266 self.Print("EOF\n%\n")
dprankeb2be10a2016-02-22 17:11:00267
dprankef7b7eb7a2016-03-28 22:42:59268 if getattr(self.args, 'input_path', None):
269 DumpContentsOfFilePassedTo(
Dirk Prankef24e6b22018-03-27 20:12:30270 'argv[0] (input_path)', self.args.input_path)
dprankef7b7eb7a2016-03-28 22:42:59271 if getattr(self.args, 'swarming_targets_file', None):
272 DumpContentsOfFilePassedTo(
273 '--swarming-targets-file', self.args.swarming_targets_file)
dprankeb2be10a2016-02-22 17:11:00274
dprankefe4602312015-04-08 16:20:35275 def CmdAnalyze(self):
dpranke751516a2015-10-03 01:11:34276 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11277 return self.RunGNAnalyze(vals)
dprankefe4602312015-04-08 16:20:35278
dprankef37aebb92016-09-23 01:14:49279 def CmdExport(self):
280 self.ReadConfigFile()
281 obj = {}
282 for master, builders in self.masters.items():
283 obj[master] = {}
284 for builder in builders:
285 config = self.masters[master][builder]
286 if not config:
287 continue
288
shenghuazhang804b21542016-10-11 02:06:49289 if isinstance(config, dict):
290 args = {k: self.FlattenConfig(v)['gn_args']
291 for k, v in config.items()}
dprankef37aebb92016-09-23 01:14:49292 elif config.startswith('//'):
293 args = config
294 else:
295 args = self.FlattenConfig(config)['gn_args']
296 if 'error' in args:
297 continue
298
299 obj[master][builder] = args
300
301 # Dump object and trim trailing whitespace.
302 s = '\n'.join(l.rstrip() for l in
303 json.dumps(obj, sort_keys=True, indent=2).splitlines())
304 self.Print(s)
305 return 0
306
dprankefe4602312015-04-08 16:20:35307 def CmdGen(self):
dpranke751516a2015-10-03 01:11:34308 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11309 return self.RunGNGen(vals)
dprankefe4602312015-04-08 16:20:35310
Erik Chen42df41d2018-08-21 17:13:31311 def CmdIsolateEverything(self):
312 vals = self.Lookup()
313 return self.RunGNGenAllIsolates(vals)
314
dprankefe4602312015-04-08 16:20:35315 def CmdHelp(self):
316 if self.args.subcommand:
317 self.ParseArgs([self.args.subcommand, '--help'])
318 else:
319 self.ParseArgs(['--help'])
320
dpranke751516a2015-10-03 01:11:34321 def CmdIsolate(self):
322 vals = self.GetConfig()
323 if not vals:
324 return 1
Dirk Prankef24e6b22018-03-27 20:12:30325 if self.args.build:
326 ret = self.Build(self.args.target)
327 if ret:
328 return ret
Dirk Pranked181a1a2017-12-14 01:47:11329 return self.RunGNIsolate(vals)
dpranke751516a2015-10-03 01:11:34330
331 def CmdLookup(self):
332 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11333 cmd = self.GNCmd('gen', '_path_')
334 gn_args = self.GNArgs(vals)
335 self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args)
336 env = None
dpranke751516a2015-10-03 01:11:34337
338 self.PrintCmd(cmd, env)
339 return 0
340
341 def CmdRun(self):
342 vals = self.GetConfig()
343 if not vals:
344 return 1
Dirk Pranked181a1a2017-12-14 01:47:11345 if self.args.build:
Dirk Prankef24e6b22018-03-27 20:12:30346 ret = self.Build(self.args.target)
dpranke751516a2015-10-03 01:11:34347 if ret:
348 return ret
Dirk Pranked181a1a2017-12-14 01:47:11349 ret = self.RunGNIsolate(vals)
350 if ret:
351 return ret
dpranke751516a2015-10-03 01:11:34352
Dirk Pranke8cb6aa782017-12-16 02:31:33353 if self.args.swarmed:
Dirk Prankef24e6b22018-03-27 20:12:30354 return self._RunUnderSwarming(self.args.path, self.args.target)
Dirk Pranke8cb6aa782017-12-16 02:31:33355 else:
Dirk Prankef24e6b22018-03-27 20:12:30356 return self._RunLocallyIsolated(self.args.path, self.args.target)
357
358 def CmdZip(self):
359 ret = self.CmdIsolate()
360 if ret:
361 return ret
362
363 zip_dir = None
364 try:
365 zip_dir = self.TempDir()
366 remap_cmd = [
367 self.executable,
368 self.PathJoin(self.chromium_src_dir, 'tools', 'swarming_client',
369 'isolate.py'),
370 'remap',
Kenneth Russell2e75e2f2018-11-15 22:37:28371 '--collapse_symlinks',
Dirk Prankef24e6b22018-03-27 20:12:30372 '-s', self.PathJoin(self.args.path, self.args.target + '.isolated'),
373 '-o', zip_dir
374 ]
375 self.Run(remap_cmd)
376
377 zip_path = self.args.zip_path
378 with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as fp:
379 for root, _, files in os.walk(zip_dir):
380 for filename in files:
381 path = self.PathJoin(root, filename)
382 fp.write(path, self.RelPath(path, zip_dir))
383 finally:
384 if zip_dir:
385 self.RemoveDirectory(zip_dir)
Dirk Pranke8cb6aa782017-12-16 02:31:33386
Robert Iannucci5a9d75f62018-03-02 05:28:20387 @staticmethod
388 def _AddBaseSoftware(cmd):
389 # HACK(iannucci): These packages SHOULD NOT BE HERE.
390 # Remove method once Swarming Pool Task Templates are implemented.
391 # crbug.com/812428
392
393 # Add in required base software. This should be kept in sync with the
John Budorick9d9175372019-04-01 19:04:24394 # `chromium_swarming` recipe module in build.git. All references to
395 # `swarming_module` below are purely due to this.
Robert Iannucci5a9d75f62018-03-02 05:28:20396 cipd_packages = [
397 ('infra/python/cpython/${platform}',
398 'version:2.7.14.chromium14'),
399 ('infra/tools/luci/logdog/butler/${platform}',
400 'git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c'),
401 ('infra/tools/luci/vpython-native/${platform}',
John Budorick5a8e6ee2019-04-10 16:03:26402 'git_revision:0bff6ebf817352838b0e6f65fd6460b38c505c9c'),
Robert Iannucci5a9d75f62018-03-02 05:28:20403 ('infra/tools/luci/vpython/${platform}',
John Budorick5a8e6ee2019-04-10 16:03:26404 'git_revision:0bff6ebf817352838b0e6f65fd6460b38c505c9c'),
Robert Iannucci5a9d75f62018-03-02 05:28:20405 ]
406 for pkg, vers in cipd_packages:
407 cmd.append('--cipd-package=.swarming_module:%s:%s' % (pkg, vers))
408
409 # Add packages to $PATH
410 cmd.extend([
411 '--env-prefix=PATH', '.swarming_module',
412 '--env-prefix=PATH', '.swarming_module/bin',
413 ])
414
415 # Add cache directives for vpython.
416 vpython_cache_path = '.swarming_module_cache/vpython'
417 cmd.extend([
418 '--named-cache=swarming_module_cache_vpython', vpython_cache_path,
419 '--env-prefix=VPYTHON_VIRTUALENV_ROOT', vpython_cache_path,
420 ])
421
Dirk Pranke8cb6aa782017-12-16 02:31:33422 def _RunUnderSwarming(self, build_dir, target):
Marc-Antoine Ruel559cc4732019-03-19 22:20:46423 isolate_server = 'isolateserver.appspot.com'
424 namespace = 'default-gzip'
425 swarming_server = 'chromium-swarm.appspot.com'
Dirk Pranke8cb6aa782017-12-16 02:31:33426 # TODO(dpranke): Look up the information for the target in
427 # the //testing/buildbot.json file, if possible, so that we
428 # can determine the isolate target, command line, and additional
429 # swarming parameters, if possible.
430 #
431 # TODO(dpranke): Also, add support for sharding and merging results.
432 dimensions = []
433 for k, v in self._DefaultDimensions() + self.args.dimensions:
434 dimensions += ['-d', k, v]
435
436 cmd = [
437 self.executable,
438 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
439 'archive',
Marc-Antoine Ruel559cc4732019-03-19 22:20:46440 '-s', self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
441 '-I', isolate_server,
442 '--namespace', namespace,
Dirk Pranke8cb6aa782017-12-16 02:31:33443 ]
444 ret, out, _ = self.Run(cmd, force_verbose=False)
445 if ret:
446 return ret
447
448 isolated_hash = out.splitlines()[0].split()[0]
449 cmd = [
450 self.executable,
451 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
452 'run',
453 '-s', isolated_hash,
Marc-Antoine Ruel559cc4732019-03-19 22:20:46454 '-I', isolate_server,
455 '--namespace', namespace,
456 '-S', swarming_server,
Dirk Pranke8cb6aa782017-12-16 02:31:33457 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20458 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33459 if self.args.extra_args:
460 cmd += ['--'] + self.args.extra_args
461 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
462 return ret
463
464 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50465 cmd = [
dpranke751516a2015-10-03 01:11:34466 self.executable,
467 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
468 'run',
469 '-s',
dpranke030d7a6d2016-03-26 17:23:50470 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33471 ]
dpranke030d7a6d2016-03-26 17:23:50472 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33473 cmd += ['--'] + self.args.extra_args
474 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34475 return ret
476
Dirk Pranke8cb6aa782017-12-16 02:31:33477 def _DefaultDimensions(self):
478 if not self.args.default_dimensions:
479 return []
480
481 # This code is naive and just picks reasonable defaults per platform.
482 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40483 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33484 elif self.platform.startswith('linux'):
485 os_dim = ('os', 'Ubuntu-14.04')
486 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40487 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33488 else:
489 raise MBErr('unrecognized platform string "%s"' % self.platform)
490
491 return [('pool', 'Chrome'),
492 ('cpu', 'x86-64'),
493 os_dim]
494
dpranke0cafc162016-03-19 00:41:10495 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35496 errs = []
497
498 # Read the file to make sure it parses.
499 self.ReadConfigFile()
500
dpranke3be00142016-03-17 22:46:04501 # Build a list of all of the configs referenced by builders.
dprankefe4602312015-04-08 16:20:35502 all_configs = {}
dprankefe4602312015-04-08 16:20:35503 for master in self.masters:
dpranke3be00142016-03-17 22:46:04504 for config in self.masters[master].values():
shenghuazhang804b21542016-10-11 02:06:49505 if isinstance(config, dict):
506 for c in config.values():
dprankeb9380a12016-07-21 21:44:09507 all_configs[c] = master
508 else:
509 all_configs[config] = master
dprankefe4602312015-04-08 16:20:35510
dpranke9dd5e252016-04-14 04:23:09511 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35512 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09513 if config.startswith('//'):
514 if not self.Exists(self.ToAbsPath(config)):
515 errs.append('Unknown args file "%s" referenced from "%s".' %
516 (config, loc))
517 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35518 errs.append('Unknown config "%s" referenced from "%s".' %
519 (config, loc))
520
521 # Check that every actual config is actually referenced.
522 for config in self.configs:
523 if not config in all_configs:
524 errs.append('Unused config "%s".' % config)
525
526 # Figure out the whole list of mixins, and check that every mixin
527 # listed by a config or another mixin actually exists.
528 referenced_mixins = set()
529 for config, mixins in self.configs.items():
530 for mixin in mixins:
531 if not mixin in self.mixins:
532 errs.append('Unknown mixin "%s" referenced by config "%s".' %
533 (mixin, config))
534 referenced_mixins.add(mixin)
535
536 for mixin in self.mixins:
537 for sub_mixin in self.mixins[mixin].get('mixins', []):
538 if not sub_mixin in self.mixins:
539 errs.append('Unknown mixin "%s" referenced by mixin "%s".' %
540 (sub_mixin, mixin))
541 referenced_mixins.add(sub_mixin)
542
543 # Check that every mixin defined is actually referenced somewhere.
544 for mixin in self.mixins:
545 if not mixin in referenced_mixins:
546 errs.append('Unreferenced mixin "%s".' % mixin)
547
dpranke255085e2016-03-16 05:23:59548 # If we're checking the Chromium config, check that the 'chromium' bots
549 # which build public artifacts do not include the chrome_with_codecs mixin.
550 if self.args.config_file == self.default_config:
551 if 'chromium' in self.masters:
552 for builder in self.masters['chromium']:
553 config = self.masters['chromium'][builder]
554 def RecurseMixins(current_mixin):
555 if current_mixin == 'chrome_with_codecs':
556 errs.append('Public artifact builder "%s" can not contain the '
557 '"chrome_with_codecs" mixin.' % builder)
558 return
559 if not 'mixins' in self.mixins[current_mixin]:
560 return
561 for mixin in self.mixins[current_mixin]['mixins']:
562 RecurseMixins(mixin)
dalecurtis56fd27e2016-03-09 23:06:41563
dpranke255085e2016-03-16 05:23:59564 for mixin in self.configs[config]:
565 RecurseMixins(mixin)
566 else:
567 errs.append('Missing "chromium" master. Please update this '
568 'proprietary codecs check with the name of the master '
569 'responsible for public build artifacts.')
dalecurtis56fd27e2016-03-09 23:06:41570
dprankefe4602312015-04-08 16:20:35571 if errs:
dpranke4323c80632015-08-10 22:53:54572 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17573 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35574
dpranke0cafc162016-03-19 00:41:10575 if print_ok:
576 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35577 return 0
578
579 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30580 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34581
dprankef37aebb92016-09-23 01:14:49582 vals = self.DefaultVals()
dpranke751516a2015-10-03 01:11:34583 if self.args.builder or self.args.master or self.args.config:
584 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11585 # Re-run gn gen in order to ensure the config is consistent with the
586 # build dir.
587 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34588 return vals
589
Dirk Pranked181a1a2017-12-14 01:47:11590 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
591 'toolchain.ninja')
592 if not self.Exists(toolchain_path):
593 self.Print('Must either specify a path to an existing GN build dir '
594 'or pass in a -m/-b pair or a -c flag to specify the '
595 'configuration')
596 return {}
dpranke751516a2015-10-03 01:11:34597
Dirk Pranked181a1a2017-12-14 01:47:11598 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34599 return vals
600
dprankef37aebb92016-09-23 01:14:49601 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58602 args_contents = ""
603 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
604 if self.Exists(gn_args_path):
605 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34606 gn_args = []
607 for l in args_contents.splitlines():
608 fields = l.split(' ')
609 name = fields[0]
610 val = ' '.join(fields[2:])
611 gn_args.append('%s=%s' % (name, val))
612
dprankef37aebb92016-09-23 01:14:49613 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34614
615 def Lookup(self):
Erik Chen8eec7f42019-04-11 16:34:44616 self.ReadConfigFile()
617 config = self.ConfigFromArgs()
618 if config.startswith('//'):
619 if not self.Exists(self.ToAbsPath(config)):
620 raise MBErr('args file "%s" not found' % config)
621 vals = self.DefaultVals()
622 vals['args_file'] = config
623 else:
624 if not config in self.configs:
625 raise MBErr('Config "%s" not found in %s' %
626 (config, self.args.config_file))
627 vals = self.FlattenConfig(config)
dprankef37aebb92016-09-23 01:14:49628 return vals
dprankee0f486f2015-11-19 23:42:00629
dprankefe4602312015-04-08 16:20:35630 def ReadConfigFile(self):
631 if not self.Exists(self.args.config_file):
632 raise MBErr('config file not found at %s' % self.args.config_file)
633
634 try:
635 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
636 except SyntaxError as e:
637 raise MBErr('Failed to parse config file "%s": %s' %
638 (self.args.config_file, e))
639
dprankefe4602312015-04-08 16:20:35640 self.configs = contents['configs']
dprankefe4602312015-04-08 16:20:35641 self.masters = contents['masters']
642 self.mixins = contents['mixins']
dprankefe4602312015-04-08 16:20:35643
dprankecb4a2e242016-09-19 01:13:14644 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20645 if not self.args.isolate_map_files:
646 self.args.isolate_map_files = [self.default_isolate_map]
647
648 for f in self.args.isolate_map_files:
649 if not self.Exists(f):
650 raise MBErr('isolate map file not found at %s' % f)
651 isolate_maps = {}
652 for isolate_map in self.args.isolate_map_files:
653 try:
654 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
655 duplicates = set(isolate_map).intersection(isolate_maps)
656 if duplicates:
657 raise MBErr(
658 'Duplicate targets in isolate map files: %s.' %
659 ', '.join(duplicates))
660 isolate_maps.update(isolate_map)
661 except SyntaxError as e:
662 raise MBErr(
663 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
664 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14665
dprankefe4602312015-04-08 16:20:35666 def ConfigFromArgs(self):
667 if self.args.config:
668 if self.args.master or self.args.builder:
669 raise MBErr('Can not specific both -c/--config and -m/--master or '
670 '-b/--builder')
671
672 return self.args.config
673
674 if not self.args.master or not self.args.builder:
675 raise MBErr('Must specify either -c/--config or '
676 '(-m/--master and -b/--builder)')
677
678 if not self.args.master in self.masters:
679 raise MBErr('Master name "%s" not found in "%s"' %
680 (self.args.master, self.args.config_file))
681
682 if not self.args.builder in self.masters[self.args.master]:
683 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
684 (self.args.builder, self.args.master, self.args.config_file))
685
dprankeb9380a12016-07-21 21:44:09686 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49687 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09688 if self.args.phase is None:
689 raise MBErr('Must specify a build --phase for %s on %s' %
690 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49691 phase = str(self.args.phase)
692 if phase not in config:
693 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09694 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49695 return config[phase]
dprankeb9380a12016-07-21 21:44:09696
697 if self.args.phase is not None:
698 raise MBErr('Must not specify a build --phase for %s on %s' %
699 (self.args.builder, self.args.master))
700 return config
dprankefe4602312015-04-08 16:20:35701
702 def FlattenConfig(self, config):
703 mixins = self.configs[config]
dprankef37aebb92016-09-23 01:14:49704 vals = self.DefaultVals()
dprankefe4602312015-04-08 16:20:35705
706 visited = []
707 self.FlattenMixins(mixins, vals, visited)
708 return vals
709
dprankef37aebb92016-09-23 01:14:49710 def DefaultVals(self):
711 return {
712 'args_file': '',
713 'cros_passthrough': False,
714 'gn_args': '',
dprankef37aebb92016-09-23 01:14:49715 }
716
dprankefe4602312015-04-08 16:20:35717 def FlattenMixins(self, mixins, vals, visited):
718 for m in mixins:
719 if m not in self.mixins:
720 raise MBErr('Unknown mixin "%s"' % m)
dprankeee5b51f62015-04-09 00:03:22721
dprankefe4602312015-04-08 16:20:35722 visited.append(m)
723
724 mixin_vals = self.mixins[m]
dpranke73ed0d62016-04-25 19:18:34725
726 if 'cros_passthrough' in mixin_vals:
727 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
Dirk Pranke6b99f072017-04-05 00:58:30728 if 'args_file' in mixin_vals:
729 if vals['args_file']:
730 raise MBErr('args_file specified multiple times in mixins '
731 'for %s on %s' % (self.args.builder, self.args.master))
732 vals['args_file'] = mixin_vals['args_file']
dprankefe4602312015-04-08 16:20:35733 if 'gn_args' in mixin_vals:
734 if vals['gn_args']:
735 vals['gn_args'] += ' ' + mixin_vals['gn_args']
736 else:
737 vals['gn_args'] = mixin_vals['gn_args']
dpranke73ed0d62016-04-25 19:18:34738
dprankefe4602312015-04-08 16:20:35739 if 'mixins' in mixin_vals:
740 self.FlattenMixins(mixin_vals['mixins'], vals, visited)
741 return vals
742
Takuto Ikuta9dffd7e2018-09-05 01:04:00743 def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
Dirk Prankef24e6b22018-03-27 20:12:30744 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50745
Takuto Ikuta9dffd7e2018-09-05 01:04:00746 if check:
747 cmd = self.GNCmd('gen', build_dir, '--check')
748 else:
749 cmd = self.GNCmd('gen', build_dir)
dprankeeca4a782016-04-14 01:42:38750 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:09751 if compute_inputs_for_analyze:
752 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:38753
754 # Since GN hasn't run yet, the build directory may not even exist.
755 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
756
757 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:54758 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:39759
dpranke751516a2015-10-03 01:11:34760 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:39761 # We need GN to generate the list of runtime dependencies for
762 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:14763 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:39764 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:00765 path = self.args.swarming_targets_file
766 if not self.Exists(path):
767 self.WriteFailureAndRaise('"%s" does not exist' % path,
768 output_path=None)
769 contents = self.ReadFile(path)
Erik Chen42df41d2018-08-21 17:13:31770 isolate_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:00771
dprankecb4a2e242016-09-19 01:13:14772 isolate_map = self.ReadIsolateMap()
Dirk Pranke7a7e9b62019-02-17 01:46:25773 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
774 isolate_map, build_dir)
775
Erik Chen42df41d2018-08-21 17:13:31776 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:00777 if err:
Dirk Pranke7a7e9b62019-02-17 01:46:25778 raise MBErr(err)
dpranke74559b52015-06-10 21:20:39779
dpranke751516a2015-10-03 01:11:34780 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:14781 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:39782 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
783
dprankefe4602312015-04-08 16:20:35784 ret, _, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:40785 if ret:
Dirk Pranke7a7e9b62019-02-17 01:46:25786 # If `gn gen` failed, we should exit early rather than trying to
787 # generate isolates. Run() will have already logged any error output.
788 self.Print('GN gen failed: %d' % ret)
789 return ret
dpranke74559b52015-06-10 21:20:39790
Erik Chen42df41d2018-08-21 17:13:31791 if getattr(self.args, 'swarming_targets_file', None):
Dirk Pranke7a7e9b62019-02-17 01:46:25792 self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
Erik Chen42df41d2018-08-21 17:13:31793
794 return 0
795
796 def RunGNGenAllIsolates(self, vals):
797 """
798 This command generates all .isolate files.
799
800 This command assumes that "mb.py gen" has already been run, as it relies on
801 "gn ls" to fetch all gn targets. If uses that output, combined with the
802 isolate_map, to determine all isolates that can be generated for the current
803 gn configuration.
804 """
805 build_dir = self.args.path
806 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
807 force_verbose=False)
808 if ret:
809 # If `gn ls` failed, we should exit early rather than trying to
810 # generate isolates.
811 self.Print('GN ls failed: %d' % ret)
812 return ret
813
814 # Create a reverse map from isolate label to isolate dict.
815 isolate_map = self.ReadIsolateMap()
816 isolate_dict_map = {}
817 for key, isolate_dict in isolate_map.iteritems():
818 isolate_dict_map[isolate_dict['label']] = isolate_dict
819 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
820
821 runtime_deps = []
822
823 isolate_targets = []
824 # For every GN target, look up the isolate dict.
825 for line in output.splitlines():
826 target = line.strip()
827 if target in isolate_dict_map:
828 if isolate_dict_map[target]['type'] == 'additional_compile_target':
829 # By definition, additional_compile_targets are not tests, so we
830 # shouldn't generate isolates for them.
831 continue
832
833 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
834 runtime_deps.append(target)
835
Dirk Pranke7a7e9b62019-02-17 01:46:25836 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
837 isolate_map, build_dir)
838
Erik Chen42df41d2018-08-21 17:13:31839 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
840 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
841 cmd = self.GNCmd('gen', build_dir)
842 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
843 self.Run(cmd)
844
845 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
846
Dirk Pranke7a7e9b62019-02-17 01:46:25847 def RemovePossiblyStaleRuntimeDepsFiles(self, vals, targets, isolate_map,
848 build_dir):
849 # TODO(crbug.com/932700): Because `gn gen --runtime-deps-list-file`
850 # puts the runtime_deps file in different locations based on the actual
851 # type of a target, we may end up with multiple possible runtime_deps
852 # files in a given build directory, where some of the entries might be
853 # stale (since we might be reusing an existing build directory).
854 #
855 # We need to be able to get the right one reliably; you might think
856 # we can just pick the newest file, but because GN won't update timestamps
857 # if the contents of the files change, an older runtime_deps
858 # file might actually be the one we should use over a newer one (see
859 # crbug.com/932387 for a more complete explanation and example).
860 #
861 # In order to avoid this, we need to delete any possible runtime_deps
862 # files *prior* to running GN. As long as the files aren't actually
863 # needed during the build, this hopefully will not cause unnecessary
864 # build work, and so it should be safe.
865 #
866 # Ultimately, we should just make sure we get the runtime_deps files
867 # in predictable locations so we don't have this issue at all, and
868 # that's what crbug.com/932700 is for.
869 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, targets, isolate_map)
870 for rpaths in possible_rpaths.values():
871 for rpath in rpaths:
872 path = self.ToAbsPath(build_dir, rpath)
873 if self.Exists(path):
874 self.RemoveFile(path)
875
Erik Chen42df41d2018-08-21 17:13:31876 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
877 """
878 Generates isolates for a list of ninja targets.
879
880 Ninja targets are transformed to GN targets via isolate_map.
881
882 This function assumes that a previous invocation of "mb.py gen" has
883 generated runtime deps for all targets.
884 """
Dirk Pranke7a7e9b62019-02-17 01:46:25885 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, ninja_targets,
886 isolate_map)
887
888 for target, rpaths in possible_rpaths.items():
889 # TODO(crbug.com/932700): We don't know where each .runtime_deps
890 # file might be, but assuming we called
891 # RemovePossiblyStaleRuntimeDepsFiles prior to calling `gn gen`,
892 # there should only be one file.
893 found_one = False
894 path_to_use = None
895 for r in rpaths:
896 path = self.ToAbsPath(build_dir, r)
897 if self.Exists(path):
898 if found_one:
899 raise MBErr('Found more than one of %s' % ', '.join(rpaths))
900 path_to_use = path
901 found_one = True
902
903 if not found_one:
904 raise MBErr('Did not find any of %s' % ', '.join(rpaths))
905
906 command, extra_files = self.GetIsolateCommand(target, vals)
907 runtime_deps = self.ReadFile(path_to_use).splitlines()
908
909 canonical_target = target.replace(':','_').replace('/','_')
910 self.WriteIsolateFiles(build_dir, command, canonical_target, runtime_deps,
911 extra_files)
912
913 def PossibleRuntimeDepsPaths(self, vals, ninja_targets, isolate_map):
914 """Returns a map of targets to possible .runtime_deps paths.
915
916 Each ninja target maps on to a GN label, but depending on the type
917 of the GN target, `gn gen --runtime-deps-list-file` will write
918 the .runtime_deps files into different locations. Unfortunately, in
919 some cases we don't actually know which of multiple locations will
920 actually be used, so we return all plausible candidates.
921
922 The paths that are returned are relative to the build directory.
923 """
924
jbudoricke3c4f95e2016-04-28 23:17:38925 android = 'target_os="android"' in vals['gn_args']
Dirk Pranke26de05aec2019-04-03 19:18:38926 ios = 'target_os="ios"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:42927 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:30928 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Dirk Pranke7a7e9b62019-02-17 01:46:25929 possible_runtime_deps_rpaths = {}
Erik Chen42df41d2018-08-21 17:13:31930 for target in ninja_targets:
John Budorick39f14962019-04-11 23:03:20931 target_type = isolate_map[target]['type']
932 label = isolate_map[target]['label']
933 stamp_runtime_deps = 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')
Erik Chen42df41d2018-08-21 17:13:31934 # TODO(https://crbug.com/876065): 'official_tests' use
935 # type='additional_compile_target' to isolate tests. This is not the
936 # intended use for 'additional_compile_target'.
John Budorick39f14962019-04-11 23:03:20937 if (target_type == 'additional_compile_target' and
Erik Chen42df41d2018-08-21 17:13:31938 target != 'official_tests'):
939 # By definition, additional_compile_targets are not tests, so we
940 # shouldn't generate isolates for them.
Dirk Pranke7a7e9b62019-02-17 01:46:25941 raise MBErr('Cannot generate isolate for %s since it is an '
942 'additional_compile_target.' % target)
John Budorick39f14962019-04-11 23:03:20943 elif fuchsia or ios or target_type == 'generated_script':
944 # iOS and Fuchsia targets end up as groups.
945 # generated_script targets are always actions.
946 rpaths = [stamp_runtime_deps]
Erik Chen42df41d2018-08-21 17:13:31947 elif android:
jbudoricke3c4f95e2016-04-28 23:17:38948 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:02949 # will result in runtime_deps associated with the stamp file, while the
950 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:44951 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:25952 rpaths = [
dprankecb4a2e242016-09-19 01:13:14953 target + '.runtime_deps',
John Budorick39f14962019-04-11 23:03:20954 stamp_runtime_deps]
955 elif (target_type == 'script' or
956 target_type == 'fuzzer' or
dprankecb4a2e242016-09-19 01:13:14957 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:11958 # For script targets, the build target is usually a group,
959 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:49960 # for the label, which lives under the obj/ directory, but it may
961 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:44962 label = isolate_map[target]['label']
John Budorick39f14962019-04-11 23:03:20963 rpaths = [stamp_runtime_deps]
Nico Weberd94b71a2018-02-22 22:00:30964 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:25965 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:49966 else:
Dirk Pranke7a7e9b62019-02-17 01:46:25967 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:30968 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:25969 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:52970 else:
Dirk Pranke7a7e9b62019-02-17 01:46:25971 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:02972
Dirk Pranke7a7e9b62019-02-17 01:46:25973 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:41974
Dirk Pranke7a7e9b62019-02-17 01:46:25975 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:34976
977 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:30978 target = self.args.target
dprankecb4a2e242016-09-19 01:13:14979 isolate_map = self.ReadIsolateMap()
980 err, labels = self.MapTargetsToLabels(isolate_map, [target])
981 if err:
982 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:25983
dprankecb4a2e242016-09-19 01:13:14984 label = labels[0]
dpranke751516a2015-10-03 01:11:34985
Dirk Prankef24e6b22018-03-27 20:12:30986 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:14987 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:34988
dprankeeca4a782016-04-14 01:42:38989 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:20990 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:34991 if ret:
dpranke030d7a6d2016-03-26 17:23:50992 if out:
993 self.Print(out)
dpranke751516a2015-10-03 01:11:34994 return ret
995
996 runtime_deps = out.splitlines()
997
998 self.WriteIsolateFiles(build_dir, command, target, runtime_deps,
999 extra_files)
1000
1001 ret, _, _ = self.Run([
1002 self.executable,
1003 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1004 'check',
1005 '-i',
1006 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1007 '-s',
1008 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1009 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301010
dprankefe4602312015-04-08 16:20:351011 return ret
1012
dpranke751516a2015-10-03 01:11:341013 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps,
1014 extra_files):
1015 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
1016 self.WriteFile(isolate_path,
1017 pprint.pformat({
1018 'variables': {
1019 'command': command,
1020 'files': sorted(runtime_deps + extra_files),
1021 }
1022 }) + '\n')
1023
1024 self.WriteJSON(
1025 {
1026 'args': [
1027 '--isolated',
1028 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1029 '--isolate',
1030 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1031 ],
1032 'dir': self.chromium_src_dir,
1033 'version': 1,
1034 },
1035 isolate_path + 'd.gen.json',
1036 )
1037
dprankecb4a2e242016-09-19 01:13:141038 def MapTargetsToLabels(self, isolate_map, targets):
1039 labels = []
1040 err = ''
1041
dprankecb4a2e242016-09-19 01:13:141042 for target in targets:
1043 if target == 'all':
1044 labels.append(target)
1045 elif target.startswith('//'):
1046 labels.append(target)
1047 else:
1048 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421049 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141050 err += ('test target "%s" type is unknown\n' % target)
1051 else:
thakis024d6f32017-05-16 23:21:421052 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141053 else:
1054 err += ('target "%s" not found in '
1055 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1056
1057 return err, labels
1058
dprankeeca4a782016-04-14 01:42:381059 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461060 if self.platform == 'linux2':
1061 subdir, exe = 'linux64', 'gn'
1062 elif self.platform == 'darwin':
1063 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251064 elif self.platform == 'aix6':
1065 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461066 else:
1067 subdir, exe = 'win', 'gn.exe'
1068
1069 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081070 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521071
dprankecb4a2e242016-09-19 01:13:141072
dprankeeca4a782016-04-14 01:42:381073 def GNArgs(self, vals):
dpranke73ed0d62016-04-25 19:18:341074 if vals['cros_passthrough']:
1075 if not 'GN_ARGS' in os.environ:
1076 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1077 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161078 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301079 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341080 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351081 if vals['gn_args']:
1082 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341083 else:
1084 gn_args = vals['gn_args']
1085
dpranked0c138b2016-04-13 18:28:471086 if self.args.goma_dir:
1087 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381088
agrieve41d21a72016-04-14 18:02:261089 android_version_code = self.args.android_version_code
1090 if android_version_code:
1091 gn_args += ' android_default_version_code="%s"' % android_version_code
1092
1093 android_version_name = self.args.android_version_name
1094 if android_version_name:
1095 gn_args += ' android_default_version_name="%s"' % android_version_name
1096
dprankeeca4a782016-04-14 01:42:381097 # Canonicalize the arg string into a sorted, newline-separated list
1098 # of key-value pairs, and de-dup the keys if need be so that only
1099 # the last instance of each arg is listed.
1100 gn_args = gn_helpers.ToGNString(gn_helpers.FromGNArgs(gn_args))
1101
Ben Pastene65ccf6132018-11-08 00:47:591102 # If we're using the Simple Chrome SDK, add a comment at the top that
1103 # points to the doc. This must happen after the gn_helpers.ToGNString()
1104 # call above since gn_helpers strips comments.
1105 if vals['cros_passthrough']:
1106 simplechrome_comment = [
1107 '# These args are generated via the Simple Chrome SDK. See the link',
1108 '# below for more details:',
1109 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
1110 ]
1111 gn_args = '%s\n%s' % ('\n'.join(simplechrome_comment), gn_args)
1112
dpranke9dd5e252016-04-14 04:23:091113 args_file = vals.get('args_file', None)
1114 if args_file:
1115 gn_args = ('import("%s")\n' % vals['args_file']) + gn_args
dprankeeca4a782016-04-14 01:42:381116 return gn_args
dprankefe4602312015-04-08 16:20:351117
dprankecb4a2e242016-09-19 01:13:141118 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071119 isolate_map = self.ReadIsolateMap()
1120
Scott Graham3be4b4162017-09-12 00:41:411121 is_android = 'target_os="android"' in vals['gn_args']
Benjamin Pastene3bce864e2018-04-14 01:16:321122 is_simplechrome = vals.get('cros_passthrough', False)
Scott Graham3be4b4162017-09-12 00:41:411123 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:301124 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061125
kylechar39705682017-01-19 14:37:231126 # This should be true if tests with type='windowed_test_launcher' are
1127 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081128 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1129 # backends. Currently, tests are executed for the headless and X11 backends
1130 # and both can run under Xvfb.
1131 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1132 # backend.
Scott Graham3be4b4162017-09-12 00:41:411133 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251134
1135 asan = 'is_asan=true' in vals['gn_args']
1136 msan = 'is_msan=true' in vals['gn_args']
1137 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411138 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251139
dprankecb4a2e242016-09-19 01:13:141140 test_type = isolate_map[target]['type']
dprankefe0d35e2016-02-05 02:43:591141
dprankecb4a2e242016-09-19 01:13:141142 executable = isolate_map[target].get('executable', target)
bsheedy9c16ed62019-04-10 20:32:111143 executable_suffix = isolate_map[target].get(
1144 'executable_suffix', '.exe' if is_win else '')
dprankefe0d35e2016-02-05 02:43:591145
dprankea55584f12015-07-22 00:52:471146 cmdline = []
Andrii Shyshkalovc158e0102018-01-10 05:52:001147 extra_files = [
1148 '../../.vpython',
1149 '../../testing/test_env.py',
1150 ]
dpranked8113582015-06-05 20:08:251151
dprankecb4a2e242016-09-19 01:13:141152 if test_type == 'nontest':
1153 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1154 output_path=None)
1155
Roberto Carrillo1460da852018-12-14 17:10:391156 if test_type == 'fuzzer':
1157 cmdline = [
1158 '../../testing/test_env.py',
1159 '../../tools/code_coverage/run_fuzz_target.py',
1160 '--fuzzer', './' + target,
1161 '--output-dir', '${ISOLATED_OUTDIR}',
1162 '--timeout', '3600']
1163 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011164 cmdline = []
1165 if asan:
John Budorick31cdce62019-04-03 20:56:111166 cmdline += [os.path.join('bin', 'run_with_asan'), '--']
John Budorick8c4203042019-03-19 17:22:011167 cmdline += [
John Budorickfb97a852017-12-20 20:10:191168 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041169 '../../build/android/test_wrapper/logdog_wrapper.py',
1170 '--target', target,
hzl9ae14452017-04-04 23:38:021171 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481172 '--store-tombstones']
Scott Graham3be4b4162017-09-12 00:41:411173 elif is_fuchsia and test_type != 'script':
John Budorickfb97a852017-12-20 20:10:191174 cmdline = [
1175 '../../testing/test_env.py',
1176 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441177 '--test-launcher-bot-mode',
John Budorickfb97a852017-12-20 20:10:191178 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321179 elif is_simplechrome and test_type != 'script':
1180 cmdline = [
1181 '../../testing/test_env.py',
1182 os.path.join('bin', 'run_%s' % target),
1183 ]
kylechar39705682017-01-19 14:37:231184 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001185 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471186 cmdline = [
dprankefe0d35e2016-02-05 02:43:591187 '../../testing/xvfb.py',
dprankefe0d35e2016-02-05 02:43:591188 './' + str(executable) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591189 '--test-launcher-bot-mode',
1190 '--asan=%d' % asan,
1191 '--msan=%d' % msan,
1192 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411193 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471194 ]
1195 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471196 cmdline = [
1197 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591198 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251199 '--test-launcher-bot-mode',
1200 '--asan=%d' % asan,
1201 '--msan=%d' % msan,
1202 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411203 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471204 ]
dpranke6abd8652015-08-28 03:21:111205 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241206 cmdline = []
1207 # If we're testing a CrOS simplechrome build, assume we need to launch a
1208 # VM first. So prepend the command to run with the VM launcher.
1209 # TODO(bpastene): Differentiate between CrOS VM and hardware tests.
1210 if is_simplechrome:
1211 cmdline = [os.path.join('bin', 'launch_cros_vm')]
1212 cmdline += [
dpranke6abd8652015-08-28 03:21:111213 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141214 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591215 ]
John Budorick56bfa782019-04-10 02:36:211216 elif test_type == 'generated_script':
1217 cmdline = [
1218 '../../testing/test_env.py',
1219 isolate_map[target]['script'],
1220 ]
Dirk Prankef24e6b22018-03-27 20:12:301221 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471222 cmdline = [
1223 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591224 ]
dprankea55584f12015-07-22 00:52:471225 else:
1226 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1227 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251228
Abhishek Arya2f5f7342018-06-13 16:59:441229 if is_win and asan:
Alexander Dunaev384ba482018-03-21 17:56:121230 # Sandbox is not yet supported by ASAN for Windows.
1231 # Perhaps this is only needed for tests that use the sandbox?
1232 cmdline.append('--no-sandbox')
1233
dprankecb4a2e242016-09-19 01:13:141234 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591235
dpranked8113582015-06-05 20:08:251236 return cmdline, extra_files
1237
dpranke74559b52015-06-10 21:20:391238 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331239 return self.PathJoin(self.chromium_src_dir,
1240 self.ToSrcRelPath(build_path),
1241 *comps)
dpranked8113582015-06-05 20:08:251242
dprankeee5b51f62015-04-09 00:03:221243 def ToSrcRelPath(self, path):
1244 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501245 if path.startswith('//'):
1246 return path[2:].replace('/', self.sep)
1247 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351248
Dirk Pranke0fd41bcd2015-06-19 00:05:501249 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141250 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501251 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001252 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501253 if ret:
1254 return ret
1255
Dirk Prankef24e6b22018-03-27 20:12:301256 build_path = self.args.path
1257 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141258 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301259 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141260 gn_output_path = output_path + '.gn'
1261
dpranke7837fc362015-11-19 03:54:161262 inp = self.ReadInputJSON(['files', 'test_targets',
1263 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321264 if self.args.verbose:
1265 self.Print()
1266 self.Print('analyze input:')
1267 self.PrintJSON(inp)
1268 self.Print()
1269
dpranke76734662015-04-16 02:17:501270
dpranke7c5f614d2015-07-22 23:43:391271 # This shouldn't normally happen, but could due to unusual race conditions,
1272 # like a try job that gets scheduled before a patch lands but runs after
1273 # the patch has landed.
1274 if not inp['files']:
1275 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161276 self.WriteJSON({
1277 'status': 'No dependency',
1278 'compile_targets': [],
1279 'test_targets': [],
1280 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391281 return 0
1282
dprankecb4a2e242016-09-19 01:13:141283 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161284 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561285
dprankecb4a2e242016-09-19 01:13:141286 isolate_map = self.ReadIsolateMap()
1287 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1288 isolate_map, inp['additional_compile_targets'])
1289 if err:
1290 raise MBErr(err)
1291
1292 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1293 isolate_map, inp['test_targets'])
1294 if err:
1295 raise MBErr(err)
1296 labels_to_targets = {}
1297 for i, label in enumerate(gn_inp['test_targets']):
1298 labels_to_targets[label] = inp['test_targets'][i]
1299
dprankef61de2f2015-05-14 04:09:561300 try:
dprankecb4a2e242016-09-19 01:13:141301 self.WriteJSON(gn_inp, gn_input_path)
1302 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
1303 ret, _, _ = self.Run(cmd, force_verbose=True)
1304 if ret:
1305 return ret
dpranke067d0142015-05-14 22:52:451306
dprankecb4a2e242016-09-19 01:13:141307 gn_outp_str = self.ReadFile(gn_output_path)
1308 try:
1309 gn_outp = json.loads(gn_outp_str)
1310 except Exception as e:
1311 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1312 % (repr(gn_outp_str), str(e)))
1313 raise
1314
1315 outp = {}
1316 if 'status' in gn_outp:
1317 outp['status'] = gn_outp['status']
1318 if 'error' in gn_outp:
1319 outp['error'] = gn_outp['error']
1320 if 'invalid_targets' in gn_outp:
1321 outp['invalid_targets'] = gn_outp['invalid_targets']
1322 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491323 all_input_compile_targets = sorted(
1324 set(inp['test_targets'] + inp['additional_compile_targets']))
1325
1326 # If we're building 'all', we can throw away the rest of the targets
1327 # since they're redundant.
dpranke385a3102016-09-20 22:04:081328 if 'all' in gn_outp['compile_targets']:
1329 outp['compile_targets'] = ['all']
1330 else:
Dirk Pranke45165072017-11-08 04:57:491331 outp['compile_targets'] = gn_outp['compile_targets']
1332
1333 # crbug.com/736215: When GN returns targets back, for targets in
1334 # the default toolchain, GN will have generated a phony ninja
1335 # target matching the label, and so we can safely (and easily)
1336 # transform any GN label into the matching ninja target. For
1337 # targets in other toolchains, though, GN doesn't generate the
1338 # phony targets, and we don't know how to turn the labels into
1339 # compile targets. In this case, we also conservatively give up
1340 # and build everything. Probably the right thing to do here is
1341 # to have GN return the compile targets directly.
1342 if any("(" in target for target in outp['compile_targets']):
1343 self.Print('WARNING: targets with non-default toolchains were '
1344 'found, building everything instead.')
1345 outp['compile_targets'] = all_input_compile_targets
1346 else:
dpranke385a3102016-09-20 22:04:081347 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491348 label.replace('//', '') for label in outp['compile_targets']]
1349
1350 # Windows has a maximum command line length of 8k; even Linux
1351 # maxes out at 128k; if analyze returns a *really long* list of
1352 # targets, we just give up and conservatively build everything instead.
1353 # Probably the right thing here is for ninja to support response
1354 # files as input on the command line
1355 # (see https://github.com/ninja-build/ninja/issues/1355).
1356 if len(' '.join(outp['compile_targets'])) > 7*1024:
1357 self.Print('WARNING: Too many compile targets were affected.')
1358 self.Print('WARNING: Building everything instead to avoid '
1359 'command-line length issues.')
1360 outp['compile_targets'] = all_input_compile_targets
1361
1362
dprankecb4a2e242016-09-19 01:13:141363 if 'test_targets' in gn_outp:
1364 outp['test_targets'] = [
1365 labels_to_targets[label] for label in gn_outp['test_targets']]
1366
1367 if self.args.verbose:
1368 self.Print()
1369 self.Print('analyze output:')
1370 self.PrintJSON(outp)
1371 self.Print()
1372
1373 self.WriteJSON(outp, output_path)
1374
dprankef61de2f2015-05-14 04:09:561375 finally:
dprankecb4a2e242016-09-19 01:13:141376 if self.Exists(gn_input_path):
1377 self.RemoveFile(gn_input_path)
1378 if self.Exists(gn_output_path):
1379 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351380
1381 return 0
1382
dpranked8113582015-06-05 20:08:251383 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301384 path = self.args.input_path
1385 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351386 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321387 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351388
1389 try:
1390 inp = json.loads(self.ReadFile(path))
1391 except Exception as e:
1392 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321393 (path, e), output_path)
dpranked8113582015-06-05 20:08:251394
1395 for k in required_keys:
1396 if not k in inp:
1397 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1398 output_path)
dprankefe4602312015-04-08 16:20:351399
1400 return inp
1401
dpranked5b2b9432015-06-23 16:55:301402 def WriteFailureAndRaise(self, msg, output_path):
1403 if output_path:
dprankee0547cd2015-09-15 01:27:401404 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351405 raise MBErr(msg)
1406
dprankee0547cd2015-09-15 01:27:401407 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321408 try:
dprankee0547cd2015-09-15 01:27:401409 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1410 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321411 except Exception as e:
1412 raise MBErr('Error %s writing to the output path "%s"' %
1413 (e, path))
dprankefe4602312015-04-08 16:20:351414
aneeshmde50f472016-04-01 01:13:101415 def CheckCompile(self, master, builder):
1416 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1417 url = urllib2.quote(url_template.format(master=master, builder=builder),
1418 safe=':/()?=')
1419 try:
1420 builds = json.loads(self.Fetch(url))
1421 except Exception as e:
1422 return str(e)
1423 successes = sorted(
1424 [int(x) for x in builds.keys() if "text" in builds[x] and
1425 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1426 reverse=True)
1427 if not successes:
1428 return "no successful builds"
1429 build = builds[str(successes[0])]
1430 step_names = set([step["name"] for step in build["steps"]])
1431 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1432 if compile_indicators & step_names:
1433 return "compiles"
1434 return "does not compile"
1435
dpranke3cec199c2015-09-22 23:29:021436 def PrintCmd(self, cmd, env):
1437 if self.platform == 'win32':
1438 env_prefix = 'set '
1439 env_quoter = QuoteForSet
1440 shell_quoter = QuoteForCmd
1441 else:
1442 env_prefix = ''
1443 env_quoter = pipes.quote
1444 shell_quoter = pipes.quote
1445
1446 def print_env(var):
1447 if env and var in env:
1448 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1449
dprankeec079262016-06-07 02:21:201450 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021451
dpranke8c2cfd32015-09-17 20:12:331452 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351453 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021454 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351455
dprankecda00332015-04-11 04:18:321456 def PrintJSON(self, obj):
1457 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1458
dpranke751516a2015-10-03 01:11:341459 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301460 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381461 if self.platform == 'win32':
1462 # On Windows use the batch script since there is no exe
1463 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1464 else:
1465 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341466 if self.args.jobs:
1467 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1468 ninja_cmd.append(target)
1469 ret, _, _ = self.Run(ninja_cmd, force_verbose=False, buffer_output=False)
1470 return ret
1471
1472 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351473 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401474 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021475 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351476 if self.args.dryrun:
1477 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401478
dpranke751516a2015-10-03 01:11:341479 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401480 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341481 if ret:
1482 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351483 if out:
dprankeee5b51f62015-04-09 00:03:221484 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351485 if err:
dprankeee5b51f62015-04-09 00:03:221486 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351487 return ret, out, err
1488
dpranke751516a2015-10-03 01:11:341489 def Call(self, cmd, env=None, buffer_output=True):
1490 if buffer_output:
1491 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1492 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1493 env=env)
1494 out, err = p.communicate()
1495 else:
1496 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1497 env=env)
1498 p.wait()
1499 out = err = ''
dprankefe4602312015-04-08 16:20:351500 return p.returncode, out, err
1501
1502 def ExpandUser(self, path):
1503 # This function largely exists so it can be overridden for testing.
1504 return os.path.expanduser(path)
1505
1506 def Exists(self, path):
1507 # This function largely exists so it can be overridden for testing.
1508 return os.path.exists(path)
1509
dpranke867bcf4a2016-03-14 22:28:321510 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501511 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321512 f = urllib2.urlopen(url)
1513 contents = f.read()
1514 f.close()
1515 return contents
1516
dprankec3441d12015-06-23 23:01:351517 def MaybeMakeDirectory(self, path):
1518 try:
1519 os.makedirs(path)
1520 except OSError, e:
1521 if e.errno != errno.EEXIST:
1522 raise
1523
dpranke8c2cfd32015-09-17 20:12:331524 def PathJoin(self, *comps):
1525 # This function largely exists so it can be overriden for testing.
1526 return os.path.join(*comps)
1527
dpranke030d7a6d2016-03-26 17:23:501528 def Print(self, *args, **kwargs):
1529 # This function largely exists so it can be overridden for testing.
1530 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101531 if kwargs.get('stream', sys.stdout) == sys.stdout:
1532 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501533
dprankefe4602312015-04-08 16:20:351534 def ReadFile(self, path):
1535 # This function largely exists so it can be overriden for testing.
1536 with open(path) as fp:
1537 return fp.read()
1538
dpranke030d7a6d2016-03-26 17:23:501539 def RelPath(self, path, start='.'):
1540 # This function largely exists so it can be overriden for testing.
1541 return os.path.relpath(path, start)
1542
dprankef61de2f2015-05-14 04:09:561543 def RemoveFile(self, path):
1544 # This function largely exists so it can be overriden for testing.
1545 os.remove(path)
1546
dprankec161aa92015-09-14 20:21:131547 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331548 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131549 # In other places in chromium, we often have to retry this command
1550 # because we're worried about other processes still holding on to
1551 # file handles, but when MB is invoked, it will be early enough in the
1552 # build that their should be no other processes to interfere. We
1553 # can change this if need be.
1554 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1555 else:
1556 shutil.rmtree(abs_path, ignore_errors=True)
1557
Dirk Prankef24e6b22018-03-27 20:12:301558 def TempDir(self):
1559 # This function largely exists so it can be overriden for testing.
1560 return tempfile.mkdtemp(prefix='mb_')
1561
dprankef61de2f2015-05-14 04:09:561562 def TempFile(self, mode='w'):
1563 # This function largely exists so it can be overriden for testing.
1564 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1565
dprankee0547cd2015-09-15 01:27:401566 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351567 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401568 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301569 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351570 with open(path, 'w') as fp:
1571 return fp.write(contents)
1572
dprankef61de2f2015-05-14 04:09:561573
dprankefe4602312015-04-08 16:20:351574class MBErr(Exception):
1575 pass
1576
1577
dpranke3cec199c2015-09-22 23:29:021578# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1579# details of this next section, which handles escaping command lines
1580# so that they can be copied and pasted into a cmd window.
1581UNSAFE_FOR_SET = set('^<>&|')
1582UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1583ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1584
1585
1586def QuoteForSet(arg):
1587 if any(a in UNSAFE_FOR_SET for a in arg):
1588 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1589 return arg
1590
1591
1592def QuoteForCmd(arg):
1593 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021594 if arg == '' or ' ' in arg or '"' in arg:
1595 quote_re = re.compile(r'(\\*)"')
1596 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1597
1598 # Then check to see if the arg contains any metacharacters other than
1599 # double quotes; if it does, quote everything (including the double
1600 # quotes) for safety.
1601 if any(a in UNSAFE_FOR_CMD for a in arg):
1602 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1603 return arg
1604
1605
dprankefe4602312015-04-08 16:20:351606if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591607 sys.exit(main(sys.argv[1:]))