blob: e612679ff4d240bf77fea4ca67fe14122ae55b96 [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
Karen Qian92ffd1a2019-09-11 01:09:2339def PruneVirtualEnv():
40 # Set by VirtualEnv, no need to keep it.
41 os.environ.pop('VIRTUAL_ENV', None)
42
43 # Set by VPython, if scripts want it back they have to set it explicitly.
44 os.environ.pop('PYTHONNOUSERSITE', None)
45
46 # Look for "activate_this.py" in this path, which is installed by VirtualEnv.
47 # This mechanism is used by vpython as well to sanitize VirtualEnvs from
48 # $PATH.
49 os.environ['PATH'] = os.pathsep.join([
50 p for p in os.environ.get('PATH', '').split(os.pathsep)
51 if not os.path.isfile(os.path.join(p, 'activate_this.py'))
52 ])
53
dprankeeca4a782016-04-14 01:42:3854
dprankefe4602312015-04-08 16:20:3555def main(args):
Karen Qian92ffd1a2019-09-11 01:09:2356 # Prune all evidence of VPython/VirtualEnv out of the environment. This means
57 # that we 'unwrap' vpython VirtualEnv path/env manipulation. Invocations of
58 # `python` from GN should never inherit the gn.py's own VirtualEnv. This also
59 # helps to ensure that generated ninja files do not reference python.exe from
60 # the VirtualEnv generated from depot_tools' own .vpython file (or lack
61 # thereof), but instead reference the default python from the PATH.
62 PruneVirtualEnv()
63
dprankeee5b51f62015-04-09 00:03:2264 mbw = MetaBuildWrapper()
dpranke255085e2016-03-16 05:23:5965 return mbw.Main(args)
dprankefe4602312015-04-08 16:20:3566
67
68class MetaBuildWrapper(object):
69 def __init__(self):
dprankeeca4a782016-04-14 01:42:3870 self.chromium_src_dir = CHROMIUM_SRC_DIR
71 self.default_config = os.path.join(self.chromium_src_dir, 'tools', 'mb',
72 'mb_config.pyl')
kjellander902bcb62016-10-26 06:20:5073 self.default_isolate_map = os.path.join(self.chromium_src_dir, 'testing',
74 'buildbot', 'gn_isolate_map.pyl')
dpranke8c2cfd32015-09-17 20:12:3375 self.executable = sys.executable
dpranked1fba482015-04-14 20:54:5176 self.platform = sys.platform
dpranke8c2cfd32015-09-17 20:12:3377 self.sep = os.sep
dprankefe4602312015-04-08 16:20:3578 self.args = argparse.Namespace()
79 self.configs = {}
80 self.masters = {}
81 self.mixins = {}
dprankefe4602312015-04-08 16:20:3582
dpranke255085e2016-03-16 05:23:5983 def Main(self, args):
84 self.ParseArgs(args)
85 try:
86 ret = self.args.func()
87 if ret:
88 self.DumpInputFiles()
89 return ret
90 except KeyboardInterrupt:
dprankecb4a2e242016-09-19 01:13:1491 self.Print('interrupted, exiting')
dpranke255085e2016-03-16 05:23:5992 return 130
dprankebbe6d4672016-04-19 06:56:5793 except Exception:
dpranke255085e2016-03-16 05:23:5994 self.DumpInputFiles()
dprankebbe6d4672016-04-19 06:56:5795 s = traceback.format_exc()
96 for l in s.splitlines():
97 self.Print(l)
dpranke255085e2016-03-16 05:23:5998 return 1
99
dprankefe4602312015-04-08 16:20:35100 def ParseArgs(self, argv):
101 def AddCommonOptions(subp):
102 subp.add_argument('-b', '--builder',
103 help='builder name to look up config from')
104 subp.add_argument('-m', '--master',
105 help='master name to look up config from')
106 subp.add_argument('-c', '--config',
107 help='configuration to analyze')
shenghuazhang804b21542016-10-11 02:06:49108 subp.add_argument('--phase',
109 help='optional phase name (used when builders '
110 'do multiple compiles with different '
111 'arguments in a single build)')
dprankefe4602312015-04-08 16:20:35112 subp.add_argument('-f', '--config-file', metavar='PATH',
113 default=self.default_config,
114 help='path to config file '
kjellander902bcb62016-10-26 06:20:50115 '(default is %(default)s)')
116 subp.add_argument('-i', '--isolate-map-file', metavar='PATH',
kjellander902bcb62016-10-26 06:20:50117 help='path to isolate map file '
Zhiling Huang66958462018-02-03 00:28:20118 '(default is %(default)s)',
119 default=[],
120 action='append',
121 dest='isolate_map_files')
dpranked0c138b2016-04-13 18:28:47122 subp.add_argument('-g', '--goma-dir',
123 help='path to goma directory')
agrieve41d21a72016-04-14 18:02:26124 subp.add_argument('--android-version-code',
Dirk Pranked181a1a2017-12-14 01:47:11125 help='Sets GN arg android_default_version_code')
agrieve41d21a72016-04-14 18:02:26126 subp.add_argument('--android-version-name',
Dirk Pranked181a1a2017-12-14 01:47:11127 help='Sets GN arg android_default_version_name')
dprankefe4602312015-04-08 16:20:35128 subp.add_argument('-n', '--dryrun', action='store_true',
129 help='Do a dry run (i.e., do nothing, just print '
130 'the commands that will run)')
dprankee0547cd2015-09-15 01:27:40131 subp.add_argument('-v', '--verbose', action='store_true',
132 help='verbose logging')
dprankefe4602312015-04-08 16:20:35133
Stephen Martinisb40a6852019-07-23 01:48:30134 parser = argparse.ArgumentParser(
135 prog='mb', description='mb (meta-build) is a python wrapper around GN. '
136 'See the user guide in '
137 '//tools/mb/docs/user_guide.md for detailed usage '
138 'instructions.')
139
dprankefe4602312015-04-08 16:20:35140 subps = parser.add_subparsers()
141
142 subp = subps.add_parser('analyze',
Stephen Martinis239c35a2019-07-22 19:34:40143 description='Analyze whether changes to a set of '
144 'files will cause a set of binaries to '
145 'be rebuilt.')
dprankefe4602312015-04-08 16:20:35146 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30147 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35148 help='path build was generated into.')
Dirk Prankef24e6b22018-03-27 20:12:30149 subp.add_argument('input_path',
dprankefe4602312015-04-08 16:20:35150 help='path to a file containing the input arguments '
151 'as a JSON object.')
Dirk Prankef24e6b22018-03-27 20:12:30152 subp.add_argument('output_path',
dprankefe4602312015-04-08 16:20:35153 help='path to a file containing the output arguments '
154 'as a JSON object.')
Debrian Figueroaae51d0d2019-07-22 18:04:11155 subp.add_argument('--json-output',
Debrian Figueroaae582232019-07-17 01:54:45156 help='Write errors to json.output')
dprankefe4602312015-04-08 16:20:35157 subp.set_defaults(func=self.CmdAnalyze)
158
dprankef37aebb92016-09-23 01:14:49159 subp = subps.add_parser('export',
Stephen Martinis239c35a2019-07-22 19:34:40160 description='Print out the expanded configuration '
161 'for each builder as a JSON object.')
dprankef37aebb92016-09-23 01:14:49162 subp.add_argument('-f', '--config-file', metavar='PATH',
163 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50164 help='path to config file (default is %(default)s)')
dprankef37aebb92016-09-23 01:14:49165 subp.add_argument('-g', '--goma-dir',
166 help='path to goma directory')
167 subp.set_defaults(func=self.CmdExport)
168
dprankefe4602312015-04-08 16:20:35169 subp = subps.add_parser('gen',
Stephen Martinis239c35a2019-07-22 19:34:40170 description='Generate a new set of build files.')
dprankefe4602312015-04-08 16:20:35171 AddCommonOptions(subp)
dpranke74559b52015-06-10 21:20:39172 subp.add_argument('--swarming-targets-file',
173 help='save runtime dependencies for targets listed '
174 'in file.')
Debrian Figueroaae51d0d2019-07-22 18:04:11175 subp.add_argument('--json-output',
Debrian Figueroaae582232019-07-17 01:54:45176 help='Write errors to json.output')
Dirk Prankef24e6b22018-03-27 20:12:30177 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35178 help='path to generate build into')
179 subp.set_defaults(func=self.CmdGen)
180
Erik Chen42df41d2018-08-21 17:13:31181 subp = subps.add_parser('isolate-everything',
Stephen Martinis239c35a2019-07-22 19:34:40182 description='Generates a .isolate for all targets. '
183 'Requires that mb.py gen has already '
184 'been run.')
Erik Chen42df41d2018-08-21 17:13:31185 AddCommonOptions(subp)
186 subp.set_defaults(func=self.CmdIsolateEverything)
187 subp.add_argument('path',
188 help='path build was generated into')
189
dpranke751516a2015-10-03 01:11:34190 subp = subps.add_parser('isolate',
Stephen Martinis239c35a2019-07-22 19:34:40191 description='Generate the .isolate files for a '
192 'given binary.')
dpranke751516a2015-10-03 01:11:34193 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30194 subp.add_argument('--no-build', dest='build', default=True,
195 action='store_false',
196 help='Do not build, just isolate')
197 subp.add_argument('-j', '--jobs', type=int,
198 help='Number of jobs to pass to ninja')
199 subp.add_argument('path',
dpranke751516a2015-10-03 01:11:34200 help='path build was generated into')
Dirk Prankef24e6b22018-03-27 20:12:30201 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34202 help='ninja target to generate the isolate for')
203 subp.set_defaults(func=self.CmdIsolate)
204
dprankefe4602312015-04-08 16:20:35205 subp = subps.add_parser('lookup',
Stephen Martinis239c35a2019-07-22 19:34:40206 description='Look up the command for a given '
207 'config or builder.')
dprankefe4602312015-04-08 16:20:35208 AddCommonOptions(subp)
Garrett Beatyb6cee042019-04-22 18:42:09209 subp.add_argument('--quiet', default=False, action='store_true',
210 help='Print out just the arguments, '
211 'do not emulate the output of the gen subcommand.')
212 subp.add_argument('--recursive', default=False, action='store_true',
213 help='Lookup arguments from imported files, '
214 'implies --quiet')
dprankefe4602312015-04-08 16:20:35215 subp.set_defaults(func=self.CmdLookup)
216
dpranke030d7a6d2016-03-26 17:23:50217 subp = subps.add_parser(
Stephen Martinis239c35a2019-07-22 19:34:40218 'run', formatter_class=argparse.RawDescriptionHelpFormatter)
dpranke030d7a6d2016-03-26 17:23:50219 subp.description = (
220 'Build, isolate, and run the given binary with the command line\n'
221 'listed in the isolate. You may pass extra arguments after the\n'
222 'target; use "--" if the extra arguments need to include switches.\n'
223 '\n'
224 'Examples:\n'
225 '\n'
226 ' % tools/mb/mb.py run -m chromium.linux -b "Linux Builder" \\\n'
227 ' //out/Default content_browsertests\n'
228 '\n'
229 ' % tools/mb/mb.py run out/Default content_browsertests\n'
230 '\n'
231 ' % tools/mb/mb.py run out/Default content_browsertests -- \\\n'
232 ' --test-launcher-retry-limit=0'
233 '\n'
234 )
dpranke751516a2015-10-03 01:11:34235 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30236 subp.add_argument('-j', '--jobs', type=int,
dpranke751516a2015-10-03 01:11:34237 help='Number of jobs to pass to ninja')
238 subp.add_argument('--no-build', dest='build', default=True,
239 action='store_false',
240 help='Do not build, just isolate and run')
Dirk Prankef24e6b22018-03-27 20:12:30241 subp.add_argument('path',
dpranke030d7a6d2016-03-26 17:23:50242 help=('path to generate build into (or use).'
243 ' This can be either a regular path or a '
244 'GN-style source-relative path like '
245 '//out/Default.'))
Dirk Pranke8cb6aa782017-12-16 02:31:33246 subp.add_argument('-s', '--swarmed', action='store_true',
247 help='Run under swarming with the default dimensions')
248 subp.add_argument('-d', '--dimension', default=[], action='append', nargs=2,
249 dest='dimensions', metavar='FOO bar',
250 help='dimension to filter on')
251 subp.add_argument('--no-default-dimensions', action='store_false',
252 dest='default_dimensions', default=True,
253 help='Do not automatically add dimensions to the task')
Dirk Prankef24e6b22018-03-27 20:12:30254 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34255 help='ninja target to build and run')
dpranke030d7a6d2016-03-26 17:23:50256 subp.add_argument('extra_args', nargs='*',
257 help=('extra args to pass to the isolate to run. Use '
258 '"--" as the first arg if you need to pass '
259 'switches'))
dpranke751516a2015-10-03 01:11:34260 subp.set_defaults(func=self.CmdRun)
261
dprankefe4602312015-04-08 16:20:35262 subp = subps.add_parser('validate',
Stephen Martinis239c35a2019-07-22 19:34:40263 description='Validate the config file.')
dprankea5a77ca2015-07-16 23:24:17264 subp.add_argument('-f', '--config-file', metavar='PATH',
265 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50266 help='path to config file (default is %(default)s)')
dprankefe4602312015-04-08 16:20:35267 subp.set_defaults(func=self.CmdValidate)
268
Dirk Prankef24e6b22018-03-27 20:12:30269 subp = subps.add_parser('zip',
Stephen Martinis239c35a2019-07-22 19:34:40270 description='Generate a .zip containing the files '
271 'needed for a given binary.')
Dirk Prankef24e6b22018-03-27 20:12:30272 AddCommonOptions(subp)
273 subp.add_argument('--no-build', dest='build', default=True,
274 action='store_false',
275 help='Do not build, just isolate')
276 subp.add_argument('-j', '--jobs', type=int,
277 help='Number of jobs to pass to ninja')
278 subp.add_argument('path',
279 help='path build was generated into')
280 subp.add_argument('target',
281 help='ninja target to generate the isolate for')
282 subp.add_argument('zip_path',
283 help='path to zip file to create')
284 subp.set_defaults(func=self.CmdZip)
285
dprankefe4602312015-04-08 16:20:35286 subp = subps.add_parser('help',
287 help='Get help on a subcommand.')
288 subp.add_argument(nargs='?', action='store', dest='subcommand',
289 help='The command to get help for.')
290 subp.set_defaults(func=self.CmdHelp)
291
292 self.args = parser.parse_args(argv)
293
dprankeb2be10a2016-02-22 17:11:00294 def DumpInputFiles(self):
295
dprankef7b7eb7a2016-03-28 22:42:59296 def DumpContentsOfFilePassedTo(arg_name, path):
dprankeb2be10a2016-02-22 17:11:00297 if path and self.Exists(path):
dprankef7b7eb7a2016-03-28 22:42:59298 self.Print("\n# To recreate the file passed to %s:" % arg_name)
dprankecb4a2e242016-09-19 01:13:14299 self.Print("%% cat > %s <<EOF" % path)
dprankeb2be10a2016-02-22 17:11:00300 contents = self.ReadFile(path)
dprankef7b7eb7a2016-03-28 22:42:59301 self.Print(contents)
302 self.Print("EOF\n%\n")
dprankeb2be10a2016-02-22 17:11:00303
dprankef7b7eb7a2016-03-28 22:42:59304 if getattr(self.args, 'input_path', None):
305 DumpContentsOfFilePassedTo(
Dirk Prankef24e6b22018-03-27 20:12:30306 'argv[0] (input_path)', self.args.input_path)
dprankef7b7eb7a2016-03-28 22:42:59307 if getattr(self.args, 'swarming_targets_file', None):
308 DumpContentsOfFilePassedTo(
309 '--swarming-targets-file', self.args.swarming_targets_file)
dprankeb2be10a2016-02-22 17:11:00310
dprankefe4602312015-04-08 16:20:35311 def CmdAnalyze(self):
dpranke751516a2015-10-03 01:11:34312 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11313 return self.RunGNAnalyze(vals)
dprankefe4602312015-04-08 16:20:35314
dprankef37aebb92016-09-23 01:14:49315 def CmdExport(self):
316 self.ReadConfigFile()
317 obj = {}
318 for master, builders in self.masters.items():
319 obj[master] = {}
320 for builder in builders:
321 config = self.masters[master][builder]
322 if not config:
323 continue
324
shenghuazhang804b21542016-10-11 02:06:49325 if isinstance(config, dict):
326 args = {k: self.FlattenConfig(v)['gn_args']
327 for k, v in config.items()}
dprankef37aebb92016-09-23 01:14:49328 elif config.startswith('//'):
329 args = config
330 else:
331 args = self.FlattenConfig(config)['gn_args']
332 if 'error' in args:
333 continue
334
335 obj[master][builder] = args
336
337 # Dump object and trim trailing whitespace.
338 s = '\n'.join(l.rstrip() for l in
339 json.dumps(obj, sort_keys=True, indent=2).splitlines())
340 self.Print(s)
341 return 0
342
dprankefe4602312015-04-08 16:20:35343 def CmdGen(self):
dpranke751516a2015-10-03 01:11:34344 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11345 return self.RunGNGen(vals)
dprankefe4602312015-04-08 16:20:35346
Erik Chen42df41d2018-08-21 17:13:31347 def CmdIsolateEverything(self):
348 vals = self.Lookup()
349 return self.RunGNGenAllIsolates(vals)
350
dprankefe4602312015-04-08 16:20:35351 def CmdHelp(self):
352 if self.args.subcommand:
353 self.ParseArgs([self.args.subcommand, '--help'])
354 else:
355 self.ParseArgs(['--help'])
356
dpranke751516a2015-10-03 01:11:34357 def CmdIsolate(self):
358 vals = self.GetConfig()
359 if not vals:
360 return 1
Dirk Prankef24e6b22018-03-27 20:12:30361 if self.args.build:
362 ret = self.Build(self.args.target)
363 if ret:
364 return ret
Dirk Pranked181a1a2017-12-14 01:47:11365 return self.RunGNIsolate(vals)
dpranke751516a2015-10-03 01:11:34366
367 def CmdLookup(self):
368 vals = self.Lookup()
Garrett Beatyb6cee042019-04-22 18:42:09369 gn_args = self.GNArgs(vals, expand_imports=self.args.recursive)
370 if self.args.quiet or self.args.recursive:
371 self.Print(gn_args, end='')
372 else:
373 cmd = self.GNCmd('gen', '_path_')
374 self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args)
375 env = None
dpranke751516a2015-10-03 01:11:34376
Garrett Beatyb6cee042019-04-22 18:42:09377 self.PrintCmd(cmd, env)
dpranke751516a2015-10-03 01:11:34378 return 0
379
380 def CmdRun(self):
381 vals = self.GetConfig()
382 if not vals:
383 return 1
Dirk Pranked181a1a2017-12-14 01:47:11384 if self.args.build:
Dirk Pranke5f22a822019-05-23 22:55:25385 self.Print('')
Dirk Prankef24e6b22018-03-27 20:12:30386 ret = self.Build(self.args.target)
dpranke751516a2015-10-03 01:11:34387 if ret:
388 return ret
Dirk Pranke5f22a822019-05-23 22:55:25389
390 self.Print('')
Dirk Pranked181a1a2017-12-14 01:47:11391 ret = self.RunGNIsolate(vals)
392 if ret:
393 return ret
dpranke751516a2015-10-03 01:11:34394
Dirk Pranke5f22a822019-05-23 22:55:25395 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33396 if self.args.swarmed:
Dirk Prankef24e6b22018-03-27 20:12:30397 return self._RunUnderSwarming(self.args.path, self.args.target)
Dirk Pranke8cb6aa782017-12-16 02:31:33398 else:
Dirk Prankef24e6b22018-03-27 20:12:30399 return self._RunLocallyIsolated(self.args.path, self.args.target)
400
401 def CmdZip(self):
402 ret = self.CmdIsolate()
403 if ret:
404 return ret
405
406 zip_dir = None
407 try:
408 zip_dir = self.TempDir()
409 remap_cmd = [
410 self.executable,
411 self.PathJoin(self.chromium_src_dir, 'tools', 'swarming_client',
412 'isolate.py'),
413 'remap',
Kenneth Russell2e75e2f2018-11-15 22:37:28414 '--collapse_symlinks',
Dirk Prankef24e6b22018-03-27 20:12:30415 '-s', self.PathJoin(self.args.path, self.args.target + '.isolated'),
416 '-o', zip_dir
417 ]
418 self.Run(remap_cmd)
419
420 zip_path = self.args.zip_path
Bruce Dawsonda03df292019-08-29 17:54:20421 with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED,
422 allowZip64=True) as fp:
Dirk Prankef24e6b22018-03-27 20:12:30423 for root, _, files in os.walk(zip_dir):
424 for filename in files:
425 path = self.PathJoin(root, filename)
426 fp.write(path, self.RelPath(path, zip_dir))
427 finally:
428 if zip_dir:
429 self.RemoveDirectory(zip_dir)
Dirk Pranke8cb6aa782017-12-16 02:31:33430
Robert Iannucci5a9d75f62018-03-02 05:28:20431 @staticmethod
432 def _AddBaseSoftware(cmd):
433 # HACK(iannucci): These packages SHOULD NOT BE HERE.
434 # Remove method once Swarming Pool Task Templates are implemented.
435 # crbug.com/812428
436
437 # Add in required base software. This should be kept in sync with the
John Budorick9d9175372019-04-01 19:04:24438 # `chromium_swarming` recipe module in build.git. All references to
439 # `swarming_module` below are purely due to this.
Robert Iannucci5a9d75f62018-03-02 05:28:20440 cipd_packages = [
441 ('infra/python/cpython/${platform}',
smut22dcd68e2019-06-25 23:33:27442 'version:2.7.15.chromium14'),
Robert Iannucci5a9d75f62018-03-02 05:28:20443 ('infra/tools/luci/logdog/butler/${platform}',
444 'git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c'),
445 ('infra/tools/luci/vpython-native/${platform}',
smut8e3640d2019-08-02 21:31:09446 'git_revision:98a268c6432f18aedd55d62b9621765316dc2a16'),
Robert Iannucci5a9d75f62018-03-02 05:28:20447 ('infra/tools/luci/vpython/${platform}',
smut8e3640d2019-08-02 21:31:09448 'git_revision:98a268c6432f18aedd55d62b9621765316dc2a16'),
Robert Iannucci5a9d75f62018-03-02 05:28:20449 ]
450 for pkg, vers in cipd_packages:
451 cmd.append('--cipd-package=.swarming_module:%s:%s' % (pkg, vers))
452
453 # Add packages to $PATH
454 cmd.extend([
455 '--env-prefix=PATH', '.swarming_module',
456 '--env-prefix=PATH', '.swarming_module/bin',
457 ])
458
459 # Add cache directives for vpython.
460 vpython_cache_path = '.swarming_module_cache/vpython'
461 cmd.extend([
462 '--named-cache=swarming_module_cache_vpython', vpython_cache_path,
463 '--env-prefix=VPYTHON_VIRTUALENV_ROOT', vpython_cache_path,
464 ])
465
Dirk Pranke8cb6aa782017-12-16 02:31:33466 def _RunUnderSwarming(self, build_dir, target):
Marc-Antoine Ruel559cc4732019-03-19 22:20:46467 isolate_server = 'isolateserver.appspot.com'
468 namespace = 'default-gzip'
469 swarming_server = 'chromium-swarm.appspot.com'
Dirk Pranke8cb6aa782017-12-16 02:31:33470 # TODO(dpranke): Look up the information for the target in
471 # the //testing/buildbot.json file, if possible, so that we
472 # can determine the isolate target, command line, and additional
473 # swarming parameters, if possible.
474 #
475 # TODO(dpranke): Also, add support for sharding and merging results.
476 dimensions = []
477 for k, v in self._DefaultDimensions() + self.args.dimensions:
478 dimensions += ['-d', k, v]
479
480 cmd = [
481 self.executable,
482 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
483 'archive',
Marc-Antoine Ruel559cc4732019-03-19 22:20:46484 '-s', self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
485 '-I', isolate_server,
486 '--namespace', namespace,
Dirk Pranke8cb6aa782017-12-16 02:31:33487 ]
Dirk Pranke5f22a822019-05-23 22:55:25488
489 # Talking to the isolateserver may fail because we're not logged in.
490 # We trap the command explicitly and rewrite the error output so that
491 # the error message is actually correct for a Chromium check out.
492 self.PrintCmd(cmd, env=None)
493 ret, out, err = self.Run(cmd, force_verbose=False)
Dirk Pranke8cb6aa782017-12-16 02:31:33494 if ret:
Dirk Pranke5f22a822019-05-23 22:55:25495 self.Print(' -> returned %d' % ret)
496 if out:
497 self.Print(out, end='')
498 if err:
499 # The swarming client will return an exit code of 2 (via
500 # argparse.ArgumentParser.error()) and print a message to indicate
501 # that auth failed, so we have to parse the message to check.
502 if (ret == 2 and 'Please login to' in err):
503 err = err.replace(' auth.py', ' tools/swarming_client/auth.py')
504 self.Print(err, end='', file=sys.stderr)
505
Dirk Pranke8cb6aa782017-12-16 02:31:33506 return ret
507
508 isolated_hash = out.splitlines()[0].split()[0]
509 cmd = [
510 self.executable,
511 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
512 'run',
513 '-s', isolated_hash,
Marc-Antoine Ruel559cc4732019-03-19 22:20:46514 '-I', isolate_server,
515 '--namespace', namespace,
516 '-S', swarming_server,
Stephen Martinis43ab3032019-09-11 20:07:41517 '--tags=purpose:user-debug-mb',
Dirk Pranke8cb6aa782017-12-16 02:31:33518 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20519 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33520 if self.args.extra_args:
521 cmd += ['--'] + self.args.extra_args
Dirk Pranke5f22a822019-05-23 22:55:25522 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33523 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
524 return ret
525
526 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50527 cmd = [
dpranke751516a2015-10-03 01:11:34528 self.executable,
529 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
530 'run',
531 '-s',
dpranke030d7a6d2016-03-26 17:23:50532 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33533 ]
dpranke030d7a6d2016-03-26 17:23:50534 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33535 cmd += ['--'] + self.args.extra_args
536 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34537 return ret
538
Dirk Pranke8cb6aa782017-12-16 02:31:33539 def _DefaultDimensions(self):
540 if not self.args.default_dimensions:
541 return []
542
543 # This code is naive and just picks reasonable defaults per platform.
544 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40545 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33546 elif self.platform.startswith('linux'):
Takuto Ikuta169663b2019-08-05 16:21:32547 os_dim = ('os', 'Ubuntu-16.04')
Dirk Pranke8cb6aa782017-12-16 02:31:33548 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40549 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33550 else:
551 raise MBErr('unrecognized platform string "%s"' % self.platform)
552
553 return [('pool', 'Chrome'),
554 ('cpu', 'x86-64'),
555 os_dim]
556
dpranke0cafc162016-03-19 00:41:10557 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35558 errs = []
559
560 # Read the file to make sure it parses.
561 self.ReadConfigFile()
562
dpranke3be00142016-03-17 22:46:04563 # Build a list of all of the configs referenced by builders.
dprankefe4602312015-04-08 16:20:35564 all_configs = {}
dprankefe4602312015-04-08 16:20:35565 for master in self.masters:
dpranke3be00142016-03-17 22:46:04566 for config in self.masters[master].values():
shenghuazhang804b21542016-10-11 02:06:49567 if isinstance(config, dict):
568 for c in config.values():
dprankeb9380a12016-07-21 21:44:09569 all_configs[c] = master
570 else:
571 all_configs[config] = master
dprankefe4602312015-04-08 16:20:35572
dpranke9dd5e252016-04-14 04:23:09573 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35574 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09575 if config.startswith('//'):
576 if not self.Exists(self.ToAbsPath(config)):
577 errs.append('Unknown args file "%s" referenced from "%s".' %
578 (config, loc))
579 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35580 errs.append('Unknown config "%s" referenced from "%s".' %
581 (config, loc))
582
583 # Check that every actual config is actually referenced.
584 for config in self.configs:
585 if not config in all_configs:
586 errs.append('Unused config "%s".' % config)
587
588 # Figure out the whole list of mixins, and check that every mixin
589 # listed by a config or another mixin actually exists.
590 referenced_mixins = set()
591 for config, mixins in self.configs.items():
592 for mixin in mixins:
593 if not mixin in self.mixins:
594 errs.append('Unknown mixin "%s" referenced by config "%s".' %
595 (mixin, config))
596 referenced_mixins.add(mixin)
597
598 for mixin in self.mixins:
599 for sub_mixin in self.mixins[mixin].get('mixins', []):
600 if not sub_mixin in self.mixins:
601 errs.append('Unknown mixin "%s" referenced by mixin "%s".' %
602 (sub_mixin, mixin))
603 referenced_mixins.add(sub_mixin)
604
605 # Check that every mixin defined is actually referenced somewhere.
606 for mixin in self.mixins:
607 if not mixin in referenced_mixins:
608 errs.append('Unreferenced mixin "%s".' % mixin)
609
dpranke255085e2016-03-16 05:23:59610 # If we're checking the Chromium config, check that the 'chromium' bots
611 # which build public artifacts do not include the chrome_with_codecs mixin.
612 if self.args.config_file == self.default_config:
613 if 'chromium' in self.masters:
614 for builder in self.masters['chromium']:
615 config = self.masters['chromium'][builder]
616 def RecurseMixins(current_mixin):
617 if current_mixin == 'chrome_with_codecs':
618 errs.append('Public artifact builder "%s" can not contain the '
619 '"chrome_with_codecs" mixin.' % builder)
620 return
621 if not 'mixins' in self.mixins[current_mixin]:
622 return
623 for mixin in self.mixins[current_mixin]['mixins']:
624 RecurseMixins(mixin)
dalecurtis56fd27e2016-03-09 23:06:41625
dpranke255085e2016-03-16 05:23:59626 for mixin in self.configs[config]:
627 RecurseMixins(mixin)
628 else:
629 errs.append('Missing "chromium" master. Please update this '
630 'proprietary codecs check with the name of the master '
631 'responsible for public build artifacts.')
dalecurtis56fd27e2016-03-09 23:06:41632
dprankefe4602312015-04-08 16:20:35633 if errs:
dpranke4323c80632015-08-10 22:53:54634 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17635 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35636
dpranke0cafc162016-03-19 00:41:10637 if print_ok:
638 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35639 return 0
640
641 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30642 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34643
dprankef37aebb92016-09-23 01:14:49644 vals = self.DefaultVals()
dpranke751516a2015-10-03 01:11:34645 if self.args.builder or self.args.master or self.args.config:
646 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11647 # Re-run gn gen in order to ensure the config is consistent with the
648 # build dir.
649 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34650 return vals
651
Dirk Pranked181a1a2017-12-14 01:47:11652 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
653 'toolchain.ninja')
654 if not self.Exists(toolchain_path):
655 self.Print('Must either specify a path to an existing GN build dir '
656 'or pass in a -m/-b pair or a -c flag to specify the '
657 'configuration')
658 return {}
dpranke751516a2015-10-03 01:11:34659
Dirk Pranked181a1a2017-12-14 01:47:11660 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34661 return vals
662
dprankef37aebb92016-09-23 01:14:49663 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58664 args_contents = ""
665 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
666 if self.Exists(gn_args_path):
667 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34668 gn_args = []
669 for l in args_contents.splitlines():
670 fields = l.split(' ')
671 name = fields[0]
672 val = ' '.join(fields[2:])
673 gn_args.append('%s=%s' % (name, val))
674
dprankef37aebb92016-09-23 01:14:49675 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34676
677 def Lookup(self):
Erik Chen238f4ac2019-04-12 19:02:50678 vals = self.ReadIOSBotConfig()
679 if not vals:
680 self.ReadConfigFile()
681 config = self.ConfigFromArgs()
682 if config.startswith('//'):
683 if not self.Exists(self.ToAbsPath(config)):
684 raise MBErr('args file "%s" not found' % config)
685 vals = self.DefaultVals()
686 vals['args_file'] = config
687 else:
688 if not config in self.configs:
689 raise MBErr('Config "%s" not found in %s' %
690 (config, self.args.config_file))
691 vals = self.FlattenConfig(config)
692 return vals
693
694 def ReadIOSBotConfig(self):
695 if not self.args.master or not self.args.builder:
696 return {}
697 path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
698 self.args.master, self.args.builder + '.json')
699 if not self.Exists(path):
700 return {}
701
702 contents = json.loads(self.ReadFile(path))
703 gn_args = ' '.join(contents.get('gn_args', []))
704
705 vals = self.DefaultVals()
706 vals['gn_args'] = gn_args
dprankef37aebb92016-09-23 01:14:49707 return vals
dprankee0f486f2015-11-19 23:42:00708
dprankefe4602312015-04-08 16:20:35709 def ReadConfigFile(self):
710 if not self.Exists(self.args.config_file):
711 raise MBErr('config file not found at %s' % self.args.config_file)
712
713 try:
714 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
715 except SyntaxError as e:
716 raise MBErr('Failed to parse config file "%s": %s' %
717 (self.args.config_file, e))
718
dprankefe4602312015-04-08 16:20:35719 self.configs = contents['configs']
720 self.masters = contents['masters']
721 self.mixins = contents['mixins']
dprankefe4602312015-04-08 16:20:35722
dprankecb4a2e242016-09-19 01:13:14723 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20724 if not self.args.isolate_map_files:
725 self.args.isolate_map_files = [self.default_isolate_map]
726
727 for f in self.args.isolate_map_files:
728 if not self.Exists(f):
729 raise MBErr('isolate map file not found at %s' % f)
730 isolate_maps = {}
731 for isolate_map in self.args.isolate_map_files:
732 try:
733 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
734 duplicates = set(isolate_map).intersection(isolate_maps)
735 if duplicates:
736 raise MBErr(
737 'Duplicate targets in isolate map files: %s.' %
738 ', '.join(duplicates))
739 isolate_maps.update(isolate_map)
740 except SyntaxError as e:
741 raise MBErr(
742 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
743 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14744
dprankefe4602312015-04-08 16:20:35745 def ConfigFromArgs(self):
746 if self.args.config:
747 if self.args.master or self.args.builder:
748 raise MBErr('Can not specific both -c/--config and -m/--master or '
749 '-b/--builder')
750
751 return self.args.config
752
753 if not self.args.master or not self.args.builder:
754 raise MBErr('Must specify either -c/--config or '
755 '(-m/--master and -b/--builder)')
756
757 if not self.args.master in self.masters:
758 raise MBErr('Master name "%s" not found in "%s"' %
759 (self.args.master, self.args.config_file))
760
761 if not self.args.builder in self.masters[self.args.master]:
762 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
763 (self.args.builder, self.args.master, self.args.config_file))
764
dprankeb9380a12016-07-21 21:44:09765 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49766 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09767 if self.args.phase is None:
768 raise MBErr('Must specify a build --phase for %s on %s' %
769 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49770 phase = str(self.args.phase)
771 if phase not in config:
772 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09773 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49774 return config[phase]
dprankeb9380a12016-07-21 21:44:09775
776 if self.args.phase is not None:
777 raise MBErr('Must not specify a build --phase for %s on %s' %
778 (self.args.builder, self.args.master))
779 return config
dprankefe4602312015-04-08 16:20:35780
781 def FlattenConfig(self, config):
782 mixins = self.configs[config]
dprankef37aebb92016-09-23 01:14:49783 vals = self.DefaultVals()
dprankefe4602312015-04-08 16:20:35784
785 visited = []
786 self.FlattenMixins(mixins, vals, visited)
787 return vals
788
dprankef37aebb92016-09-23 01:14:49789 def DefaultVals(self):
790 return {
791 'args_file': '',
792 'cros_passthrough': False,
793 'gn_args': '',
dprankef37aebb92016-09-23 01:14:49794 }
795
dprankefe4602312015-04-08 16:20:35796 def FlattenMixins(self, mixins, vals, visited):
797 for m in mixins:
798 if m not in self.mixins:
799 raise MBErr('Unknown mixin "%s"' % m)
dprankeee5b51f62015-04-09 00:03:22800
dprankefe4602312015-04-08 16:20:35801 visited.append(m)
802
803 mixin_vals = self.mixins[m]
dpranke73ed0d62016-04-25 19:18:34804
805 if 'cros_passthrough' in mixin_vals:
806 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
Dirk Pranke6b99f072017-04-05 00:58:30807 if 'args_file' in mixin_vals:
808 if vals['args_file']:
809 raise MBErr('args_file specified multiple times in mixins '
810 'for %s on %s' % (self.args.builder, self.args.master))
811 vals['args_file'] = mixin_vals['args_file']
dprankefe4602312015-04-08 16:20:35812 if 'gn_args' in mixin_vals:
813 if vals['gn_args']:
814 vals['gn_args'] += ' ' + mixin_vals['gn_args']
815 else:
816 vals['gn_args'] = mixin_vals['gn_args']
dpranke73ed0d62016-04-25 19:18:34817
dprankefe4602312015-04-08 16:20:35818 if 'mixins' in mixin_vals:
819 self.FlattenMixins(mixin_vals['mixins'], vals, visited)
820 return vals
821
Takuto Ikuta9dffd7e2018-09-05 01:04:00822 def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
Dirk Prankef24e6b22018-03-27 20:12:30823 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50824
Takuto Ikuta9dffd7e2018-09-05 01:04:00825 if check:
826 cmd = self.GNCmd('gen', build_dir, '--check')
827 else:
828 cmd = self.GNCmd('gen', build_dir)
dprankeeca4a782016-04-14 01:42:38829 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:09830 if compute_inputs_for_analyze:
831 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:38832
833 # Since GN hasn't run yet, the build directory may not even exist.
834 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
835
836 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:54837 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:39838
dpranke751516a2015-10-03 01:11:34839 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:39840 # We need GN to generate the list of runtime dependencies for
841 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:14842 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:39843 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:00844 path = self.args.swarming_targets_file
845 if not self.Exists(path):
846 self.WriteFailureAndRaise('"%s" does not exist' % path,
847 output_path=None)
848 contents = self.ReadFile(path)
Erik Chen42df41d2018-08-21 17:13:31849 isolate_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:00850
dprankecb4a2e242016-09-19 01:13:14851 isolate_map = self.ReadIsolateMap()
Dirk Pranke7a7e9b62019-02-17 01:46:25852 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
853 isolate_map, build_dir)
854
Erik Chen42df41d2018-08-21 17:13:31855 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:00856 if err:
Dirk Pranke7a7e9b62019-02-17 01:46:25857 raise MBErr(err)
dpranke74559b52015-06-10 21:20:39858
dpranke751516a2015-10-03 01:11:34859 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:14860 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:39861 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
862
Debrian Figueroaae582232019-07-17 01:54:45863 ret, output, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:40864 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:11865 if self.args.json_output:
Debrian Figueroaae582232019-07-17 01:54:45866 # write errors to json.output
867 self.WriteJSON({'output': output}, self.args.json_output)
Dirk Pranke7a7e9b62019-02-17 01:46:25868 # If `gn gen` failed, we should exit early rather than trying to
869 # generate isolates. Run() will have already logged any error output.
870 self.Print('GN gen failed: %d' % ret)
871 return ret
dpranke74559b52015-06-10 21:20:39872
Erik Chen42df41d2018-08-21 17:13:31873 if getattr(self.args, 'swarming_targets_file', None):
Nico Weber0fd016762019-08-25 14:48:14874 ret = self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
Erik Chen42df41d2018-08-21 17:13:31875
Nico Weber0fd016762019-08-25 14:48:14876 return ret
Erik Chen42df41d2018-08-21 17:13:31877
878 def RunGNGenAllIsolates(self, vals):
879 """
880 This command generates all .isolate files.
881
882 This command assumes that "mb.py gen" has already been run, as it relies on
883 "gn ls" to fetch all gn targets. If uses that output, combined with the
884 isolate_map, to determine all isolates that can be generated for the current
885 gn configuration.
886 """
887 build_dir = self.args.path
888 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
889 force_verbose=False)
890 if ret:
891 # If `gn ls` failed, we should exit early rather than trying to
892 # generate isolates.
893 self.Print('GN ls failed: %d' % ret)
894 return ret
895
896 # Create a reverse map from isolate label to isolate dict.
897 isolate_map = self.ReadIsolateMap()
898 isolate_dict_map = {}
899 for key, isolate_dict in isolate_map.iteritems():
900 isolate_dict_map[isolate_dict['label']] = isolate_dict
901 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
902
903 runtime_deps = []
904
905 isolate_targets = []
906 # For every GN target, look up the isolate dict.
907 for line in output.splitlines():
908 target = line.strip()
909 if target in isolate_dict_map:
910 if isolate_dict_map[target]['type'] == 'additional_compile_target':
911 # By definition, additional_compile_targets are not tests, so we
912 # shouldn't generate isolates for them.
913 continue
914
915 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
916 runtime_deps.append(target)
917
Dirk Pranke7a7e9b62019-02-17 01:46:25918 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
919 isolate_map, build_dir)
920
Erik Chen42df41d2018-08-21 17:13:31921 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
922 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
923 cmd = self.GNCmd('gen', build_dir)
924 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
925 self.Run(cmd)
926
927 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
928
Dirk Pranke7a7e9b62019-02-17 01:46:25929 def RemovePossiblyStaleRuntimeDepsFiles(self, vals, targets, isolate_map,
930 build_dir):
931 # TODO(crbug.com/932700): Because `gn gen --runtime-deps-list-file`
932 # puts the runtime_deps file in different locations based on the actual
933 # type of a target, we may end up with multiple possible runtime_deps
934 # files in a given build directory, where some of the entries might be
935 # stale (since we might be reusing an existing build directory).
936 #
937 # We need to be able to get the right one reliably; you might think
938 # we can just pick the newest file, but because GN won't update timestamps
939 # if the contents of the files change, an older runtime_deps
940 # file might actually be the one we should use over a newer one (see
941 # crbug.com/932387 for a more complete explanation and example).
942 #
943 # In order to avoid this, we need to delete any possible runtime_deps
944 # files *prior* to running GN. As long as the files aren't actually
945 # needed during the build, this hopefully will not cause unnecessary
946 # build work, and so it should be safe.
947 #
948 # Ultimately, we should just make sure we get the runtime_deps files
949 # in predictable locations so we don't have this issue at all, and
950 # that's what crbug.com/932700 is for.
951 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, targets, isolate_map)
952 for rpaths in possible_rpaths.values():
953 for rpath in rpaths:
954 path = self.ToAbsPath(build_dir, rpath)
955 if self.Exists(path):
956 self.RemoveFile(path)
957
Erik Chen42df41d2018-08-21 17:13:31958 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
959 """
960 Generates isolates for a list of ninja targets.
961
962 Ninja targets are transformed to GN targets via isolate_map.
963
964 This function assumes that a previous invocation of "mb.py gen" has
965 generated runtime deps for all targets.
966 """
Dirk Pranke7a7e9b62019-02-17 01:46:25967 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, ninja_targets,
968 isolate_map)
969
970 for target, rpaths in possible_rpaths.items():
971 # TODO(crbug.com/932700): We don't know where each .runtime_deps
972 # file might be, but assuming we called
973 # RemovePossiblyStaleRuntimeDepsFiles prior to calling `gn gen`,
974 # there should only be one file.
975 found_one = False
976 path_to_use = None
977 for r in rpaths:
978 path = self.ToAbsPath(build_dir, r)
979 if self.Exists(path):
980 if found_one:
981 raise MBErr('Found more than one of %s' % ', '.join(rpaths))
982 path_to_use = path
983 found_one = True
984
985 if not found_one:
986 raise MBErr('Did not find any of %s' % ', '.join(rpaths))
987
988 command, extra_files = self.GetIsolateCommand(target, vals)
989 runtime_deps = self.ReadFile(path_to_use).splitlines()
990
991 canonical_target = target.replace(':','_').replace('/','_')
Nico Weber0fd016762019-08-25 14:48:14992 ret = self.WriteIsolateFiles(build_dir, command, canonical_target,
993 runtime_deps, vals, extra_files)
994 if ret:
995 return ret
996 return 0
Dirk Pranke7a7e9b62019-02-17 01:46:25997
998 def PossibleRuntimeDepsPaths(self, vals, ninja_targets, isolate_map):
999 """Returns a map of targets to possible .runtime_deps paths.
1000
1001 Each ninja target maps on to a GN label, but depending on the type
1002 of the GN target, `gn gen --runtime-deps-list-file` will write
1003 the .runtime_deps files into different locations. Unfortunately, in
1004 some cases we don't actually know which of multiple locations will
1005 actually be used, so we return all plausible candidates.
1006
1007 The paths that are returned are relative to the build directory.
1008 """
1009
jbudoricke3c4f95e2016-04-28 23:17:381010 android = 'target_os="android"' in vals['gn_args']
Dirk Pranke26de05aec2019-04-03 19:18:381011 ios = 'target_os="ios"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:421012 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:301013 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Dirk Pranke7a7e9b62019-02-17 01:46:251014 possible_runtime_deps_rpaths = {}
Erik Chen42df41d2018-08-21 17:13:311015 for target in ninja_targets:
John Budorick39f14962019-04-11 23:03:201016 target_type = isolate_map[target]['type']
1017 label = isolate_map[target]['label']
1018 stamp_runtime_deps = 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')
Erik Chen42df41d2018-08-21 17:13:311019 # TODO(https://crbug.com/876065): 'official_tests' use
1020 # type='additional_compile_target' to isolate tests. This is not the
1021 # intended use for 'additional_compile_target'.
John Budorick39f14962019-04-11 23:03:201022 if (target_type == 'additional_compile_target' and
Erik Chen42df41d2018-08-21 17:13:311023 target != 'official_tests'):
1024 # By definition, additional_compile_targets are not tests, so we
1025 # shouldn't generate isolates for them.
Dirk Pranke7a7e9b62019-02-17 01:46:251026 raise MBErr('Cannot generate isolate for %s since it is an '
1027 'additional_compile_target.' % target)
John Budorick39f14962019-04-11 23:03:201028 elif fuchsia or ios or target_type == 'generated_script':
1029 # iOS and Fuchsia targets end up as groups.
1030 # generated_script targets are always actions.
1031 rpaths = [stamp_runtime_deps]
Erik Chen42df41d2018-08-21 17:13:311032 elif android:
jbudoricke3c4f95e2016-04-28 23:17:381033 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:021034 # will result in runtime_deps associated with the stamp file, while the
1035 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:441036 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:251037 rpaths = [
dprankecb4a2e242016-09-19 01:13:141038 target + '.runtime_deps',
John Budorick39f14962019-04-11 23:03:201039 stamp_runtime_deps]
1040 elif (target_type == 'script' or
1041 target_type == 'fuzzer' or
dprankecb4a2e242016-09-19 01:13:141042 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:111043 # For script targets, the build target is usually a group,
1044 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:491045 # for the label, which lives under the obj/ directory, but it may
1046 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:441047 label = isolate_map[target]['label']
John Budorick39f14962019-04-11 23:03:201048 rpaths = [stamp_runtime_deps]
Nico Weberd94b71a2018-02-22 22:00:301049 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:251050 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:491051 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251052 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:301053 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:251054 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:521055 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251056 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:021057
Dirk Pranke7a7e9b62019-02-17 01:46:251058 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:411059
Dirk Pranke7a7e9b62019-02-17 01:46:251060 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:341061
1062 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:301063 target = self.args.target
dprankecb4a2e242016-09-19 01:13:141064 isolate_map = self.ReadIsolateMap()
1065 err, labels = self.MapTargetsToLabels(isolate_map, [target])
1066 if err:
1067 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:251068
dprankecb4a2e242016-09-19 01:13:141069 label = labels[0]
dpranke751516a2015-10-03 01:11:341070
Dirk Prankef24e6b22018-03-27 20:12:301071 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:141072 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:341073
dprankeeca4a782016-04-14 01:42:381074 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:201075 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:341076 if ret:
dpranke030d7a6d2016-03-26 17:23:501077 if out:
1078 self.Print(out)
dpranke751516a2015-10-03 01:11:341079 return ret
1080
1081 runtime_deps = out.splitlines()
1082
Nico Weber0fd016762019-08-25 14:48:141083 ret = self.WriteIsolateFiles(build_dir, command, target, runtime_deps, vals,
1084 extra_files)
1085 if ret:
1086 return ret
dpranke751516a2015-10-03 01:11:341087
1088 ret, _, _ = self.Run([
1089 self.executable,
1090 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1091 'check',
1092 '-i',
1093 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1094 '-s',
1095 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1096 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301097
dprankefe4602312015-04-08 16:20:351098 return ret
1099
Nico Weber0fd016762019-08-25 14:48:141100 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps, vals,
dpranke751516a2015-10-03 01:11:341101 extra_files):
1102 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
Nico Weber0fd016762019-08-25 14:48:141103 files = sorted(set(runtime_deps + extra_files))
1104
1105 # Complain if any file is a directory that's inside the build directory,
1106 # since that makes incremental builds incorrect. See
1107 # https://crbug.com/912946
1108 is_android = 'target_os="android"' in vals['gn_args']
1109 is_cros = ('target_os="chromeos"' in vals['gn_args'] or
1110 vals.get('cros_passthrough', False))
1111 is_mac = self.platform == 'darwin'
Nico Weber0fd016762019-08-25 14:48:141112 is_msan = 'is_msan=true' in vals['gn_args']
1113
1114 err = ''
1115 for f in files:
1116 # Skip a few configs that need extra cleanup for now.
1117 # TODO(https://crbug.com/912946): Fix everything on all platforms and
1118 # enable check everywhere.
Nico Weberd9886b92019-09-10 17:52:171119 if is_android:
Nico Weber0fd016762019-08-25 14:48:141120 break
1121
1122 # Skip a few existing violations that need to be cleaned up. Each of
1123 # these will lead to incorrect incremental builds if their directory
1124 # contents change. Do not add to this list.
1125 # TODO(https://crbug.com/912946): Remove this if statement.
Nico Weber89895822019-08-27 18:59:031126 if ((is_msan and f == 'instrumented_libraries_prebuilt/') or
Clifford Chenge1244822019-08-27 17:26:551127 f == 'mr_extension/' or # https://crbug.com/997947
Nico Weber0fd016762019-08-25 14:48:141128 f == 'locales/' or
1129 f.startswith('nacl_test_data/') or
Nico Weber5eee4522019-09-05 23:28:051130 f.startswith('ppapi_nacl_tests_libs/') or
Nico Weberd9886b92019-09-10 17:52:171131 (is_cros and f in ( # https://crbug.com/1002509
1132 'chromevox_test_data/',
1133 'gen/ui/file_manager/file_manager/',
1134 'resources/chromeos/',
1135 'resources/chromeos/autoclick/',
1136 'resources/chromeos/chromevox/',
1137 'resources/chromeos/select_to_speak/',
1138 'test_data/chrome/browser/resources/chromeos/autoclick/',
1139 'test_data/chrome/browser/resources/chromeos/chromevox/',
1140 'test_data/chrome/browser/resources/chromeos/select_to_speak/',
1141 )) or
Nico Weber5eee4522019-09-05 23:28:051142 (is_mac and f in ( # https://crbug.com/1000667
Nico Weber5eee4522019-09-05 23:28:051143 'AlertNotificationService.xpc/',
Nico Weber5eee4522019-09-05 23:28:051144 'Chromium Framework.framework/',
1145 'Chromium Helper.app/',
1146 'Chromium.app/',
Nico Weber5eee4522019-09-05 23:28:051147 'Content Shell.app/',
Nico Weber5eee4522019-09-05 23:28:051148 'Google Chrome Framework.framework/',
1149 'Google Chrome Helper (GPU).app/',
Nico Weber5eee4522019-09-05 23:28:051150 'Google Chrome Helper (Plugin).app/',
Nico Weber5eee4522019-09-05 23:28:051151 'Google Chrome Helper (Renderer).app/',
Nico Weber5eee4522019-09-05 23:28:051152 'Google Chrome Helper.app/',
Nico Weber5eee4522019-09-05 23:28:051153 'Google Chrome.app/',
Nico Weber5eee4522019-09-05 23:28:051154 'blink_deprecated_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051155 'blink_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051156 'corb_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051157 'obj/tools/grit/brotli_mac_asan_workaround/',
1158 'power_saver_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051159 'ppapi_tests.plugin/',
Nico Weber5eee4522019-09-05 23:28:051160 'ui_unittests Framework.framework/',
1161 ))):
Nico Weber0fd016762019-08-25 14:48:141162 continue
1163
Nico Weber24e54f992019-08-26 14:33:321164 # This runs before the build, so we can't use isdir(f). But
Nico Weber0fd016762019-08-25 14:48:141165 # isolate.py luckily requires data directories to end with '/', so we
Nico Weber24e54f992019-08-26 14:33:321166 # can check for that.
Nico Weber57dbc9952019-09-04 13:33:581167 if not f.startswith('../../') and f.endswith('/'):
Nico Weber24e54f992019-08-26 14:33:321168 # Don't use self.PathJoin() -- all involved paths consistently use
1169 # forward slashes, so don't add one single backslash on Windows.
1170 err += '\n' + build_dir + '/' + f
Nico Weber0fd016762019-08-25 14:48:141171
1172 if err:
1173 self.Print('error: gn `data` items may not list generated directories; '
Nico Weber24e54f992019-08-26 14:33:321174 'list files in directory instead for:' + err)
Nico Weber0fd016762019-08-25 14:48:141175 return 1
1176
dpranke751516a2015-10-03 01:11:341177 self.WriteFile(isolate_path,
1178 pprint.pformat({
1179 'variables': {
1180 'command': command,
Nico Weber0fd016762019-08-25 14:48:141181 'files': files,
dpranke751516a2015-10-03 01:11:341182 }
1183 }) + '\n')
1184
1185 self.WriteJSON(
1186 {
1187 'args': [
1188 '--isolated',
1189 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1190 '--isolate',
1191 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1192 ],
1193 'dir': self.chromium_src_dir,
1194 'version': 1,
1195 },
1196 isolate_path + 'd.gen.json',
1197 )
1198
dprankecb4a2e242016-09-19 01:13:141199 def MapTargetsToLabels(self, isolate_map, targets):
1200 labels = []
1201 err = ''
1202
dprankecb4a2e242016-09-19 01:13:141203 for target in targets:
1204 if target == 'all':
1205 labels.append(target)
1206 elif target.startswith('//'):
1207 labels.append(target)
1208 else:
1209 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421210 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141211 err += ('test target "%s" type is unknown\n' % target)
1212 else:
thakis024d6f32017-05-16 23:21:421213 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141214 else:
1215 err += ('target "%s" not found in '
1216 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1217
1218 return err, labels
1219
dprankeeca4a782016-04-14 01:42:381220 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461221 if self.platform == 'linux2':
1222 subdir, exe = 'linux64', 'gn'
1223 elif self.platform == 'darwin':
1224 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251225 elif self.platform == 'aix6':
1226 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461227 else:
1228 subdir, exe = 'win', 'gn.exe'
1229
1230 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081231 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521232
dprankecb4a2e242016-09-19 01:13:141233
Garrett Beatyb6cee042019-04-22 18:42:091234 def GNArgs(self, vals, expand_imports=False):
dpranke73ed0d62016-04-25 19:18:341235 if vals['cros_passthrough']:
1236 if not 'GN_ARGS' in os.environ:
1237 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1238 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161239 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301240 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341241 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351242 if vals['gn_args']:
1243 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341244 else:
1245 gn_args = vals['gn_args']
1246
dpranked0c138b2016-04-13 18:28:471247 if self.args.goma_dir:
1248 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381249
agrieve41d21a72016-04-14 18:02:261250 android_version_code = self.args.android_version_code
1251 if android_version_code:
1252 gn_args += ' android_default_version_code="%s"' % android_version_code
1253
1254 android_version_name = self.args.android_version_name
1255 if android_version_name:
1256 gn_args += ' android_default_version_name="%s"' % android_version_name
1257
Garrett Beatyb6cee042019-04-22 18:42:091258 args_gn_lines = []
1259 parsed_gn_args = {}
dprankeeca4a782016-04-14 01:42:381260
Ben Pastene65ccf6132018-11-08 00:47:591261 # If we're using the Simple Chrome SDK, add a comment at the top that
1262 # points to the doc. This must happen after the gn_helpers.ToGNString()
1263 # call above since gn_helpers strips comments.
1264 if vals['cros_passthrough']:
Garrett Beatyb6cee042019-04-22 18:42:091265 args_gn_lines.extend([
Ben Pastene65ccf6132018-11-08 00:47:591266 '# These args are generated via the Simple Chrome SDK. See the link',
1267 '# below for more details:',
1268 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
Garrett Beatyb6cee042019-04-22 18:42:091269 ])
Ben Pastene65ccf6132018-11-08 00:47:591270
dpranke9dd5e252016-04-14 04:23:091271 args_file = vals.get('args_file', None)
1272 if args_file:
Garrett Beatyb6cee042019-04-22 18:42:091273 if expand_imports:
1274 content = self.ReadFile(self.ToAbsPath(args_file))
1275 parsed_gn_args = gn_helpers.FromGNArgs(content)
1276 else:
1277 args_gn_lines.append('import("%s")' % args_file)
1278
1279 # Canonicalize the arg string into a sorted, newline-separated list
1280 # of key-value pairs, and de-dup the keys if need be so that only
1281 # the last instance of each arg is listed.
1282 parsed_gn_args.update(gn_helpers.FromGNArgs(gn_args))
1283 args_gn_lines.append(gn_helpers.ToGNString(parsed_gn_args))
1284
1285 return '\n'.join(args_gn_lines)
dprankefe4602312015-04-08 16:20:351286
dprankecb4a2e242016-09-19 01:13:141287 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071288 isolate_map = self.ReadIsolateMap()
1289
Scott Graham3be4b4162017-09-12 00:41:411290 is_android = 'target_os="android"' in vals['gn_args']
1291 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Caleb Raittof983d102019-06-21 23:05:021292 is_cros = 'target_os="chromeos"' in vals['gn_args']
Nico Webera7bc1cb2019-06-15 17:42:391293 is_simplechrome = vals.get('cros_passthrough', False)
1294 is_mac = self.platform == 'darwin'
Nico Weberd94b71a2018-02-22 22:00:301295 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061296
kylechar39705682017-01-19 14:37:231297 # This should be true if tests with type='windowed_test_launcher' are
1298 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081299 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1300 # backends. Currently, tests are executed for the headless and X11 backends
1301 # and both can run under Xvfb.
1302 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1303 # backend.
Scott Graham3be4b4162017-09-12 00:41:411304 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251305
1306 asan = 'is_asan=true' in vals['gn_args']
1307 msan = 'is_msan=true' in vals['gn_args']
1308 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411309 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
Yun Liu7cef1072019-06-27 21:22:191310 java_coverage = 'jacoco_coverage=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251311
dprankecb4a2e242016-09-19 01:13:141312 test_type = isolate_map[target]['type']
Brian Sheedy234580e52019-09-10 17:42:511313 use_python3 = isolate_map[target].get('use_python3', False)
dprankefe0d35e2016-02-05 02:43:591314
dprankecb4a2e242016-09-19 01:13:141315 executable = isolate_map[target].get('executable', target)
bsheedy9c16ed62019-04-10 20:32:111316 executable_suffix = isolate_map[target].get(
1317 'executable_suffix', '.exe' if is_win else '')
dprankefe0d35e2016-02-05 02:43:591318
Brian Sheedy234580e52019-09-10 17:42:511319 if use_python3:
1320 cmdline = [ 'vpython3' ]
1321 extra_files = [ '../../.vpython3' ]
1322 else:
1323 cmdline = [ 'vpython' ]
1324 extra_files = [ '../../.vpython' ]
1325 extra_files += [
Andrii Shyshkalovc158e0102018-01-10 05:52:001326 '../../testing/test_env.py',
1327 ]
dpranked8113582015-06-05 20:08:251328
dprankecb4a2e242016-09-19 01:13:141329 if test_type == 'nontest':
1330 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1331 output_path=None)
1332
John Budorick93e88ac82019-04-12 18:39:111333 if test_type == 'generated_script':
Ben Pastenecb0fb412019-06-11 02:31:541334 script = isolate_map[target]['script']
1335 if self.platform == 'win32':
1336 script += '.bat'
Brian Sheedy234580e52019-09-10 17:42:511337 cmdline += [
John Budorick93e88ac82019-04-12 18:39:111338 '../../testing/test_env.py',
Ben Pastenecb0fb412019-06-11 02:31:541339 script,
John Budorick93e88ac82019-04-12 18:39:111340 ]
1341 elif test_type == 'fuzzer':
Brian Sheedy234580e52019-09-10 17:42:511342 cmdline += [
Roberto Carrillo1460da852018-12-14 17:10:391343 '../../testing/test_env.py',
1344 '../../tools/code_coverage/run_fuzz_target.py',
1345 '--fuzzer', './' + target,
1346 '--output-dir', '${ISOLATED_OUTDIR}',
1347 '--timeout', '3600']
1348 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011349 if asan:
John Budorick31cdce62019-04-03 20:56:111350 cmdline += [os.path.join('bin', 'run_with_asan'), '--']
John Budorick8c4203042019-03-19 17:22:011351 cmdline += [
John Budorickfb97a852017-12-20 20:10:191352 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041353 '../../build/android/test_wrapper/logdog_wrapper.py',
1354 '--target', target,
hzl9ae14452017-04-04 23:38:021355 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481356 '--store-tombstones']
Yun Liu7cef1072019-06-27 21:22:191357 if java_coverage:
1358 cmdline += ['--coverage-dir', '${ISOLATED_OUTDIR}']
Scott Graham3be4b4162017-09-12 00:41:411359 elif is_fuchsia and test_type != 'script':
Brian Sheedy234580e52019-09-10 17:42:511360 cmdline += [
John Budorickfb97a852017-12-20 20:10:191361 '../../testing/test_env.py',
1362 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441363 '--test-launcher-bot-mode',
Sergey Ulanovd851243b2019-06-25 00:33:471364 '--system-log-file', '${ISOLATED_OUTDIR}/system_log'
John Budorickfb97a852017-12-20 20:10:191365 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321366 elif is_simplechrome and test_type != 'script':
Brian Sheedy234580e52019-09-10 17:42:511367 cmdline += [
Benjamin Pastene3bce864e2018-04-14 01:16:321368 '../../testing/test_env.py',
1369 os.path.join('bin', 'run_%s' % target),
1370 ]
kylechar39705682017-01-19 14:37:231371 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001372 extra_files.append('../../testing/xvfb.py')
Brian Sheedy234580e52019-09-10 17:42:511373 cmdline += [
Nico Webera7bc1cb2019-06-15 17:42:391374 '../../testing/xvfb.py',
1375 './' + str(executable) + executable_suffix,
1376 '--test-launcher-bot-mode',
1377 '--asan=%d' % asan,
1378 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1379 # supported.
1380 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021381 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1382 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
Nico Webera7bc1cb2019-06-15 17:42:391383 '--msan=%d' % msan,
1384 '--tsan=%d' % tsan,
1385 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471386 ]
1387 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
Brian Sheedy234580e52019-09-10 17:42:511388 cmdline += [
dprankea55584f12015-07-22 00:52:471389 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591390 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251391 '--test-launcher-bot-mode',
1392 '--asan=%d' % asan,
Caleb Raitto1fb2cc9e2019-06-14 01:04:231393 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1394 # supported.
Nico Webera7bc1cb2019-06-15 17:42:391395 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021396 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1397 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
dpranked8113582015-06-05 20:08:251398 '--msan=%d' % msan,
1399 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411400 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471401 ]
dpranke6abd8652015-08-28 03:21:111402 elif test_type == 'script':
Ben Pastene4534c39e2019-07-08 22:55:341403 # If we're testing a CrOS simplechrome build, assume we need to prepare a
1404 # DUT for testing. So prepend the command to run with the test wrapper.
Ben Pastene8ab6954d2018-05-04 04:08:241405 if is_simplechrome:
Ben Pastene908863c2019-07-25 16:20:031406 cmdline = [
1407 os.path.join('bin', 'cros_test_wrapper'),
1408 '--logs-dir=${ISOLATED_OUTDIR}',
1409 ]
Ben Pastene8ab6954d2018-05-04 04:08:241410 cmdline += [
dpranke6abd8652015-08-28 03:21:111411 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141412 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591413 ]
Dirk Prankef24e6b22018-03-27 20:12:301414 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471415 cmdline = [
1416 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591417 ]
dprankea55584f12015-07-22 00:52:471418 else:
1419 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1420 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251421
dprankecb4a2e242016-09-19 01:13:141422 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591423
dpranked8113582015-06-05 20:08:251424 return cmdline, extra_files
1425
dpranke74559b52015-06-10 21:20:391426 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331427 return self.PathJoin(self.chromium_src_dir,
1428 self.ToSrcRelPath(build_path),
1429 *comps)
dpranked8113582015-06-05 20:08:251430
dprankeee5b51f62015-04-09 00:03:221431 def ToSrcRelPath(self, path):
1432 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501433 if path.startswith('//'):
1434 return path[2:].replace('/', self.sep)
1435 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351436
Dirk Pranke0fd41bcd2015-06-19 00:05:501437 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141438 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501439 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001440 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501441 if ret:
1442 return ret
1443
Dirk Prankef24e6b22018-03-27 20:12:301444 build_path = self.args.path
1445 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141446 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301447 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141448 gn_output_path = output_path + '.gn'
1449
dpranke7837fc362015-11-19 03:54:161450 inp = self.ReadInputJSON(['files', 'test_targets',
1451 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321452 if self.args.verbose:
1453 self.Print()
1454 self.Print('analyze input:')
1455 self.PrintJSON(inp)
1456 self.Print()
1457
dpranke76734662015-04-16 02:17:501458
dpranke7c5f614d2015-07-22 23:43:391459 # This shouldn't normally happen, but could due to unusual race conditions,
1460 # like a try job that gets scheduled before a patch lands but runs after
1461 # the patch has landed.
1462 if not inp['files']:
1463 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161464 self.WriteJSON({
1465 'status': 'No dependency',
1466 'compile_targets': [],
1467 'test_targets': [],
1468 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391469 return 0
1470
dprankecb4a2e242016-09-19 01:13:141471 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161472 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561473
dprankecb4a2e242016-09-19 01:13:141474 isolate_map = self.ReadIsolateMap()
1475 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1476 isolate_map, inp['additional_compile_targets'])
1477 if err:
1478 raise MBErr(err)
1479
1480 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1481 isolate_map, inp['test_targets'])
1482 if err:
1483 raise MBErr(err)
1484 labels_to_targets = {}
1485 for i, label in enumerate(gn_inp['test_targets']):
1486 labels_to_targets[label] = inp['test_targets'][i]
1487
dprankef61de2f2015-05-14 04:09:561488 try:
dprankecb4a2e242016-09-19 01:13:141489 self.WriteJSON(gn_inp, gn_input_path)
1490 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
Debrian Figueroaae51d0d2019-07-22 18:04:111491 ret, output, _ = self.Run(cmd, force_verbose=True)
dprankecb4a2e242016-09-19 01:13:141492 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:111493 if self.args.json_output:
1494 # write errors to json.output
1495 self.WriteJSON({'output': output}, self.args.json_output)
dprankecb4a2e242016-09-19 01:13:141496 return ret
dpranke067d0142015-05-14 22:52:451497
dprankecb4a2e242016-09-19 01:13:141498 gn_outp_str = self.ReadFile(gn_output_path)
1499 try:
1500 gn_outp = json.loads(gn_outp_str)
1501 except Exception as e:
1502 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1503 % (repr(gn_outp_str), str(e)))
1504 raise
1505
1506 outp = {}
1507 if 'status' in gn_outp:
1508 outp['status'] = gn_outp['status']
1509 if 'error' in gn_outp:
1510 outp['error'] = gn_outp['error']
1511 if 'invalid_targets' in gn_outp:
1512 outp['invalid_targets'] = gn_outp['invalid_targets']
1513 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491514 all_input_compile_targets = sorted(
1515 set(inp['test_targets'] + inp['additional_compile_targets']))
1516
1517 # If we're building 'all', we can throw away the rest of the targets
1518 # since they're redundant.
dpranke385a3102016-09-20 22:04:081519 if 'all' in gn_outp['compile_targets']:
1520 outp['compile_targets'] = ['all']
1521 else:
Dirk Pranke45165072017-11-08 04:57:491522 outp['compile_targets'] = gn_outp['compile_targets']
1523
1524 # crbug.com/736215: When GN returns targets back, for targets in
1525 # the default toolchain, GN will have generated a phony ninja
1526 # target matching the label, and so we can safely (and easily)
1527 # transform any GN label into the matching ninja target. For
1528 # targets in other toolchains, though, GN doesn't generate the
1529 # phony targets, and we don't know how to turn the labels into
1530 # compile targets. In this case, we also conservatively give up
1531 # and build everything. Probably the right thing to do here is
1532 # to have GN return the compile targets directly.
1533 if any("(" in target for target in outp['compile_targets']):
1534 self.Print('WARNING: targets with non-default toolchains were '
1535 'found, building everything instead.')
1536 outp['compile_targets'] = all_input_compile_targets
1537 else:
dpranke385a3102016-09-20 22:04:081538 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491539 label.replace('//', '') for label in outp['compile_targets']]
1540
1541 # Windows has a maximum command line length of 8k; even Linux
1542 # maxes out at 128k; if analyze returns a *really long* list of
1543 # targets, we just give up and conservatively build everything instead.
1544 # Probably the right thing here is for ninja to support response
1545 # files as input on the command line
1546 # (see https://github.com/ninja-build/ninja/issues/1355).
1547 if len(' '.join(outp['compile_targets'])) > 7*1024:
1548 self.Print('WARNING: Too many compile targets were affected.')
1549 self.Print('WARNING: Building everything instead to avoid '
1550 'command-line length issues.')
1551 outp['compile_targets'] = all_input_compile_targets
1552
1553
dprankecb4a2e242016-09-19 01:13:141554 if 'test_targets' in gn_outp:
1555 outp['test_targets'] = [
1556 labels_to_targets[label] for label in gn_outp['test_targets']]
1557
1558 if self.args.verbose:
1559 self.Print()
1560 self.Print('analyze output:')
1561 self.PrintJSON(outp)
1562 self.Print()
1563
1564 self.WriteJSON(outp, output_path)
1565
dprankef61de2f2015-05-14 04:09:561566 finally:
dprankecb4a2e242016-09-19 01:13:141567 if self.Exists(gn_input_path):
1568 self.RemoveFile(gn_input_path)
1569 if self.Exists(gn_output_path):
1570 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351571
1572 return 0
1573
dpranked8113582015-06-05 20:08:251574 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301575 path = self.args.input_path
1576 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351577 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321578 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351579
1580 try:
1581 inp = json.loads(self.ReadFile(path))
1582 except Exception as e:
1583 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321584 (path, e), output_path)
dpranked8113582015-06-05 20:08:251585
1586 for k in required_keys:
1587 if not k in inp:
1588 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1589 output_path)
dprankefe4602312015-04-08 16:20:351590
1591 return inp
1592
dpranked5b2b9432015-06-23 16:55:301593 def WriteFailureAndRaise(self, msg, output_path):
1594 if output_path:
dprankee0547cd2015-09-15 01:27:401595 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351596 raise MBErr(msg)
1597
dprankee0547cd2015-09-15 01:27:401598 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321599 try:
dprankee0547cd2015-09-15 01:27:401600 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1601 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321602 except Exception as e:
1603 raise MBErr('Error %s writing to the output path "%s"' %
1604 (e, path))
dprankefe4602312015-04-08 16:20:351605
aneeshmde50f472016-04-01 01:13:101606 def CheckCompile(self, master, builder):
1607 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1608 url = urllib2.quote(url_template.format(master=master, builder=builder),
1609 safe=':/()?=')
1610 try:
1611 builds = json.loads(self.Fetch(url))
1612 except Exception as e:
1613 return str(e)
1614 successes = sorted(
1615 [int(x) for x in builds.keys() if "text" in builds[x] and
1616 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1617 reverse=True)
1618 if not successes:
1619 return "no successful builds"
1620 build = builds[str(successes[0])]
1621 step_names = set([step["name"] for step in build["steps"]])
1622 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1623 if compile_indicators & step_names:
1624 return "compiles"
1625 return "does not compile"
1626
dpranke3cec199c2015-09-22 23:29:021627 def PrintCmd(self, cmd, env):
1628 if self.platform == 'win32':
1629 env_prefix = 'set '
1630 env_quoter = QuoteForSet
1631 shell_quoter = QuoteForCmd
1632 else:
1633 env_prefix = ''
1634 env_quoter = pipes.quote
1635 shell_quoter = pipes.quote
1636
1637 def print_env(var):
1638 if env and var in env:
1639 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1640
dprankeec079262016-06-07 02:21:201641 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021642
dpranke8c2cfd32015-09-17 20:12:331643 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351644 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021645 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351646
dprankecda00332015-04-11 04:18:321647 def PrintJSON(self, obj):
1648 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1649
dpranke751516a2015-10-03 01:11:341650 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301651 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381652 if self.platform == 'win32':
1653 # On Windows use the batch script since there is no exe
1654 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1655 else:
1656 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341657 if self.args.jobs:
1658 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1659 ninja_cmd.append(target)
Dirk Pranke5f22a822019-05-23 22:55:251660 ret, _, _ = self.Run(ninja_cmd, buffer_output=False)
dpranke751516a2015-10-03 01:11:341661 return ret
1662
1663 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351664 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401665 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021666 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351667 if self.args.dryrun:
1668 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401669
dpranke751516a2015-10-03 01:11:341670 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401671 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341672 if ret:
1673 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351674 if out:
Debrian Figueroaae582232019-07-17 01:54:451675 # This is the error seen on the logs
dprankeee5b51f62015-04-09 00:03:221676 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351677 if err:
dprankeee5b51f62015-04-09 00:03:221678 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351679 return ret, out, err
1680
dpranke751516a2015-10-03 01:11:341681 def Call(self, cmd, env=None, buffer_output=True):
1682 if buffer_output:
1683 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1684 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1685 env=env)
1686 out, err = p.communicate()
1687 else:
1688 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1689 env=env)
1690 p.wait()
1691 out = err = ''
dprankefe4602312015-04-08 16:20:351692 return p.returncode, out, err
1693
1694 def ExpandUser(self, path):
1695 # This function largely exists so it can be overridden for testing.
1696 return os.path.expanduser(path)
1697
1698 def Exists(self, path):
1699 # This function largely exists so it can be overridden for testing.
1700 return os.path.exists(path)
1701
dpranke867bcf4a2016-03-14 22:28:321702 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501703 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321704 f = urllib2.urlopen(url)
1705 contents = f.read()
1706 f.close()
1707 return contents
1708
dprankec3441d12015-06-23 23:01:351709 def MaybeMakeDirectory(self, path):
1710 try:
1711 os.makedirs(path)
1712 except OSError, e:
1713 if e.errno != errno.EEXIST:
1714 raise
1715
dpranke8c2cfd32015-09-17 20:12:331716 def PathJoin(self, *comps):
1717 # This function largely exists so it can be overriden for testing.
1718 return os.path.join(*comps)
1719
dpranke030d7a6d2016-03-26 17:23:501720 def Print(self, *args, **kwargs):
1721 # This function largely exists so it can be overridden for testing.
1722 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101723 if kwargs.get('stream', sys.stdout) == sys.stdout:
1724 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501725
dprankefe4602312015-04-08 16:20:351726 def ReadFile(self, path):
1727 # This function largely exists so it can be overriden for testing.
1728 with open(path) as fp:
1729 return fp.read()
1730
dpranke030d7a6d2016-03-26 17:23:501731 def RelPath(self, path, start='.'):
1732 # This function largely exists so it can be overriden for testing.
1733 return os.path.relpath(path, start)
1734
dprankef61de2f2015-05-14 04:09:561735 def RemoveFile(self, path):
1736 # This function largely exists so it can be overriden for testing.
1737 os.remove(path)
1738
dprankec161aa92015-09-14 20:21:131739 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331740 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131741 # In other places in chromium, we often have to retry this command
1742 # because we're worried about other processes still holding on to
1743 # file handles, but when MB is invoked, it will be early enough in the
1744 # build that their should be no other processes to interfere. We
1745 # can change this if need be.
1746 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1747 else:
1748 shutil.rmtree(abs_path, ignore_errors=True)
1749
Dirk Prankef24e6b22018-03-27 20:12:301750 def TempDir(self):
1751 # This function largely exists so it can be overriden for testing.
1752 return tempfile.mkdtemp(prefix='mb_')
1753
dprankef61de2f2015-05-14 04:09:561754 def TempFile(self, mode='w'):
1755 # This function largely exists so it can be overriden for testing.
1756 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1757
dprankee0547cd2015-09-15 01:27:401758 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351759 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401760 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301761 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351762 with open(path, 'w') as fp:
1763 return fp.write(contents)
1764
dprankef61de2f2015-05-14 04:09:561765
dprankefe4602312015-04-08 16:20:351766class MBErr(Exception):
1767 pass
1768
1769
dpranke3cec199c2015-09-22 23:29:021770# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1771# details of this next section, which handles escaping command lines
1772# so that they can be copied and pasted into a cmd window.
1773UNSAFE_FOR_SET = set('^<>&|')
1774UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1775ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1776
1777
1778def QuoteForSet(arg):
1779 if any(a in UNSAFE_FOR_SET for a in arg):
1780 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1781 return arg
1782
1783
1784def QuoteForCmd(arg):
1785 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021786 if arg == '' or ' ' in arg or '"' in arg:
1787 quote_re = re.compile(r'(\\*)"')
1788 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1789
1790 # Then check to see if the arg contains any metacharacters other than
1791 # double quotes; if it does, quote everything (including the double
1792 # quotes) for safety.
1793 if any(a in UNSAFE_FOR_CMD for a in arg):
1794 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1795 return arg
1796
1797
dprankefe4602312015-04-08 16:20:351798if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591799 sys.exit(main(sys.argv[1:]))