blob: d95f0f18bd3eaf800c6fb22b3fdd9d30c3f84787 [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
Stephen Martinisb40a6852019-07-23 01:48:30111 parser = argparse.ArgumentParser(
112 prog='mb', description='mb (meta-build) is a python wrapper around GN. '
113 'See the user guide in '
114 '//tools/mb/docs/user_guide.md for detailed usage '
115 'instructions.')
116
dprankefe4602312015-04-08 16:20:35117 subps = parser.add_subparsers()
118
119 subp = subps.add_parser('analyze',
Stephen Martinis239c35a2019-07-22 19:34:40120 description='Analyze whether changes to a set of '
121 'files will cause a set of binaries to '
122 'be rebuilt.')
dprankefe4602312015-04-08 16:20:35123 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30124 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35125 help='path build was generated into.')
Dirk Prankef24e6b22018-03-27 20:12:30126 subp.add_argument('input_path',
dprankefe4602312015-04-08 16:20:35127 help='path to a file containing the input arguments '
128 'as a JSON object.')
Dirk Prankef24e6b22018-03-27 20:12:30129 subp.add_argument('output_path',
dprankefe4602312015-04-08 16:20:35130 help='path to a file containing the output arguments '
131 'as a JSON object.')
Debrian Figueroaae51d0d2019-07-22 18:04:11132 subp.add_argument('--json-output',
Debrian Figueroaae582232019-07-17 01:54:45133 help='Write errors to json.output')
dprankefe4602312015-04-08 16:20:35134 subp.set_defaults(func=self.CmdAnalyze)
135
dprankef37aebb92016-09-23 01:14:49136 subp = subps.add_parser('export',
Stephen Martinis239c35a2019-07-22 19:34:40137 description='Print out the expanded configuration '
138 'for each builder as a JSON object.')
dprankef37aebb92016-09-23 01:14:49139 subp.add_argument('-f', '--config-file', metavar='PATH',
140 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50141 help='path to config file (default is %(default)s)')
dprankef37aebb92016-09-23 01:14:49142 subp.add_argument('-g', '--goma-dir',
143 help='path to goma directory')
144 subp.set_defaults(func=self.CmdExport)
145
dprankefe4602312015-04-08 16:20:35146 subp = subps.add_parser('gen',
Stephen Martinis239c35a2019-07-22 19:34:40147 description='Generate a new set of build files.')
dprankefe4602312015-04-08 16:20:35148 AddCommonOptions(subp)
dpranke74559b52015-06-10 21:20:39149 subp.add_argument('--swarming-targets-file',
150 help='save runtime dependencies for targets listed '
151 'in file.')
Debrian Figueroaae51d0d2019-07-22 18:04:11152 subp.add_argument('--json-output',
Debrian Figueroaae582232019-07-17 01:54:45153 help='Write errors to json.output')
Dirk Prankef24e6b22018-03-27 20:12:30154 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35155 help='path to generate build into')
156 subp.set_defaults(func=self.CmdGen)
157
Erik Chen42df41d2018-08-21 17:13:31158 subp = subps.add_parser('isolate-everything',
Stephen Martinis239c35a2019-07-22 19:34:40159 description='Generates a .isolate for all targets. '
160 'Requires that mb.py gen has already '
161 'been run.')
Erik Chen42df41d2018-08-21 17:13:31162 AddCommonOptions(subp)
163 subp.set_defaults(func=self.CmdIsolateEverything)
164 subp.add_argument('path',
165 help='path build was generated into')
166
dpranke751516a2015-10-03 01:11:34167 subp = subps.add_parser('isolate',
Stephen Martinis239c35a2019-07-22 19:34:40168 description='Generate the .isolate files for a '
169 'given binary.')
dpranke751516a2015-10-03 01:11:34170 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30171 subp.add_argument('--no-build', dest='build', default=True,
172 action='store_false',
173 help='Do not build, just isolate')
174 subp.add_argument('-j', '--jobs', type=int,
175 help='Number of jobs to pass to ninja')
176 subp.add_argument('path',
dpranke751516a2015-10-03 01:11:34177 help='path build was generated into')
Dirk Prankef24e6b22018-03-27 20:12:30178 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34179 help='ninja target to generate the isolate for')
180 subp.set_defaults(func=self.CmdIsolate)
181
dprankefe4602312015-04-08 16:20:35182 subp = subps.add_parser('lookup',
Stephen Martinis239c35a2019-07-22 19:34:40183 description='Look up the command for a given '
184 'config or builder.')
dprankefe4602312015-04-08 16:20:35185 AddCommonOptions(subp)
Garrett Beatyb6cee042019-04-22 18:42:09186 subp.add_argument('--quiet', default=False, action='store_true',
187 help='Print out just the arguments, '
188 'do not emulate the output of the gen subcommand.')
189 subp.add_argument('--recursive', default=False, action='store_true',
190 help='Lookup arguments from imported files, '
191 'implies --quiet')
dprankefe4602312015-04-08 16:20:35192 subp.set_defaults(func=self.CmdLookup)
193
dpranke030d7a6d2016-03-26 17:23:50194 subp = subps.add_parser(
Stephen Martinis239c35a2019-07-22 19:34:40195 'run', formatter_class=argparse.RawDescriptionHelpFormatter)
dpranke030d7a6d2016-03-26 17:23:50196 subp.description = (
197 'Build, isolate, and run the given binary with the command line\n'
198 'listed in the isolate. You may pass extra arguments after the\n'
199 'target; use "--" if the extra arguments need to include switches.\n'
200 '\n'
201 'Examples:\n'
202 '\n'
203 ' % tools/mb/mb.py run -m chromium.linux -b "Linux Builder" \\\n'
204 ' //out/Default content_browsertests\n'
205 '\n'
206 ' % tools/mb/mb.py run out/Default content_browsertests\n'
207 '\n'
208 ' % tools/mb/mb.py run out/Default content_browsertests -- \\\n'
209 ' --test-launcher-retry-limit=0'
210 '\n'
211 )
dpranke751516a2015-10-03 01:11:34212 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30213 subp.add_argument('-j', '--jobs', type=int,
dpranke751516a2015-10-03 01:11:34214 help='Number of jobs to pass to ninja')
215 subp.add_argument('--no-build', dest='build', default=True,
216 action='store_false',
217 help='Do not build, just isolate and run')
Dirk Prankef24e6b22018-03-27 20:12:30218 subp.add_argument('path',
dpranke030d7a6d2016-03-26 17:23:50219 help=('path to generate build into (or use).'
220 ' This can be either a regular path or a '
221 'GN-style source-relative path like '
222 '//out/Default.'))
Dirk Pranke8cb6aa782017-12-16 02:31:33223 subp.add_argument('-s', '--swarmed', action='store_true',
224 help='Run under swarming with the default dimensions')
225 subp.add_argument('-d', '--dimension', default=[], action='append', nargs=2,
226 dest='dimensions', metavar='FOO bar',
227 help='dimension to filter on')
228 subp.add_argument('--no-default-dimensions', action='store_false',
229 dest='default_dimensions', default=True,
230 help='Do not automatically add dimensions to the task')
Dirk Prankef24e6b22018-03-27 20:12:30231 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34232 help='ninja target to build and run')
dpranke030d7a6d2016-03-26 17:23:50233 subp.add_argument('extra_args', nargs='*',
234 help=('extra args to pass to the isolate to run. Use '
235 '"--" as the first arg if you need to pass '
236 'switches'))
dpranke751516a2015-10-03 01:11:34237 subp.set_defaults(func=self.CmdRun)
238
dprankefe4602312015-04-08 16:20:35239 subp = subps.add_parser('validate',
Stephen Martinis239c35a2019-07-22 19:34:40240 description='Validate the config file.')
dprankea5a77ca2015-07-16 23:24:17241 subp.add_argument('-f', '--config-file', metavar='PATH',
242 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50243 help='path to config file (default is %(default)s)')
dprankefe4602312015-04-08 16:20:35244 subp.set_defaults(func=self.CmdValidate)
245
Dirk Prankef24e6b22018-03-27 20:12:30246 subp = subps.add_parser('zip',
Stephen Martinis239c35a2019-07-22 19:34:40247 description='Generate a .zip containing the files '
248 'needed for a given binary.')
Dirk Prankef24e6b22018-03-27 20:12:30249 AddCommonOptions(subp)
250 subp.add_argument('--no-build', dest='build', default=True,
251 action='store_false',
252 help='Do not build, just isolate')
253 subp.add_argument('-j', '--jobs', type=int,
254 help='Number of jobs to pass to ninja')
255 subp.add_argument('path',
256 help='path build was generated into')
257 subp.add_argument('target',
258 help='ninja target to generate the isolate for')
259 subp.add_argument('zip_path',
260 help='path to zip file to create')
261 subp.set_defaults(func=self.CmdZip)
262
dprankefe4602312015-04-08 16:20:35263 subp = subps.add_parser('help',
264 help='Get help on a subcommand.')
265 subp.add_argument(nargs='?', action='store', dest='subcommand',
266 help='The command to get help for.')
267 subp.set_defaults(func=self.CmdHelp)
268
269 self.args = parser.parse_args(argv)
270
dprankeb2be10a2016-02-22 17:11:00271 def DumpInputFiles(self):
272
dprankef7b7eb7a2016-03-28 22:42:59273 def DumpContentsOfFilePassedTo(arg_name, path):
dprankeb2be10a2016-02-22 17:11:00274 if path and self.Exists(path):
dprankef7b7eb7a2016-03-28 22:42:59275 self.Print("\n# To recreate the file passed to %s:" % arg_name)
dprankecb4a2e242016-09-19 01:13:14276 self.Print("%% cat > %s <<EOF" % path)
dprankeb2be10a2016-02-22 17:11:00277 contents = self.ReadFile(path)
dprankef7b7eb7a2016-03-28 22:42:59278 self.Print(contents)
279 self.Print("EOF\n%\n")
dprankeb2be10a2016-02-22 17:11:00280
dprankef7b7eb7a2016-03-28 22:42:59281 if getattr(self.args, 'input_path', None):
282 DumpContentsOfFilePassedTo(
Dirk Prankef24e6b22018-03-27 20:12:30283 'argv[0] (input_path)', self.args.input_path)
dprankef7b7eb7a2016-03-28 22:42:59284 if getattr(self.args, 'swarming_targets_file', None):
285 DumpContentsOfFilePassedTo(
286 '--swarming-targets-file', self.args.swarming_targets_file)
dprankeb2be10a2016-02-22 17:11:00287
dprankefe4602312015-04-08 16:20:35288 def CmdAnalyze(self):
dpranke751516a2015-10-03 01:11:34289 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11290 return self.RunGNAnalyze(vals)
dprankefe4602312015-04-08 16:20:35291
dprankef37aebb92016-09-23 01:14:49292 def CmdExport(self):
293 self.ReadConfigFile()
294 obj = {}
295 for master, builders in self.masters.items():
296 obj[master] = {}
297 for builder in builders:
298 config = self.masters[master][builder]
299 if not config:
300 continue
301
shenghuazhang804b21542016-10-11 02:06:49302 if isinstance(config, dict):
303 args = {k: self.FlattenConfig(v)['gn_args']
304 for k, v in config.items()}
dprankef37aebb92016-09-23 01:14:49305 elif config.startswith('//'):
306 args = config
307 else:
308 args = self.FlattenConfig(config)['gn_args']
309 if 'error' in args:
310 continue
311
312 obj[master][builder] = args
313
314 # Dump object and trim trailing whitespace.
315 s = '\n'.join(l.rstrip() for l in
316 json.dumps(obj, sort_keys=True, indent=2).splitlines())
317 self.Print(s)
318 return 0
319
dprankefe4602312015-04-08 16:20:35320 def CmdGen(self):
dpranke751516a2015-10-03 01:11:34321 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11322 return self.RunGNGen(vals)
dprankefe4602312015-04-08 16:20:35323
Erik Chen42df41d2018-08-21 17:13:31324 def CmdIsolateEverything(self):
325 vals = self.Lookup()
326 return self.RunGNGenAllIsolates(vals)
327
dprankefe4602312015-04-08 16:20:35328 def CmdHelp(self):
329 if self.args.subcommand:
330 self.ParseArgs([self.args.subcommand, '--help'])
331 else:
332 self.ParseArgs(['--help'])
333
dpranke751516a2015-10-03 01:11:34334 def CmdIsolate(self):
335 vals = self.GetConfig()
336 if not vals:
337 return 1
Dirk Prankef24e6b22018-03-27 20:12:30338 if self.args.build:
339 ret = self.Build(self.args.target)
340 if ret:
341 return ret
Dirk Pranked181a1a2017-12-14 01:47:11342 return self.RunGNIsolate(vals)
dpranke751516a2015-10-03 01:11:34343
344 def CmdLookup(self):
345 vals = self.Lookup()
Garrett Beatyb6cee042019-04-22 18:42:09346 gn_args = self.GNArgs(vals, expand_imports=self.args.recursive)
347 if self.args.quiet or self.args.recursive:
348 self.Print(gn_args, end='')
349 else:
350 cmd = self.GNCmd('gen', '_path_')
351 self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args)
352 env = None
dpranke751516a2015-10-03 01:11:34353
Garrett Beatyb6cee042019-04-22 18:42:09354 self.PrintCmd(cmd, env)
dpranke751516a2015-10-03 01:11:34355 return 0
356
357 def CmdRun(self):
358 vals = self.GetConfig()
359 if not vals:
360 return 1
Dirk Pranked181a1a2017-12-14 01:47:11361 if self.args.build:
Dirk Pranke5f22a822019-05-23 22:55:25362 self.Print('')
Dirk Prankef24e6b22018-03-27 20:12:30363 ret = self.Build(self.args.target)
dpranke751516a2015-10-03 01:11:34364 if ret:
365 return ret
Dirk Pranke5f22a822019-05-23 22:55:25366
367 self.Print('')
Dirk Pranked181a1a2017-12-14 01:47:11368 ret = self.RunGNIsolate(vals)
369 if ret:
370 return ret
dpranke751516a2015-10-03 01:11:34371
Dirk Pranke5f22a822019-05-23 22:55:25372 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33373 if self.args.swarmed:
Dirk Prankef24e6b22018-03-27 20:12:30374 return self._RunUnderSwarming(self.args.path, self.args.target)
Dirk Pranke8cb6aa782017-12-16 02:31:33375 else:
Dirk Prankef24e6b22018-03-27 20:12:30376 return self._RunLocallyIsolated(self.args.path, self.args.target)
377
378 def CmdZip(self):
379 ret = self.CmdIsolate()
380 if ret:
381 return ret
382
383 zip_dir = None
384 try:
385 zip_dir = self.TempDir()
386 remap_cmd = [
387 self.executable,
388 self.PathJoin(self.chromium_src_dir, 'tools', 'swarming_client',
389 'isolate.py'),
390 'remap',
Kenneth Russell2e75e2f2018-11-15 22:37:28391 '--collapse_symlinks',
Dirk Prankef24e6b22018-03-27 20:12:30392 '-s', self.PathJoin(self.args.path, self.args.target + '.isolated'),
393 '-o', zip_dir
394 ]
395 self.Run(remap_cmd)
396
397 zip_path = self.args.zip_path
Bruce Dawsonda03df292019-08-29 17:54:20398 with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED,
399 allowZip64=True) as fp:
Dirk Prankef24e6b22018-03-27 20:12:30400 for root, _, files in os.walk(zip_dir):
401 for filename in files:
402 path = self.PathJoin(root, filename)
403 fp.write(path, self.RelPath(path, zip_dir))
404 finally:
405 if zip_dir:
406 self.RemoveDirectory(zip_dir)
Dirk Pranke8cb6aa782017-12-16 02:31:33407
Robert Iannucci5a9d75f62018-03-02 05:28:20408 @staticmethod
409 def _AddBaseSoftware(cmd):
410 # HACK(iannucci): These packages SHOULD NOT BE HERE.
411 # Remove method once Swarming Pool Task Templates are implemented.
412 # crbug.com/812428
413
414 # Add in required base software. This should be kept in sync with the
John Budorick9d9175372019-04-01 19:04:24415 # `chromium_swarming` recipe module in build.git. All references to
416 # `swarming_module` below are purely due to this.
Robert Iannucci5a9d75f62018-03-02 05:28:20417 cipd_packages = [
418 ('infra/python/cpython/${platform}',
smut22dcd68e2019-06-25 23:33:27419 'version:2.7.15.chromium14'),
Robert Iannucci5a9d75f62018-03-02 05:28:20420 ('infra/tools/luci/logdog/butler/${platform}',
421 'git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c'),
422 ('infra/tools/luci/vpython-native/${platform}',
smut8e3640d2019-08-02 21:31:09423 'git_revision:98a268c6432f18aedd55d62b9621765316dc2a16'),
Robert Iannucci5a9d75f62018-03-02 05:28:20424 ('infra/tools/luci/vpython/${platform}',
smut8e3640d2019-08-02 21:31:09425 'git_revision:98a268c6432f18aedd55d62b9621765316dc2a16'),
Robert Iannucci5a9d75f62018-03-02 05:28:20426 ]
427 for pkg, vers in cipd_packages:
428 cmd.append('--cipd-package=.swarming_module:%s:%s' % (pkg, vers))
429
430 # Add packages to $PATH
431 cmd.extend([
432 '--env-prefix=PATH', '.swarming_module',
433 '--env-prefix=PATH', '.swarming_module/bin',
434 ])
435
436 # Add cache directives for vpython.
437 vpython_cache_path = '.swarming_module_cache/vpython'
438 cmd.extend([
439 '--named-cache=swarming_module_cache_vpython', vpython_cache_path,
440 '--env-prefix=VPYTHON_VIRTUALENV_ROOT', vpython_cache_path,
441 ])
442
Dirk Pranke8cb6aa782017-12-16 02:31:33443 def _RunUnderSwarming(self, build_dir, target):
Marc-Antoine Ruel559cc4732019-03-19 22:20:46444 isolate_server = 'isolateserver.appspot.com'
445 namespace = 'default-gzip'
446 swarming_server = 'chromium-swarm.appspot.com'
Dirk Pranke8cb6aa782017-12-16 02:31:33447 # TODO(dpranke): Look up the information for the target in
448 # the //testing/buildbot.json file, if possible, so that we
449 # can determine the isolate target, command line, and additional
450 # swarming parameters, if possible.
451 #
452 # TODO(dpranke): Also, add support for sharding and merging results.
453 dimensions = []
454 for k, v in self._DefaultDimensions() + self.args.dimensions:
455 dimensions += ['-d', k, v]
456
457 cmd = [
458 self.executable,
459 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
460 'archive',
Marc-Antoine Ruel559cc4732019-03-19 22:20:46461 '-s', self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
462 '-I', isolate_server,
463 '--namespace', namespace,
Dirk Pranke8cb6aa782017-12-16 02:31:33464 ]
Dirk Pranke5f22a822019-05-23 22:55:25465
466 # Talking to the isolateserver may fail because we're not logged in.
467 # We trap the command explicitly and rewrite the error output so that
468 # the error message is actually correct for a Chromium check out.
469 self.PrintCmd(cmd, env=None)
470 ret, out, err = self.Run(cmd, force_verbose=False)
Dirk Pranke8cb6aa782017-12-16 02:31:33471 if ret:
Dirk Pranke5f22a822019-05-23 22:55:25472 self.Print(' -> returned %d' % ret)
473 if out:
474 self.Print(out, end='')
475 if err:
476 # The swarming client will return an exit code of 2 (via
477 # argparse.ArgumentParser.error()) and print a message to indicate
478 # that auth failed, so we have to parse the message to check.
479 if (ret == 2 and 'Please login to' in err):
480 err = err.replace(' auth.py', ' tools/swarming_client/auth.py')
481 self.Print(err, end='', file=sys.stderr)
482
Dirk Pranke8cb6aa782017-12-16 02:31:33483 return ret
484
485 isolated_hash = out.splitlines()[0].split()[0]
486 cmd = [
487 self.executable,
488 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
489 'run',
490 '-s', isolated_hash,
Marc-Antoine Ruel559cc4732019-03-19 22:20:46491 '-I', isolate_server,
492 '--namespace', namespace,
493 '-S', swarming_server,
Dirk Pranke8cb6aa782017-12-16 02:31:33494 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20495 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33496 if self.args.extra_args:
497 cmd += ['--'] + self.args.extra_args
Dirk Pranke5f22a822019-05-23 22:55:25498 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33499 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
500 return ret
501
502 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50503 cmd = [
dpranke751516a2015-10-03 01:11:34504 self.executable,
505 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
506 'run',
507 '-s',
dpranke030d7a6d2016-03-26 17:23:50508 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33509 ]
dpranke030d7a6d2016-03-26 17:23:50510 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33511 cmd += ['--'] + self.args.extra_args
512 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34513 return ret
514
Dirk Pranke8cb6aa782017-12-16 02:31:33515 def _DefaultDimensions(self):
516 if not self.args.default_dimensions:
517 return []
518
519 # This code is naive and just picks reasonable defaults per platform.
520 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40521 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33522 elif self.platform.startswith('linux'):
Takuto Ikuta169663b2019-08-05 16:21:32523 os_dim = ('os', 'Ubuntu-16.04')
Dirk Pranke8cb6aa782017-12-16 02:31:33524 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40525 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33526 else:
527 raise MBErr('unrecognized platform string "%s"' % self.platform)
528
529 return [('pool', 'Chrome'),
530 ('cpu', 'x86-64'),
531 os_dim]
532
dpranke0cafc162016-03-19 00:41:10533 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35534 errs = []
535
536 # Read the file to make sure it parses.
537 self.ReadConfigFile()
538
dpranke3be00142016-03-17 22:46:04539 # Build a list of all of the configs referenced by builders.
dprankefe4602312015-04-08 16:20:35540 all_configs = {}
dprankefe4602312015-04-08 16:20:35541 for master in self.masters:
dpranke3be00142016-03-17 22:46:04542 for config in self.masters[master].values():
shenghuazhang804b21542016-10-11 02:06:49543 if isinstance(config, dict):
544 for c in config.values():
dprankeb9380a12016-07-21 21:44:09545 all_configs[c] = master
546 else:
547 all_configs[config] = master
dprankefe4602312015-04-08 16:20:35548
dpranke9dd5e252016-04-14 04:23:09549 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35550 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09551 if config.startswith('//'):
552 if not self.Exists(self.ToAbsPath(config)):
553 errs.append('Unknown args file "%s" referenced from "%s".' %
554 (config, loc))
555 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35556 errs.append('Unknown config "%s" referenced from "%s".' %
557 (config, loc))
558
559 # Check that every actual config is actually referenced.
560 for config in self.configs:
561 if not config in all_configs:
562 errs.append('Unused config "%s".' % config)
563
564 # Figure out the whole list of mixins, and check that every mixin
565 # listed by a config or another mixin actually exists.
566 referenced_mixins = set()
567 for config, mixins in self.configs.items():
568 for mixin in mixins:
569 if not mixin in self.mixins:
570 errs.append('Unknown mixin "%s" referenced by config "%s".' %
571 (mixin, config))
572 referenced_mixins.add(mixin)
573
574 for mixin in self.mixins:
575 for sub_mixin in self.mixins[mixin].get('mixins', []):
576 if not sub_mixin in self.mixins:
577 errs.append('Unknown mixin "%s" referenced by mixin "%s".' %
578 (sub_mixin, mixin))
579 referenced_mixins.add(sub_mixin)
580
581 # Check that every mixin defined is actually referenced somewhere.
582 for mixin in self.mixins:
583 if not mixin in referenced_mixins:
584 errs.append('Unreferenced mixin "%s".' % mixin)
585
dpranke255085e2016-03-16 05:23:59586 # If we're checking the Chromium config, check that the 'chromium' bots
587 # which build public artifacts do not include the chrome_with_codecs mixin.
588 if self.args.config_file == self.default_config:
589 if 'chromium' in self.masters:
590 for builder in self.masters['chromium']:
591 config = self.masters['chromium'][builder]
592 def RecurseMixins(current_mixin):
593 if current_mixin == 'chrome_with_codecs':
594 errs.append('Public artifact builder "%s" can not contain the '
595 '"chrome_with_codecs" mixin.' % builder)
596 return
597 if not 'mixins' in self.mixins[current_mixin]:
598 return
599 for mixin in self.mixins[current_mixin]['mixins']:
600 RecurseMixins(mixin)
dalecurtis56fd27e2016-03-09 23:06:41601
dpranke255085e2016-03-16 05:23:59602 for mixin in self.configs[config]:
603 RecurseMixins(mixin)
604 else:
605 errs.append('Missing "chromium" master. Please update this '
606 'proprietary codecs check with the name of the master '
607 'responsible for public build artifacts.')
dalecurtis56fd27e2016-03-09 23:06:41608
dprankefe4602312015-04-08 16:20:35609 if errs:
dpranke4323c80632015-08-10 22:53:54610 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17611 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35612
dpranke0cafc162016-03-19 00:41:10613 if print_ok:
614 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35615 return 0
616
617 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30618 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34619
dprankef37aebb92016-09-23 01:14:49620 vals = self.DefaultVals()
dpranke751516a2015-10-03 01:11:34621 if self.args.builder or self.args.master or self.args.config:
622 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11623 # Re-run gn gen in order to ensure the config is consistent with the
624 # build dir.
625 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34626 return vals
627
Dirk Pranked181a1a2017-12-14 01:47:11628 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
629 'toolchain.ninja')
630 if not self.Exists(toolchain_path):
631 self.Print('Must either specify a path to an existing GN build dir '
632 'or pass in a -m/-b pair or a -c flag to specify the '
633 'configuration')
634 return {}
dpranke751516a2015-10-03 01:11:34635
Dirk Pranked181a1a2017-12-14 01:47:11636 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34637 return vals
638
dprankef37aebb92016-09-23 01:14:49639 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58640 args_contents = ""
641 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
642 if self.Exists(gn_args_path):
643 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34644 gn_args = []
645 for l in args_contents.splitlines():
646 fields = l.split(' ')
647 name = fields[0]
648 val = ' '.join(fields[2:])
649 gn_args.append('%s=%s' % (name, val))
650
dprankef37aebb92016-09-23 01:14:49651 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34652
653 def Lookup(self):
Erik Chen238f4ac2019-04-12 19:02:50654 vals = self.ReadIOSBotConfig()
655 if not vals:
656 self.ReadConfigFile()
657 config = self.ConfigFromArgs()
658 if config.startswith('//'):
659 if not self.Exists(self.ToAbsPath(config)):
660 raise MBErr('args file "%s" not found' % config)
661 vals = self.DefaultVals()
662 vals['args_file'] = config
663 else:
664 if not config in self.configs:
665 raise MBErr('Config "%s" not found in %s' %
666 (config, self.args.config_file))
667 vals = self.FlattenConfig(config)
668 return vals
669
670 def ReadIOSBotConfig(self):
671 if not self.args.master or not self.args.builder:
672 return {}
673 path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
674 self.args.master, self.args.builder + '.json')
675 if not self.Exists(path):
676 return {}
677
678 contents = json.loads(self.ReadFile(path))
679 gn_args = ' '.join(contents.get('gn_args', []))
680
681 vals = self.DefaultVals()
682 vals['gn_args'] = gn_args
dprankef37aebb92016-09-23 01:14:49683 return vals
dprankee0f486f2015-11-19 23:42:00684
dprankefe4602312015-04-08 16:20:35685 def ReadConfigFile(self):
686 if not self.Exists(self.args.config_file):
687 raise MBErr('config file not found at %s' % self.args.config_file)
688
689 try:
690 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
691 except SyntaxError as e:
692 raise MBErr('Failed to parse config file "%s": %s' %
693 (self.args.config_file, e))
694
dprankefe4602312015-04-08 16:20:35695 self.configs = contents['configs']
696 self.masters = contents['masters']
697 self.mixins = contents['mixins']
dprankefe4602312015-04-08 16:20:35698
dprankecb4a2e242016-09-19 01:13:14699 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20700 if not self.args.isolate_map_files:
701 self.args.isolate_map_files = [self.default_isolate_map]
702
703 for f in self.args.isolate_map_files:
704 if not self.Exists(f):
705 raise MBErr('isolate map file not found at %s' % f)
706 isolate_maps = {}
707 for isolate_map in self.args.isolate_map_files:
708 try:
709 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
710 duplicates = set(isolate_map).intersection(isolate_maps)
711 if duplicates:
712 raise MBErr(
713 'Duplicate targets in isolate map files: %s.' %
714 ', '.join(duplicates))
715 isolate_maps.update(isolate_map)
716 except SyntaxError as e:
717 raise MBErr(
718 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
719 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14720
dprankefe4602312015-04-08 16:20:35721 def ConfigFromArgs(self):
722 if self.args.config:
723 if self.args.master or self.args.builder:
724 raise MBErr('Can not specific both -c/--config and -m/--master or '
725 '-b/--builder')
726
727 return self.args.config
728
729 if not self.args.master or not self.args.builder:
730 raise MBErr('Must specify either -c/--config or '
731 '(-m/--master and -b/--builder)')
732
733 if not self.args.master in self.masters:
734 raise MBErr('Master name "%s" not found in "%s"' %
735 (self.args.master, self.args.config_file))
736
737 if not self.args.builder in self.masters[self.args.master]:
738 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
739 (self.args.builder, self.args.master, self.args.config_file))
740
dprankeb9380a12016-07-21 21:44:09741 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49742 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09743 if self.args.phase is None:
744 raise MBErr('Must specify a build --phase for %s on %s' %
745 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49746 phase = str(self.args.phase)
747 if phase not in config:
748 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09749 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49750 return config[phase]
dprankeb9380a12016-07-21 21:44:09751
752 if self.args.phase is not None:
753 raise MBErr('Must not specify a build --phase for %s on %s' %
754 (self.args.builder, self.args.master))
755 return config
dprankefe4602312015-04-08 16:20:35756
757 def FlattenConfig(self, config):
758 mixins = self.configs[config]
dprankef37aebb92016-09-23 01:14:49759 vals = self.DefaultVals()
dprankefe4602312015-04-08 16:20:35760
761 visited = []
762 self.FlattenMixins(mixins, vals, visited)
763 return vals
764
dprankef37aebb92016-09-23 01:14:49765 def DefaultVals(self):
766 return {
767 'args_file': '',
768 'cros_passthrough': False,
769 'gn_args': '',
dprankef37aebb92016-09-23 01:14:49770 }
771
dprankefe4602312015-04-08 16:20:35772 def FlattenMixins(self, mixins, vals, visited):
773 for m in mixins:
774 if m not in self.mixins:
775 raise MBErr('Unknown mixin "%s"' % m)
dprankeee5b51f62015-04-09 00:03:22776
dprankefe4602312015-04-08 16:20:35777 visited.append(m)
778
779 mixin_vals = self.mixins[m]
dpranke73ed0d62016-04-25 19:18:34780
781 if 'cros_passthrough' in mixin_vals:
782 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
Dirk Pranke6b99f072017-04-05 00:58:30783 if 'args_file' in mixin_vals:
784 if vals['args_file']:
785 raise MBErr('args_file specified multiple times in mixins '
786 'for %s on %s' % (self.args.builder, self.args.master))
787 vals['args_file'] = mixin_vals['args_file']
dprankefe4602312015-04-08 16:20:35788 if 'gn_args' in mixin_vals:
789 if vals['gn_args']:
790 vals['gn_args'] += ' ' + mixin_vals['gn_args']
791 else:
792 vals['gn_args'] = mixin_vals['gn_args']
dpranke73ed0d62016-04-25 19:18:34793
dprankefe4602312015-04-08 16:20:35794 if 'mixins' in mixin_vals:
795 self.FlattenMixins(mixin_vals['mixins'], vals, visited)
796 return vals
797
Takuto Ikuta9dffd7e2018-09-05 01:04:00798 def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
Dirk Prankef24e6b22018-03-27 20:12:30799 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50800
Takuto Ikuta9dffd7e2018-09-05 01:04:00801 if check:
802 cmd = self.GNCmd('gen', build_dir, '--check')
803 else:
804 cmd = self.GNCmd('gen', build_dir)
dprankeeca4a782016-04-14 01:42:38805 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:09806 if compute_inputs_for_analyze:
807 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:38808
809 # Since GN hasn't run yet, the build directory may not even exist.
810 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
811
812 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:54813 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:39814
dpranke751516a2015-10-03 01:11:34815 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:39816 # We need GN to generate the list of runtime dependencies for
817 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:14818 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:39819 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:00820 path = self.args.swarming_targets_file
821 if not self.Exists(path):
822 self.WriteFailureAndRaise('"%s" does not exist' % path,
823 output_path=None)
824 contents = self.ReadFile(path)
Erik Chen42df41d2018-08-21 17:13:31825 isolate_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:00826
dprankecb4a2e242016-09-19 01:13:14827 isolate_map = self.ReadIsolateMap()
Dirk Pranke7a7e9b62019-02-17 01:46:25828 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
829 isolate_map, build_dir)
830
Erik Chen42df41d2018-08-21 17:13:31831 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:00832 if err:
Dirk Pranke7a7e9b62019-02-17 01:46:25833 raise MBErr(err)
dpranke74559b52015-06-10 21:20:39834
dpranke751516a2015-10-03 01:11:34835 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:14836 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:39837 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
838
Debrian Figueroaae582232019-07-17 01:54:45839 ret, output, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:40840 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:11841 if self.args.json_output:
Debrian Figueroaae582232019-07-17 01:54:45842 # write errors to json.output
843 self.WriteJSON({'output': output}, self.args.json_output)
Dirk Pranke7a7e9b62019-02-17 01:46:25844 # If `gn gen` failed, we should exit early rather than trying to
845 # generate isolates. Run() will have already logged any error output.
846 self.Print('GN gen failed: %d' % ret)
847 return ret
dpranke74559b52015-06-10 21:20:39848
Erik Chen42df41d2018-08-21 17:13:31849 if getattr(self.args, 'swarming_targets_file', None):
Nico Weber0fd016762019-08-25 14:48:14850 ret = self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
Erik Chen42df41d2018-08-21 17:13:31851
Nico Weber0fd016762019-08-25 14:48:14852 return ret
Erik Chen42df41d2018-08-21 17:13:31853
854 def RunGNGenAllIsolates(self, vals):
855 """
856 This command generates all .isolate files.
857
858 This command assumes that "mb.py gen" has already been run, as it relies on
859 "gn ls" to fetch all gn targets. If uses that output, combined with the
860 isolate_map, to determine all isolates that can be generated for the current
861 gn configuration.
862 """
863 build_dir = self.args.path
864 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
865 force_verbose=False)
866 if ret:
867 # If `gn ls` failed, we should exit early rather than trying to
868 # generate isolates.
869 self.Print('GN ls failed: %d' % ret)
870 return ret
871
872 # Create a reverse map from isolate label to isolate dict.
873 isolate_map = self.ReadIsolateMap()
874 isolate_dict_map = {}
875 for key, isolate_dict in isolate_map.iteritems():
876 isolate_dict_map[isolate_dict['label']] = isolate_dict
877 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
878
879 runtime_deps = []
880
881 isolate_targets = []
882 # For every GN target, look up the isolate dict.
883 for line in output.splitlines():
884 target = line.strip()
885 if target in isolate_dict_map:
886 if isolate_dict_map[target]['type'] == 'additional_compile_target':
887 # By definition, additional_compile_targets are not tests, so we
888 # shouldn't generate isolates for them.
889 continue
890
891 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
892 runtime_deps.append(target)
893
Dirk Pranke7a7e9b62019-02-17 01:46:25894 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
895 isolate_map, build_dir)
896
Erik Chen42df41d2018-08-21 17:13:31897 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
898 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
899 cmd = self.GNCmd('gen', build_dir)
900 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
901 self.Run(cmd)
902
903 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
904
Dirk Pranke7a7e9b62019-02-17 01:46:25905 def RemovePossiblyStaleRuntimeDepsFiles(self, vals, targets, isolate_map,
906 build_dir):
907 # TODO(crbug.com/932700): Because `gn gen --runtime-deps-list-file`
908 # puts the runtime_deps file in different locations based on the actual
909 # type of a target, we may end up with multiple possible runtime_deps
910 # files in a given build directory, where some of the entries might be
911 # stale (since we might be reusing an existing build directory).
912 #
913 # We need to be able to get the right one reliably; you might think
914 # we can just pick the newest file, but because GN won't update timestamps
915 # if the contents of the files change, an older runtime_deps
916 # file might actually be the one we should use over a newer one (see
917 # crbug.com/932387 for a more complete explanation and example).
918 #
919 # In order to avoid this, we need to delete any possible runtime_deps
920 # files *prior* to running GN. As long as the files aren't actually
921 # needed during the build, this hopefully will not cause unnecessary
922 # build work, and so it should be safe.
923 #
924 # Ultimately, we should just make sure we get the runtime_deps files
925 # in predictable locations so we don't have this issue at all, and
926 # that's what crbug.com/932700 is for.
927 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, targets, isolate_map)
928 for rpaths in possible_rpaths.values():
929 for rpath in rpaths:
930 path = self.ToAbsPath(build_dir, rpath)
931 if self.Exists(path):
932 self.RemoveFile(path)
933
Erik Chen42df41d2018-08-21 17:13:31934 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
935 """
936 Generates isolates for a list of ninja targets.
937
938 Ninja targets are transformed to GN targets via isolate_map.
939
940 This function assumes that a previous invocation of "mb.py gen" has
941 generated runtime deps for all targets.
942 """
Dirk Pranke7a7e9b62019-02-17 01:46:25943 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, ninja_targets,
944 isolate_map)
945
946 for target, rpaths in possible_rpaths.items():
947 # TODO(crbug.com/932700): We don't know where each .runtime_deps
948 # file might be, but assuming we called
949 # RemovePossiblyStaleRuntimeDepsFiles prior to calling `gn gen`,
950 # there should only be one file.
951 found_one = False
952 path_to_use = None
953 for r in rpaths:
954 path = self.ToAbsPath(build_dir, r)
955 if self.Exists(path):
956 if found_one:
957 raise MBErr('Found more than one of %s' % ', '.join(rpaths))
958 path_to_use = path
959 found_one = True
960
961 if not found_one:
962 raise MBErr('Did not find any of %s' % ', '.join(rpaths))
963
964 command, extra_files = self.GetIsolateCommand(target, vals)
965 runtime_deps = self.ReadFile(path_to_use).splitlines()
966
967 canonical_target = target.replace(':','_').replace('/','_')
Nico Weber0fd016762019-08-25 14:48:14968 ret = self.WriteIsolateFiles(build_dir, command, canonical_target,
969 runtime_deps, vals, extra_files)
970 if ret:
971 return ret
972 return 0
Dirk Pranke7a7e9b62019-02-17 01:46:25973
974 def PossibleRuntimeDepsPaths(self, vals, ninja_targets, isolate_map):
975 """Returns a map of targets to possible .runtime_deps paths.
976
977 Each ninja target maps on to a GN label, but depending on the type
978 of the GN target, `gn gen --runtime-deps-list-file` will write
979 the .runtime_deps files into different locations. Unfortunately, in
980 some cases we don't actually know which of multiple locations will
981 actually be used, so we return all plausible candidates.
982
983 The paths that are returned are relative to the build directory.
984 """
985
jbudoricke3c4f95e2016-04-28 23:17:38986 android = 'target_os="android"' in vals['gn_args']
Dirk Pranke26de05aec2019-04-03 19:18:38987 ios = 'target_os="ios"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:42988 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:30989 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Dirk Pranke7a7e9b62019-02-17 01:46:25990 possible_runtime_deps_rpaths = {}
Erik Chen42df41d2018-08-21 17:13:31991 for target in ninja_targets:
John Budorick39f14962019-04-11 23:03:20992 target_type = isolate_map[target]['type']
993 label = isolate_map[target]['label']
994 stamp_runtime_deps = 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')
Erik Chen42df41d2018-08-21 17:13:31995 # TODO(https://crbug.com/876065): 'official_tests' use
996 # type='additional_compile_target' to isolate tests. This is not the
997 # intended use for 'additional_compile_target'.
John Budorick39f14962019-04-11 23:03:20998 if (target_type == 'additional_compile_target' and
Erik Chen42df41d2018-08-21 17:13:31999 target != 'official_tests'):
1000 # By definition, additional_compile_targets are not tests, so we
1001 # shouldn't generate isolates for them.
Dirk Pranke7a7e9b62019-02-17 01:46:251002 raise MBErr('Cannot generate isolate for %s since it is an '
1003 'additional_compile_target.' % target)
John Budorick39f14962019-04-11 23:03:201004 elif fuchsia or ios or target_type == 'generated_script':
1005 # iOS and Fuchsia targets end up as groups.
1006 # generated_script targets are always actions.
1007 rpaths = [stamp_runtime_deps]
Erik Chen42df41d2018-08-21 17:13:311008 elif android:
jbudoricke3c4f95e2016-04-28 23:17:381009 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:021010 # will result in runtime_deps associated with the stamp file, while the
1011 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:441012 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:251013 rpaths = [
dprankecb4a2e242016-09-19 01:13:141014 target + '.runtime_deps',
John Budorick39f14962019-04-11 23:03:201015 stamp_runtime_deps]
1016 elif (target_type == 'script' or
1017 target_type == 'fuzzer' or
dprankecb4a2e242016-09-19 01:13:141018 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:111019 # For script targets, the build target is usually a group,
1020 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:491021 # for the label, which lives under the obj/ directory, but it may
1022 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:441023 label = isolate_map[target]['label']
John Budorick39f14962019-04-11 23:03:201024 rpaths = [stamp_runtime_deps]
Nico Weberd94b71a2018-02-22 22:00:301025 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:251026 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:491027 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251028 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:301029 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:251030 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:521031 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251032 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:021033
Dirk Pranke7a7e9b62019-02-17 01:46:251034 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:411035
Dirk Pranke7a7e9b62019-02-17 01:46:251036 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:341037
1038 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:301039 target = self.args.target
dprankecb4a2e242016-09-19 01:13:141040 isolate_map = self.ReadIsolateMap()
1041 err, labels = self.MapTargetsToLabels(isolate_map, [target])
1042 if err:
1043 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:251044
dprankecb4a2e242016-09-19 01:13:141045 label = labels[0]
dpranke751516a2015-10-03 01:11:341046
Dirk Prankef24e6b22018-03-27 20:12:301047 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:141048 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:341049
dprankeeca4a782016-04-14 01:42:381050 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:201051 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:341052 if ret:
dpranke030d7a6d2016-03-26 17:23:501053 if out:
1054 self.Print(out)
dpranke751516a2015-10-03 01:11:341055 return ret
1056
1057 runtime_deps = out.splitlines()
1058
Nico Weber0fd016762019-08-25 14:48:141059 ret = self.WriteIsolateFiles(build_dir, command, target, runtime_deps, vals,
1060 extra_files)
1061 if ret:
1062 return ret
dpranke751516a2015-10-03 01:11:341063
1064 ret, _, _ = self.Run([
1065 self.executable,
1066 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1067 'check',
1068 '-i',
1069 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1070 '-s',
1071 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1072 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301073
dprankefe4602312015-04-08 16:20:351074 return ret
1075
Nico Weber0fd016762019-08-25 14:48:141076 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps, vals,
dpranke751516a2015-10-03 01:11:341077 extra_files):
1078 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
Nico Weber0fd016762019-08-25 14:48:141079 files = sorted(set(runtime_deps + extra_files))
1080
1081 # Complain if any file is a directory that's inside the build directory,
1082 # since that makes incremental builds incorrect. See
1083 # https://crbug.com/912946
1084 is_android = 'target_os="android"' in vals['gn_args']
1085 is_cros = ('target_os="chromeos"' in vals['gn_args'] or
1086 vals.get('cros_passthrough', False))
1087 is_mac = self.platform == 'darwin'
Nico Weber0fd016762019-08-25 14:48:141088 is_msan = 'is_msan=true' in vals['gn_args']
1089
1090 err = ''
1091 for f in files:
1092 # Skip a few configs that need extra cleanup for now.
1093 # TODO(https://crbug.com/912946): Fix everything on all platforms and
1094 # enable check everywhere.
Nico Weber5eee4522019-09-05 23:28:051095 if is_android or is_cros:
Nico Weber0fd016762019-08-25 14:48:141096 break
1097
1098 # Skip a few existing violations that need to be cleaned up. Each of
1099 # these will lead to incorrect incremental builds if their directory
1100 # contents change. Do not add to this list.
1101 # TODO(https://crbug.com/912946): Remove this if statement.
Nico Weber89895822019-08-27 18:59:031102 if ((is_msan and f == 'instrumented_libraries_prebuilt/') or
Clifford Chenge1244822019-08-27 17:26:551103 f == 'mr_extension/' or # https://crbug.com/997947
Nico Weber0fd016762019-08-25 14:48:141104 f == 'locales/' or
1105 f.startswith('nacl_test_data/') or
Nico Weber5eee4522019-09-05 23:28:051106 f.startswith('ppapi_nacl_tests_libs/') or
1107 (is_mac and f in ( # https://crbug.com/1000667
Nico Weber5eee4522019-09-05 23:28:051108 'AlertNotificationService.xpc/',
Nico Weber5eee4522019-09-05 23:28:051109 'Chromium Framework.framework/',
1110 'Chromium Helper.app/',
1111 'Chromium.app/',
Nico Weber5eee4522019-09-05 23:28:051112 'Content Shell.app/',
Nico Weber5eee4522019-09-05 23:28:051113 'Google Chrome Framework.framework/',
1114 'Google Chrome Helper (GPU).app/',
Nico Weber5eee4522019-09-05 23:28:051115 'Google Chrome Helper (Plugin).app/',
Nico Weber5eee4522019-09-05 23:28:051116 'Google Chrome Helper (Renderer).app/',
Nico Weber5eee4522019-09-05 23:28:051117 'Google Chrome Helper.app/',
Nico Weber5eee4522019-09-05 23:28:051118 'Google Chrome.app/',
Nico Weber5eee4522019-09-05 23:28:051119 'blink_deprecated_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051120 'blink_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051121 'corb_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051122 'obj/tools/grit/brotli_mac_asan_workaround/',
1123 'power_saver_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051124 'ppapi_tests.plugin/',
Nico Weber5eee4522019-09-05 23:28:051125 'ui_unittests Framework.framework/',
1126 ))):
Nico Weber0fd016762019-08-25 14:48:141127 continue
1128
Nico Weber24e54f992019-08-26 14:33:321129 # This runs before the build, so we can't use isdir(f). But
Nico Weber0fd016762019-08-25 14:48:141130 # isolate.py luckily requires data directories to end with '/', so we
Nico Weber24e54f992019-08-26 14:33:321131 # can check for that.
Nico Weber57dbc9952019-09-04 13:33:581132 if not f.startswith('../../') and f.endswith('/'):
Nico Weber24e54f992019-08-26 14:33:321133 # Don't use self.PathJoin() -- all involved paths consistently use
1134 # forward slashes, so don't add one single backslash on Windows.
1135 err += '\n' + build_dir + '/' + f
Nico Weber0fd016762019-08-25 14:48:141136
1137 if err:
1138 self.Print('error: gn `data` items may not list generated directories; '
Nico Weber24e54f992019-08-26 14:33:321139 'list files in directory instead for:' + err)
Nico Weber0fd016762019-08-25 14:48:141140 return 1
1141
dpranke751516a2015-10-03 01:11:341142 self.WriteFile(isolate_path,
1143 pprint.pformat({
1144 'variables': {
1145 'command': command,
Nico Weber0fd016762019-08-25 14:48:141146 'files': files,
dpranke751516a2015-10-03 01:11:341147 }
1148 }) + '\n')
1149
1150 self.WriteJSON(
1151 {
1152 'args': [
1153 '--isolated',
1154 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1155 '--isolate',
1156 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1157 ],
1158 'dir': self.chromium_src_dir,
1159 'version': 1,
1160 },
1161 isolate_path + 'd.gen.json',
1162 )
1163
dprankecb4a2e242016-09-19 01:13:141164 def MapTargetsToLabels(self, isolate_map, targets):
1165 labels = []
1166 err = ''
1167
dprankecb4a2e242016-09-19 01:13:141168 for target in targets:
1169 if target == 'all':
1170 labels.append(target)
1171 elif target.startswith('//'):
1172 labels.append(target)
1173 else:
1174 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421175 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141176 err += ('test target "%s" type is unknown\n' % target)
1177 else:
thakis024d6f32017-05-16 23:21:421178 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141179 else:
1180 err += ('target "%s" not found in '
1181 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1182
1183 return err, labels
1184
dprankeeca4a782016-04-14 01:42:381185 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461186 if self.platform == 'linux2':
1187 subdir, exe = 'linux64', 'gn'
1188 elif self.platform == 'darwin':
1189 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251190 elif self.platform == 'aix6':
1191 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461192 else:
1193 subdir, exe = 'win', 'gn.exe'
1194
1195 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081196 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521197
dprankecb4a2e242016-09-19 01:13:141198
Garrett Beatyb6cee042019-04-22 18:42:091199 def GNArgs(self, vals, expand_imports=False):
dpranke73ed0d62016-04-25 19:18:341200 if vals['cros_passthrough']:
1201 if not 'GN_ARGS' in os.environ:
1202 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1203 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161204 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301205 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341206 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351207 if vals['gn_args']:
1208 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341209 else:
1210 gn_args = vals['gn_args']
1211
dpranked0c138b2016-04-13 18:28:471212 if self.args.goma_dir:
1213 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381214
agrieve41d21a72016-04-14 18:02:261215 android_version_code = self.args.android_version_code
1216 if android_version_code:
1217 gn_args += ' android_default_version_code="%s"' % android_version_code
1218
1219 android_version_name = self.args.android_version_name
1220 if android_version_name:
1221 gn_args += ' android_default_version_name="%s"' % android_version_name
1222
Garrett Beatyb6cee042019-04-22 18:42:091223 args_gn_lines = []
1224 parsed_gn_args = {}
dprankeeca4a782016-04-14 01:42:381225
Ben Pastene65ccf6132018-11-08 00:47:591226 # If we're using the Simple Chrome SDK, add a comment at the top that
1227 # points to the doc. This must happen after the gn_helpers.ToGNString()
1228 # call above since gn_helpers strips comments.
1229 if vals['cros_passthrough']:
Garrett Beatyb6cee042019-04-22 18:42:091230 args_gn_lines.extend([
Ben Pastene65ccf6132018-11-08 00:47:591231 '# These args are generated via the Simple Chrome SDK. See the link',
1232 '# below for more details:',
1233 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
Garrett Beatyb6cee042019-04-22 18:42:091234 ])
Ben Pastene65ccf6132018-11-08 00:47:591235
dpranke9dd5e252016-04-14 04:23:091236 args_file = vals.get('args_file', None)
1237 if args_file:
Garrett Beatyb6cee042019-04-22 18:42:091238 if expand_imports:
1239 content = self.ReadFile(self.ToAbsPath(args_file))
1240 parsed_gn_args = gn_helpers.FromGNArgs(content)
1241 else:
1242 args_gn_lines.append('import("%s")' % args_file)
1243
1244 # Canonicalize the arg string into a sorted, newline-separated list
1245 # of key-value pairs, and de-dup the keys if need be so that only
1246 # the last instance of each arg is listed.
1247 parsed_gn_args.update(gn_helpers.FromGNArgs(gn_args))
1248 args_gn_lines.append(gn_helpers.ToGNString(parsed_gn_args))
1249
1250 return '\n'.join(args_gn_lines)
dprankefe4602312015-04-08 16:20:351251
dprankecb4a2e242016-09-19 01:13:141252 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071253 isolate_map = self.ReadIsolateMap()
1254
Scott Graham3be4b4162017-09-12 00:41:411255 is_android = 'target_os="android"' in vals['gn_args']
1256 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Caleb Raittof983d102019-06-21 23:05:021257 is_cros = 'target_os="chromeos"' in vals['gn_args']
Nico Webera7bc1cb2019-06-15 17:42:391258 is_simplechrome = vals.get('cros_passthrough', False)
1259 is_mac = self.platform == 'darwin'
Nico Weberd94b71a2018-02-22 22:00:301260 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061261
kylechar39705682017-01-19 14:37:231262 # This should be true if tests with type='windowed_test_launcher' are
1263 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081264 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1265 # backends. Currently, tests are executed for the headless and X11 backends
1266 # and both can run under Xvfb.
1267 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1268 # backend.
Scott Graham3be4b4162017-09-12 00:41:411269 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251270
1271 asan = 'is_asan=true' in vals['gn_args']
1272 msan = 'is_msan=true' in vals['gn_args']
1273 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411274 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
Yun Liu7cef1072019-06-27 21:22:191275 java_coverage = 'jacoco_coverage=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251276
dprankecb4a2e242016-09-19 01:13:141277 test_type = isolate_map[target]['type']
dprankefe0d35e2016-02-05 02:43:591278
dprankecb4a2e242016-09-19 01:13:141279 executable = isolate_map[target].get('executable', target)
bsheedy9c16ed62019-04-10 20:32:111280 executable_suffix = isolate_map[target].get(
1281 'executable_suffix', '.exe' if is_win else '')
dprankefe0d35e2016-02-05 02:43:591282
dprankea55584f12015-07-22 00:52:471283 cmdline = []
Andrii Shyshkalovc158e0102018-01-10 05:52:001284 extra_files = [
1285 '../../.vpython',
1286 '../../testing/test_env.py',
1287 ]
dpranked8113582015-06-05 20:08:251288
dprankecb4a2e242016-09-19 01:13:141289 if test_type == 'nontest':
1290 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1291 output_path=None)
1292
John Budorick93e88ac82019-04-12 18:39:111293 if test_type == 'generated_script':
Ben Pastenecb0fb412019-06-11 02:31:541294 script = isolate_map[target]['script']
1295 if self.platform == 'win32':
1296 script += '.bat'
John Budorick93e88ac82019-04-12 18:39:111297 cmdline = [
1298 '../../testing/test_env.py',
Ben Pastenecb0fb412019-06-11 02:31:541299 script,
John Budorick93e88ac82019-04-12 18:39:111300 ]
1301 elif test_type == 'fuzzer':
Roberto Carrillo1460da852018-12-14 17:10:391302 cmdline = [
1303 '../../testing/test_env.py',
1304 '../../tools/code_coverage/run_fuzz_target.py',
1305 '--fuzzer', './' + target,
1306 '--output-dir', '${ISOLATED_OUTDIR}',
1307 '--timeout', '3600']
1308 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011309 cmdline = []
1310 if asan:
John Budorick31cdce62019-04-03 20:56:111311 cmdline += [os.path.join('bin', 'run_with_asan'), '--']
John Budorick8c4203042019-03-19 17:22:011312 cmdline += [
John Budorickfb97a852017-12-20 20:10:191313 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041314 '../../build/android/test_wrapper/logdog_wrapper.py',
1315 '--target', target,
hzl9ae14452017-04-04 23:38:021316 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481317 '--store-tombstones']
Yun Liu7cef1072019-06-27 21:22:191318 if java_coverage:
1319 cmdline += ['--coverage-dir', '${ISOLATED_OUTDIR}']
Scott Graham3be4b4162017-09-12 00:41:411320 elif is_fuchsia and test_type != 'script':
John Budorickfb97a852017-12-20 20:10:191321 cmdline = [
1322 '../../testing/test_env.py',
1323 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441324 '--test-launcher-bot-mode',
Sergey Ulanovd851243b2019-06-25 00:33:471325 '--system-log-file', '${ISOLATED_OUTDIR}/system_log'
John Budorickfb97a852017-12-20 20:10:191326 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321327 elif is_simplechrome and test_type != 'script':
1328 cmdline = [
1329 '../../testing/test_env.py',
1330 os.path.join('bin', 'run_%s' % target),
1331 ]
kylechar39705682017-01-19 14:37:231332 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001333 extra_files.append('../../testing/xvfb.py')
dprankea55584f12015-07-22 00:52:471334 cmdline = [
Nico Webera7bc1cb2019-06-15 17:42:391335 '../../testing/xvfb.py',
1336 './' + str(executable) + executable_suffix,
1337 '--test-launcher-bot-mode',
1338 '--asan=%d' % asan,
1339 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1340 # supported.
1341 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021342 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1343 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
Nico Webera7bc1cb2019-06-15 17:42:391344 '--msan=%d' % msan,
1345 '--tsan=%d' % tsan,
1346 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471347 ]
1348 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
dprankea55584f12015-07-22 00:52:471349 cmdline = [
1350 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591351 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251352 '--test-launcher-bot-mode',
1353 '--asan=%d' % asan,
Caleb Raitto1fb2cc9e2019-06-14 01:04:231354 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1355 # supported.
Nico Webera7bc1cb2019-06-15 17:42:391356 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021357 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1358 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
dpranked8113582015-06-05 20:08:251359 '--msan=%d' % msan,
1360 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411361 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471362 ]
dpranke6abd8652015-08-28 03:21:111363 elif test_type == 'script':
Ben Pastene8ab6954d2018-05-04 04:08:241364 cmdline = []
Ben Pastene4534c39e2019-07-08 22:55:341365 # If we're testing a CrOS simplechrome build, assume we need to prepare a
1366 # DUT for testing. So prepend the command to run with the test wrapper.
Ben Pastene8ab6954d2018-05-04 04:08:241367 if is_simplechrome:
Ben Pastene908863c2019-07-25 16:20:031368 cmdline = [
1369 os.path.join('bin', 'cros_test_wrapper'),
1370 '--logs-dir=${ISOLATED_OUTDIR}',
1371 ]
Ben Pastene8ab6954d2018-05-04 04:08:241372 cmdline += [
dpranke6abd8652015-08-28 03:21:111373 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141374 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591375 ]
Dirk Prankef24e6b22018-03-27 20:12:301376 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471377 cmdline = [
1378 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591379 ]
dprankea55584f12015-07-22 00:52:471380 else:
1381 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1382 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251383
dprankecb4a2e242016-09-19 01:13:141384 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591385
dpranked8113582015-06-05 20:08:251386 return cmdline, extra_files
1387
dpranke74559b52015-06-10 21:20:391388 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331389 return self.PathJoin(self.chromium_src_dir,
1390 self.ToSrcRelPath(build_path),
1391 *comps)
dpranked8113582015-06-05 20:08:251392
dprankeee5b51f62015-04-09 00:03:221393 def ToSrcRelPath(self, path):
1394 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501395 if path.startswith('//'):
1396 return path[2:].replace('/', self.sep)
1397 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351398
Dirk Pranke0fd41bcd2015-06-19 00:05:501399 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141400 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501401 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001402 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501403 if ret:
1404 return ret
1405
Dirk Prankef24e6b22018-03-27 20:12:301406 build_path = self.args.path
1407 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141408 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301409 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141410 gn_output_path = output_path + '.gn'
1411
dpranke7837fc362015-11-19 03:54:161412 inp = self.ReadInputJSON(['files', 'test_targets',
1413 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321414 if self.args.verbose:
1415 self.Print()
1416 self.Print('analyze input:')
1417 self.PrintJSON(inp)
1418 self.Print()
1419
dpranke76734662015-04-16 02:17:501420
dpranke7c5f614d2015-07-22 23:43:391421 # This shouldn't normally happen, but could due to unusual race conditions,
1422 # like a try job that gets scheduled before a patch lands but runs after
1423 # the patch has landed.
1424 if not inp['files']:
1425 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161426 self.WriteJSON({
1427 'status': 'No dependency',
1428 'compile_targets': [],
1429 'test_targets': [],
1430 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391431 return 0
1432
dprankecb4a2e242016-09-19 01:13:141433 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161434 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561435
dprankecb4a2e242016-09-19 01:13:141436 isolate_map = self.ReadIsolateMap()
1437 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1438 isolate_map, inp['additional_compile_targets'])
1439 if err:
1440 raise MBErr(err)
1441
1442 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1443 isolate_map, inp['test_targets'])
1444 if err:
1445 raise MBErr(err)
1446 labels_to_targets = {}
1447 for i, label in enumerate(gn_inp['test_targets']):
1448 labels_to_targets[label] = inp['test_targets'][i]
1449
dprankef61de2f2015-05-14 04:09:561450 try:
dprankecb4a2e242016-09-19 01:13:141451 self.WriteJSON(gn_inp, gn_input_path)
1452 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
Debrian Figueroaae51d0d2019-07-22 18:04:111453 ret, output, _ = self.Run(cmd, force_verbose=True)
dprankecb4a2e242016-09-19 01:13:141454 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:111455 if self.args.json_output:
1456 # write errors to json.output
1457 self.WriteJSON({'output': output}, self.args.json_output)
dprankecb4a2e242016-09-19 01:13:141458 return ret
dpranke067d0142015-05-14 22:52:451459
dprankecb4a2e242016-09-19 01:13:141460 gn_outp_str = self.ReadFile(gn_output_path)
1461 try:
1462 gn_outp = json.loads(gn_outp_str)
1463 except Exception as e:
1464 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1465 % (repr(gn_outp_str), str(e)))
1466 raise
1467
1468 outp = {}
1469 if 'status' in gn_outp:
1470 outp['status'] = gn_outp['status']
1471 if 'error' in gn_outp:
1472 outp['error'] = gn_outp['error']
1473 if 'invalid_targets' in gn_outp:
1474 outp['invalid_targets'] = gn_outp['invalid_targets']
1475 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491476 all_input_compile_targets = sorted(
1477 set(inp['test_targets'] + inp['additional_compile_targets']))
1478
1479 # If we're building 'all', we can throw away the rest of the targets
1480 # since they're redundant.
dpranke385a3102016-09-20 22:04:081481 if 'all' in gn_outp['compile_targets']:
1482 outp['compile_targets'] = ['all']
1483 else:
Dirk Pranke45165072017-11-08 04:57:491484 outp['compile_targets'] = gn_outp['compile_targets']
1485
1486 # crbug.com/736215: When GN returns targets back, for targets in
1487 # the default toolchain, GN will have generated a phony ninja
1488 # target matching the label, and so we can safely (and easily)
1489 # transform any GN label into the matching ninja target. For
1490 # targets in other toolchains, though, GN doesn't generate the
1491 # phony targets, and we don't know how to turn the labels into
1492 # compile targets. In this case, we also conservatively give up
1493 # and build everything. Probably the right thing to do here is
1494 # to have GN return the compile targets directly.
1495 if any("(" in target for target in outp['compile_targets']):
1496 self.Print('WARNING: targets with non-default toolchains were '
1497 'found, building everything instead.')
1498 outp['compile_targets'] = all_input_compile_targets
1499 else:
dpranke385a3102016-09-20 22:04:081500 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491501 label.replace('//', '') for label in outp['compile_targets']]
1502
1503 # Windows has a maximum command line length of 8k; even Linux
1504 # maxes out at 128k; if analyze returns a *really long* list of
1505 # targets, we just give up and conservatively build everything instead.
1506 # Probably the right thing here is for ninja to support response
1507 # files as input on the command line
1508 # (see https://github.com/ninja-build/ninja/issues/1355).
1509 if len(' '.join(outp['compile_targets'])) > 7*1024:
1510 self.Print('WARNING: Too many compile targets were affected.')
1511 self.Print('WARNING: Building everything instead to avoid '
1512 'command-line length issues.')
1513 outp['compile_targets'] = all_input_compile_targets
1514
1515
dprankecb4a2e242016-09-19 01:13:141516 if 'test_targets' in gn_outp:
1517 outp['test_targets'] = [
1518 labels_to_targets[label] for label in gn_outp['test_targets']]
1519
1520 if self.args.verbose:
1521 self.Print()
1522 self.Print('analyze output:')
1523 self.PrintJSON(outp)
1524 self.Print()
1525
1526 self.WriteJSON(outp, output_path)
1527
dprankef61de2f2015-05-14 04:09:561528 finally:
dprankecb4a2e242016-09-19 01:13:141529 if self.Exists(gn_input_path):
1530 self.RemoveFile(gn_input_path)
1531 if self.Exists(gn_output_path):
1532 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351533
1534 return 0
1535
dpranked8113582015-06-05 20:08:251536 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301537 path = self.args.input_path
1538 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351539 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321540 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351541
1542 try:
1543 inp = json.loads(self.ReadFile(path))
1544 except Exception as e:
1545 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321546 (path, e), output_path)
dpranked8113582015-06-05 20:08:251547
1548 for k in required_keys:
1549 if not k in inp:
1550 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1551 output_path)
dprankefe4602312015-04-08 16:20:351552
1553 return inp
1554
dpranked5b2b9432015-06-23 16:55:301555 def WriteFailureAndRaise(self, msg, output_path):
1556 if output_path:
dprankee0547cd2015-09-15 01:27:401557 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351558 raise MBErr(msg)
1559
dprankee0547cd2015-09-15 01:27:401560 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321561 try:
dprankee0547cd2015-09-15 01:27:401562 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1563 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321564 except Exception as e:
1565 raise MBErr('Error %s writing to the output path "%s"' %
1566 (e, path))
dprankefe4602312015-04-08 16:20:351567
aneeshmde50f472016-04-01 01:13:101568 def CheckCompile(self, master, builder):
1569 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1570 url = urllib2.quote(url_template.format(master=master, builder=builder),
1571 safe=':/()?=')
1572 try:
1573 builds = json.loads(self.Fetch(url))
1574 except Exception as e:
1575 return str(e)
1576 successes = sorted(
1577 [int(x) for x in builds.keys() if "text" in builds[x] and
1578 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1579 reverse=True)
1580 if not successes:
1581 return "no successful builds"
1582 build = builds[str(successes[0])]
1583 step_names = set([step["name"] for step in build["steps"]])
1584 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1585 if compile_indicators & step_names:
1586 return "compiles"
1587 return "does not compile"
1588
dpranke3cec199c2015-09-22 23:29:021589 def PrintCmd(self, cmd, env):
1590 if self.platform == 'win32':
1591 env_prefix = 'set '
1592 env_quoter = QuoteForSet
1593 shell_quoter = QuoteForCmd
1594 else:
1595 env_prefix = ''
1596 env_quoter = pipes.quote
1597 shell_quoter = pipes.quote
1598
1599 def print_env(var):
1600 if env and var in env:
1601 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1602
dprankeec079262016-06-07 02:21:201603 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021604
dpranke8c2cfd32015-09-17 20:12:331605 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351606 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021607 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351608
dprankecda00332015-04-11 04:18:321609 def PrintJSON(self, obj):
1610 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1611
dpranke751516a2015-10-03 01:11:341612 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301613 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381614 if self.platform == 'win32':
1615 # On Windows use the batch script since there is no exe
1616 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1617 else:
1618 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341619 if self.args.jobs:
1620 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1621 ninja_cmd.append(target)
Dirk Pranke5f22a822019-05-23 22:55:251622 ret, _, _ = self.Run(ninja_cmd, buffer_output=False)
dpranke751516a2015-10-03 01:11:341623 return ret
1624
1625 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351626 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401627 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021628 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351629 if self.args.dryrun:
1630 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401631
dpranke751516a2015-10-03 01:11:341632 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401633 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341634 if ret:
1635 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351636 if out:
Debrian Figueroaae582232019-07-17 01:54:451637 # This is the error seen on the logs
dprankeee5b51f62015-04-09 00:03:221638 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351639 if err:
dprankeee5b51f62015-04-09 00:03:221640 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351641 return ret, out, err
1642
dpranke751516a2015-10-03 01:11:341643 def Call(self, cmd, env=None, buffer_output=True):
1644 if buffer_output:
1645 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1646 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1647 env=env)
1648 out, err = p.communicate()
1649 else:
1650 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1651 env=env)
1652 p.wait()
1653 out = err = ''
dprankefe4602312015-04-08 16:20:351654 return p.returncode, out, err
1655
1656 def ExpandUser(self, path):
1657 # This function largely exists so it can be overridden for testing.
1658 return os.path.expanduser(path)
1659
1660 def Exists(self, path):
1661 # This function largely exists so it can be overridden for testing.
1662 return os.path.exists(path)
1663
dpranke867bcf4a2016-03-14 22:28:321664 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501665 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321666 f = urllib2.urlopen(url)
1667 contents = f.read()
1668 f.close()
1669 return contents
1670
dprankec3441d12015-06-23 23:01:351671 def MaybeMakeDirectory(self, path):
1672 try:
1673 os.makedirs(path)
1674 except OSError, e:
1675 if e.errno != errno.EEXIST:
1676 raise
1677
dpranke8c2cfd32015-09-17 20:12:331678 def PathJoin(self, *comps):
1679 # This function largely exists so it can be overriden for testing.
1680 return os.path.join(*comps)
1681
dpranke030d7a6d2016-03-26 17:23:501682 def Print(self, *args, **kwargs):
1683 # This function largely exists so it can be overridden for testing.
1684 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101685 if kwargs.get('stream', sys.stdout) == sys.stdout:
1686 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501687
dprankefe4602312015-04-08 16:20:351688 def ReadFile(self, path):
1689 # This function largely exists so it can be overriden for testing.
1690 with open(path) as fp:
1691 return fp.read()
1692
dpranke030d7a6d2016-03-26 17:23:501693 def RelPath(self, path, start='.'):
1694 # This function largely exists so it can be overriden for testing.
1695 return os.path.relpath(path, start)
1696
dprankef61de2f2015-05-14 04:09:561697 def RemoveFile(self, path):
1698 # This function largely exists so it can be overriden for testing.
1699 os.remove(path)
1700
dprankec161aa92015-09-14 20:21:131701 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331702 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131703 # In other places in chromium, we often have to retry this command
1704 # because we're worried about other processes still holding on to
1705 # file handles, but when MB is invoked, it will be early enough in the
1706 # build that their should be no other processes to interfere. We
1707 # can change this if need be.
1708 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1709 else:
1710 shutil.rmtree(abs_path, ignore_errors=True)
1711
Dirk Prankef24e6b22018-03-27 20:12:301712 def TempDir(self):
1713 # This function largely exists so it can be overriden for testing.
1714 return tempfile.mkdtemp(prefix='mb_')
1715
dprankef61de2f2015-05-14 04:09:561716 def TempFile(self, mode='w'):
1717 # This function largely exists so it can be overriden for testing.
1718 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1719
dprankee0547cd2015-09-15 01:27:401720 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351721 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401722 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301723 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351724 with open(path, 'w') as fp:
1725 return fp.write(contents)
1726
dprankef61de2f2015-05-14 04:09:561727
dprankefe4602312015-04-08 16:20:351728class MBErr(Exception):
1729 pass
1730
1731
dpranke3cec199c2015-09-22 23:29:021732# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1733# details of this next section, which handles escaping command lines
1734# so that they can be copied and pasted into a cmd window.
1735UNSAFE_FOR_SET = set('^<>&|')
1736UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1737ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1738
1739
1740def QuoteForSet(arg):
1741 if any(a in UNSAFE_FOR_SET for a in arg):
1742 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1743 return arg
1744
1745
1746def QuoteForCmd(arg):
1747 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021748 if arg == '' or ' ' in arg or '"' in arg:
1749 quote_re = re.compile(r'(\\*)"')
1750 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1751
1752 # Then check to see if the arg contains any metacharacters other than
1753 # double quotes; if it does, quote everything (including the double
1754 # quotes) for safety.
1755 if any(a in UNSAFE_FOR_CMD for a in arg):
1756 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1757 return arg
1758
1759
dprankefe4602312015-04-08 16:20:351760if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591761 sys.exit(main(sys.argv[1:]))