blob: e32ad9ee65cea666d67c386074883bdfd8e3e741 [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
dprankefe4602312015-04-08 16:20:3567class MetaBuildWrapper(object):
68 def __init__(self):
dprankeeca4a782016-04-14 01:42:3869 self.chromium_src_dir = CHROMIUM_SRC_DIR
70 self.default_config = os.path.join(self.chromium_src_dir, 'tools', 'mb',
71 'mb_config.pyl')
kjellander902bcb62016-10-26 06:20:5072 self.default_isolate_map = os.path.join(self.chromium_src_dir, 'testing',
73 'buildbot', 'gn_isolate_map.pyl')
dpranke8c2cfd32015-09-17 20:12:3374 self.executable = sys.executable
dpranked1fba482015-04-14 20:54:5175 self.platform = sys.platform
dpranke8c2cfd32015-09-17 20:12:3376 self.sep = os.sep
dprankefe4602312015-04-08 16:20:3577 self.args = argparse.Namespace()
78 self.configs = {}
79 self.masters = {}
80 self.mixins = {}
dprankefe4602312015-04-08 16:20:3581
dpranke255085e2016-03-16 05:23:5982 def Main(self, args):
83 self.ParseArgs(args)
84 try:
85 ret = self.args.func()
86 if ret:
87 self.DumpInputFiles()
88 return ret
89 except KeyboardInterrupt:
dprankecb4a2e242016-09-19 01:13:1490 self.Print('interrupted, exiting')
dpranke255085e2016-03-16 05:23:5991 return 130
dprankebbe6d4672016-04-19 06:56:5792 except Exception:
dpranke255085e2016-03-16 05:23:5993 self.DumpInputFiles()
dprankebbe6d4672016-04-19 06:56:5794 s = traceback.format_exc()
95 for l in s.splitlines():
96 self.Print(l)
dpranke255085e2016-03-16 05:23:5997 return 1
98
dprankefe4602312015-04-08 16:20:3599 def ParseArgs(self, argv):
100 def AddCommonOptions(subp):
101 subp.add_argument('-b', '--builder',
102 help='builder name to look up config from')
103 subp.add_argument('-m', '--master',
104 help='master name to look up config from')
105 subp.add_argument('-c', '--config',
106 help='configuration to analyze')
shenghuazhang804b21542016-10-11 02:06:49107 subp.add_argument('--phase',
108 help='optional phase name (used when builders '
109 'do multiple compiles with different '
110 'arguments in a single build)')
dprankefe4602312015-04-08 16:20:35111 subp.add_argument('-f', '--config-file', metavar='PATH',
112 default=self.default_config,
113 help='path to config file '
kjellander902bcb62016-10-26 06:20:50114 '(default is %(default)s)')
115 subp.add_argument('-i', '--isolate-map-file', metavar='PATH',
kjellander902bcb62016-10-26 06:20:50116 help='path to isolate map file '
Zhiling Huang66958462018-02-03 00:28:20117 '(default is %(default)s)',
118 default=[],
119 action='append',
120 dest='isolate_map_files')
dpranked0c138b2016-04-13 18:28:47121 subp.add_argument('-g', '--goma-dir',
122 help='path to goma directory')
agrieve41d21a72016-04-14 18:02:26123 subp.add_argument('--android-version-code',
Dirk Pranked181a1a2017-12-14 01:47:11124 help='Sets GN arg android_default_version_code')
agrieve41d21a72016-04-14 18:02:26125 subp.add_argument('--android-version-name',
Dirk Pranked181a1a2017-12-14 01:47:11126 help='Sets GN arg android_default_version_name')
dprankefe4602312015-04-08 16:20:35127 subp.add_argument('-n', '--dryrun', action='store_true',
128 help='Do a dry run (i.e., do nothing, just print '
129 'the commands that will run)')
dprankee0547cd2015-09-15 01:27:40130 subp.add_argument('-v', '--verbose', action='store_true',
131 help='verbose logging')
dprankefe4602312015-04-08 16:20:35132
Stephen Martinisb40a6852019-07-23 01:48:30133 parser = argparse.ArgumentParser(
134 prog='mb', description='mb (meta-build) is a python wrapper around GN. '
135 'See the user guide in '
136 '//tools/mb/docs/user_guide.md for detailed usage '
137 'instructions.')
138
dprankefe4602312015-04-08 16:20:35139 subps = parser.add_subparsers()
140
141 subp = subps.add_parser('analyze',
Stephen Martinis239c35a2019-07-22 19:34:40142 description='Analyze whether changes to a set of '
143 'files will cause a set of binaries to '
144 'be rebuilt.')
dprankefe4602312015-04-08 16:20:35145 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30146 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35147 help='path build was generated into.')
Dirk Prankef24e6b22018-03-27 20:12:30148 subp.add_argument('input_path',
dprankefe4602312015-04-08 16:20:35149 help='path to a file containing the input arguments '
150 'as a JSON object.')
Dirk Prankef24e6b22018-03-27 20:12:30151 subp.add_argument('output_path',
dprankefe4602312015-04-08 16:20:35152 help='path to a file containing the output arguments '
153 'as a JSON object.')
Debrian Figueroaae51d0d2019-07-22 18:04:11154 subp.add_argument('--json-output',
Debrian Figueroaae582232019-07-17 01:54:45155 help='Write errors to json.output')
dprankefe4602312015-04-08 16:20:35156 subp.set_defaults(func=self.CmdAnalyze)
157
dprankef37aebb92016-09-23 01:14:49158 subp = subps.add_parser('export',
Stephen Martinis239c35a2019-07-22 19:34:40159 description='Print out the expanded configuration '
160 'for each builder as a JSON object.')
dprankef37aebb92016-09-23 01:14:49161 subp.add_argument('-f', '--config-file', metavar='PATH',
162 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50163 help='path to config file (default is %(default)s)')
dprankef37aebb92016-09-23 01:14:49164 subp.add_argument('-g', '--goma-dir',
165 help='path to goma directory')
166 subp.set_defaults(func=self.CmdExport)
167
dprankefe4602312015-04-08 16:20:35168 subp = subps.add_parser('gen',
Stephen Martinis239c35a2019-07-22 19:34:40169 description='Generate a new set of build files.')
dprankefe4602312015-04-08 16:20:35170 AddCommonOptions(subp)
dpranke74559b52015-06-10 21:20:39171 subp.add_argument('--swarming-targets-file',
172 help='save runtime dependencies for targets listed '
173 'in file.')
Debrian Figueroaae51d0d2019-07-22 18:04:11174 subp.add_argument('--json-output',
Debrian Figueroaae582232019-07-17 01:54:45175 help='Write errors to json.output')
Dirk Prankef24e6b22018-03-27 20:12:30176 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35177 help='path to generate build into')
178 subp.set_defaults(func=self.CmdGen)
179
Erik Chen42df41d2018-08-21 17:13:31180 subp = subps.add_parser('isolate-everything',
Stephen Martinis239c35a2019-07-22 19:34:40181 description='Generates a .isolate for all targets. '
182 'Requires that mb.py gen has already '
183 'been run.')
Erik Chen42df41d2018-08-21 17:13:31184 AddCommonOptions(subp)
185 subp.set_defaults(func=self.CmdIsolateEverything)
186 subp.add_argument('path',
187 help='path build was generated into')
188
dpranke751516a2015-10-03 01:11:34189 subp = subps.add_parser('isolate',
Stephen Martinis239c35a2019-07-22 19:34:40190 description='Generate the .isolate files for a '
191 'given binary.')
dpranke751516a2015-10-03 01:11:34192 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30193 subp.add_argument('--no-build', dest='build', default=True,
194 action='store_false',
195 help='Do not build, just isolate')
196 subp.add_argument('-j', '--jobs', type=int,
197 help='Number of jobs to pass to ninja')
198 subp.add_argument('path',
dpranke751516a2015-10-03 01:11:34199 help='path build was generated into')
Dirk Prankef24e6b22018-03-27 20:12:30200 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34201 help='ninja target to generate the isolate for')
202 subp.set_defaults(func=self.CmdIsolate)
203
dprankefe4602312015-04-08 16:20:35204 subp = subps.add_parser('lookup',
Stephen Martinis239c35a2019-07-22 19:34:40205 description='Look up the command for a given '
206 'config or builder.')
dprankefe4602312015-04-08 16:20:35207 AddCommonOptions(subp)
Garrett Beatyb6cee042019-04-22 18:42:09208 subp.add_argument('--quiet', default=False, action='store_true',
209 help='Print out just the arguments, '
210 'do not emulate the output of the gen subcommand.')
211 subp.add_argument('--recursive', default=False, action='store_true',
212 help='Lookup arguments from imported files, '
213 'implies --quiet')
dprankefe4602312015-04-08 16:20:35214 subp.set_defaults(func=self.CmdLookup)
215
Stephen Martiniscd377012019-10-18 17:40:46216 subp = subps.add_parser('try',
217 description='Try your change on a remote builder')
218 AddCommonOptions(subp)
219 subp.add_argument('target',
220 help='ninja target to build and run')
Stephen Martinis3016084b2019-11-20 20:26:22221 subp.add_argument('--force', default=False, action='store_true',
222 help='Force the job to run. Ignores local checkout state;'
223 ' by default, the tool doesn\'t trigger jobs if there are'
224 ' local changes which are not present on Gerrit.')
Stephen Martiniscd377012019-10-18 17:40:46225 subp.set_defaults(func=self.CmdTry)
226
dpranke030d7a6d2016-03-26 17:23:50227 subp = subps.add_parser(
Stephen Martinis239c35a2019-07-22 19:34:40228 'run', formatter_class=argparse.RawDescriptionHelpFormatter)
dpranke030d7a6d2016-03-26 17:23:50229 subp.description = (
230 'Build, isolate, and run the given binary with the command line\n'
231 'listed in the isolate. You may pass extra arguments after the\n'
232 'target; use "--" if the extra arguments need to include switches.\n'
233 '\n'
234 'Examples:\n'
235 '\n'
236 ' % tools/mb/mb.py run -m chromium.linux -b "Linux Builder" \\\n'
237 ' //out/Default content_browsertests\n'
238 '\n'
239 ' % tools/mb/mb.py run out/Default content_browsertests\n'
240 '\n'
241 ' % tools/mb/mb.py run out/Default content_browsertests -- \\\n'
242 ' --test-launcher-retry-limit=0'
243 '\n'
244 )
dpranke751516a2015-10-03 01:11:34245 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30246 subp.add_argument('-j', '--jobs', type=int,
dpranke751516a2015-10-03 01:11:34247 help='Number of jobs to pass to ninja')
248 subp.add_argument('--no-build', dest='build', default=True,
249 action='store_false',
250 help='Do not build, just isolate and run')
Dirk Prankef24e6b22018-03-27 20:12:30251 subp.add_argument('path',
dpranke030d7a6d2016-03-26 17:23:50252 help=('path to generate build into (or use).'
253 ' This can be either a regular path or a '
254 'GN-style source-relative path like '
255 '//out/Default.'))
Dirk Pranke8cb6aa782017-12-16 02:31:33256 subp.add_argument('-s', '--swarmed', action='store_true',
257 help='Run under swarming with the default dimensions')
258 subp.add_argument('-d', '--dimension', default=[], action='append', nargs=2,
259 dest='dimensions', metavar='FOO bar',
260 help='dimension to filter on')
261 subp.add_argument('--no-default-dimensions', action='store_false',
262 dest='default_dimensions', default=True,
263 help='Do not automatically add dimensions to the task')
Dirk Prankef24e6b22018-03-27 20:12:30264 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34265 help='ninja target to build and run')
dpranke030d7a6d2016-03-26 17:23:50266 subp.add_argument('extra_args', nargs='*',
267 help=('extra args to pass to the isolate to run. Use '
268 '"--" as the first arg if you need to pass '
269 'switches'))
dpranke751516a2015-10-03 01:11:34270 subp.set_defaults(func=self.CmdRun)
271
dprankefe4602312015-04-08 16:20:35272 subp = subps.add_parser('validate',
Stephen Martinis239c35a2019-07-22 19:34:40273 description='Validate the config file.')
dprankea5a77ca2015-07-16 23:24:17274 subp.add_argument('-f', '--config-file', metavar='PATH',
275 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50276 help='path to config file (default is %(default)s)')
dprankefe4602312015-04-08 16:20:35277 subp.set_defaults(func=self.CmdValidate)
278
Dirk Prankef24e6b22018-03-27 20:12:30279 subp = subps.add_parser('zip',
Stephen Martinis239c35a2019-07-22 19:34:40280 description='Generate a .zip containing the files '
281 'needed for a given binary.')
Dirk Prankef24e6b22018-03-27 20:12:30282 AddCommonOptions(subp)
283 subp.add_argument('--no-build', dest='build', default=True,
284 action='store_false',
285 help='Do not build, just isolate')
286 subp.add_argument('-j', '--jobs', type=int,
287 help='Number of jobs to pass to ninja')
288 subp.add_argument('path',
289 help='path build was generated into')
290 subp.add_argument('target',
291 help='ninja target to generate the isolate for')
292 subp.add_argument('zip_path',
293 help='path to zip file to create')
294 subp.set_defaults(func=self.CmdZip)
295
dprankefe4602312015-04-08 16:20:35296 subp = subps.add_parser('help',
297 help='Get help on a subcommand.')
298 subp.add_argument(nargs='?', action='store', dest='subcommand',
299 help='The command to get help for.')
300 subp.set_defaults(func=self.CmdHelp)
301
302 self.args = parser.parse_args(argv)
303
dprankeb2be10a2016-02-22 17:11:00304 def DumpInputFiles(self):
305
dprankef7b7eb7a2016-03-28 22:42:59306 def DumpContentsOfFilePassedTo(arg_name, path):
dprankeb2be10a2016-02-22 17:11:00307 if path and self.Exists(path):
dprankef7b7eb7a2016-03-28 22:42:59308 self.Print("\n# To recreate the file passed to %s:" % arg_name)
dprankecb4a2e242016-09-19 01:13:14309 self.Print("%% cat > %s <<EOF" % path)
dprankeb2be10a2016-02-22 17:11:00310 contents = self.ReadFile(path)
dprankef7b7eb7a2016-03-28 22:42:59311 self.Print(contents)
312 self.Print("EOF\n%\n")
dprankeb2be10a2016-02-22 17:11:00313
dprankef7b7eb7a2016-03-28 22:42:59314 if getattr(self.args, 'input_path', None):
315 DumpContentsOfFilePassedTo(
Dirk Prankef24e6b22018-03-27 20:12:30316 'argv[0] (input_path)', self.args.input_path)
dprankef7b7eb7a2016-03-28 22:42:59317 if getattr(self.args, 'swarming_targets_file', None):
318 DumpContentsOfFilePassedTo(
319 '--swarming-targets-file', self.args.swarming_targets_file)
dprankeb2be10a2016-02-22 17:11:00320
dprankefe4602312015-04-08 16:20:35321 def CmdAnalyze(self):
dpranke751516a2015-10-03 01:11:34322 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11323 return self.RunGNAnalyze(vals)
dprankefe4602312015-04-08 16:20:35324
dprankef37aebb92016-09-23 01:14:49325 def CmdExport(self):
326 self.ReadConfigFile()
327 obj = {}
328 for master, builders in self.masters.items():
329 obj[master] = {}
330 for builder in builders:
331 config = self.masters[master][builder]
332 if not config:
333 continue
334
shenghuazhang804b21542016-10-11 02:06:49335 if isinstance(config, dict):
336 args = {k: self.FlattenConfig(v)['gn_args']
337 for k, v in config.items()}
dprankef37aebb92016-09-23 01:14:49338 elif config.startswith('//'):
339 args = config
340 else:
341 args = self.FlattenConfig(config)['gn_args']
342 if 'error' in args:
343 continue
344
345 obj[master][builder] = args
346
347 # Dump object and trim trailing whitespace.
348 s = '\n'.join(l.rstrip() for l in
349 json.dumps(obj, sort_keys=True, indent=2).splitlines())
350 self.Print(s)
351 return 0
352
dprankefe4602312015-04-08 16:20:35353 def CmdGen(self):
dpranke751516a2015-10-03 01:11:34354 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11355 return self.RunGNGen(vals)
dprankefe4602312015-04-08 16:20:35356
Erik Chen42df41d2018-08-21 17:13:31357 def CmdIsolateEverything(self):
358 vals = self.Lookup()
359 return self.RunGNGenAllIsolates(vals)
360
dprankefe4602312015-04-08 16:20:35361 def CmdHelp(self):
362 if self.args.subcommand:
363 self.ParseArgs([self.args.subcommand, '--help'])
364 else:
365 self.ParseArgs(['--help'])
366
dpranke751516a2015-10-03 01:11:34367 def CmdIsolate(self):
368 vals = self.GetConfig()
369 if not vals:
370 return 1
Dirk Prankef24e6b22018-03-27 20:12:30371 if self.args.build:
372 ret = self.Build(self.args.target)
373 if ret:
374 return ret
Dirk Pranked181a1a2017-12-14 01:47:11375 return self.RunGNIsolate(vals)
dpranke751516a2015-10-03 01:11:34376
377 def CmdLookup(self):
378 vals = self.Lookup()
Garrett Beatyb6cee042019-04-22 18:42:09379 gn_args = self.GNArgs(vals, expand_imports=self.args.recursive)
380 if self.args.quiet or self.args.recursive:
381 self.Print(gn_args, end='')
382 else:
383 cmd = self.GNCmd('gen', '_path_')
384 self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args)
385 env = None
dpranke751516a2015-10-03 01:11:34386
Garrett Beatyb6cee042019-04-22 18:42:09387 self.PrintCmd(cmd, env)
dpranke751516a2015-10-03 01:11:34388 return 0
389
Stephen Martiniscd377012019-10-18 17:40:46390 def CmdTry(self):
Stephen Martinis9388ffc2019-10-19 00:15:08391 ninja_target = self.args.target
392 if ninja_target.startswith('//'):
Stephen Martinis3016084b2019-11-20 20:26:22393 self.Print("Expected a ninja target like base_unittests, got %s" % (
394 ninja_target))
Stephen Martiniscd377012019-10-18 17:40:46395 return 1
396
Stephen Martinis3016084b2019-11-20 20:26:22397 _, out, _ = self.Run(['git', 'cl', 'diff', '--stat'], force_verbose=False)
398 if out:
399 self.Print("Your checkout appears to local changes which are not uploaded"
400 " to Gerrit. Changes must be committed and uploaded to Gerrit"
401 " to be tested using this tool.")
402 if not self.args.force:
403 return 1
404
Stephen Martiniscd377012019-10-18 17:40:46405 json_path = self.PathJoin(self.chromium_src_dir, 'out.json')
406 try:
407 ret, out, err = self.Run(
408 ['git', 'cl', 'issue', '--json=out.json'], force_verbose=False)
409 if ret != 0:
410 self.Print(
411 "Unable to fetch current issue. Output and error:\n%s\n%s" % (
412 out, err
413 ))
414 return ret
415 with open(json_path) as f:
416 issue_data = json.load(f)
417 finally:
418 if self.Exists(json_path):
419 os.unlink(json_path)
420
421 if not issue_data['issue']:
422 self.Print("Missing issue data. Upload your CL to Gerrit and try again.")
423 return 1
424
425 def run_cmd(previous_res, cmd):
426 res, out, err = self.Run(cmd, force_verbose=False, stdin=previous_res)
427 if res != 0:
428 self.Print("Err while running", cmd)
429 self.Print("Output", out)
430 raise Exception(err)
431 return out
432
433 result = LedResult(None, run_cmd).then(
434 # TODO(martiniss): maybe don't always assume the bucket?
435 'led', 'get-builder', 'luci.chromium.try:%s' % self.args.builder).then(
436 'led', 'edit', '-r', 'chromium_trybot_experimental',
Stephen Martinis9388ffc2019-10-19 00:15:08437 '-p', 'tests=["%s"]' % ninja_target).then(
Stephen Martinis24dbfb22019-10-21 21:30:03438 'led', 'edit-system', '--tag=purpose:user-debug-mb-try').then(
Stephen Martiniscd377012019-10-18 17:40:46439 'led', 'edit-cr-cl', issue_data['issue_url']).then(
440 'led', 'launch').result
441
442 swarming_data = json.loads(result)['swarming']
443 self.Print("Launched task at https://%s/task?id=%s" % (
444 swarming_data['host_name'], swarming_data['task_id']))
445
dpranke751516a2015-10-03 01:11:34446 def CmdRun(self):
447 vals = self.GetConfig()
448 if not vals:
449 return 1
Dirk Pranked181a1a2017-12-14 01:47:11450 if self.args.build:
Dirk Pranke5f22a822019-05-23 22:55:25451 self.Print('')
Dirk Prankef24e6b22018-03-27 20:12:30452 ret = self.Build(self.args.target)
dpranke751516a2015-10-03 01:11:34453 if ret:
454 return ret
Dirk Pranke5f22a822019-05-23 22:55:25455
456 self.Print('')
Dirk Pranked181a1a2017-12-14 01:47:11457 ret = self.RunGNIsolate(vals)
458 if ret:
459 return ret
dpranke751516a2015-10-03 01:11:34460
Dirk Pranke5f22a822019-05-23 22:55:25461 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33462 if self.args.swarmed:
Dirk Prankef24e6b22018-03-27 20:12:30463 return self._RunUnderSwarming(self.args.path, self.args.target)
Dirk Pranke8cb6aa782017-12-16 02:31:33464 else:
Dirk Prankef24e6b22018-03-27 20:12:30465 return self._RunLocallyIsolated(self.args.path, self.args.target)
466
467 def CmdZip(self):
Yun Liuc0f2f732019-09-18 17:06:31468 ret = self.CmdIsolate()
469 if ret:
470 return ret
Dirk Prankef24e6b22018-03-27 20:12:30471
Yun Liuc0f2f732019-09-18 17:06:31472 zip_dir = None
473 try:
474 zip_dir = self.TempDir()
475 remap_cmd = [
476 self.executable,
477 self.PathJoin(self.chromium_src_dir, 'tools', 'swarming_client',
478 'isolate.py'), 'remap', '--collapse_symlinks', '-s',
479 self.PathJoin(self.args.path, self.args.target + '.isolated'), '-o',
480 zip_dir
481 ]
482 self.Run(remap_cmd)
Dirk Prankef24e6b22018-03-27 20:12:30483
Yun Liuc0f2f732019-09-18 17:06:31484 zip_path = self.args.zip_path
485 with zipfile.ZipFile(
486 zip_path, 'w', zipfile.ZIP_DEFLATED, allowZip64=True) as fp:
487 for root, _, files in os.walk(zip_dir):
488 for filename in files:
489 path = self.PathJoin(root, filename)
490 fp.write(path, self.RelPath(path, zip_dir))
491 finally:
492 if zip_dir:
493 self.RemoveDirectory(zip_dir)
Dirk Pranke8cb6aa782017-12-16 02:31:33494
Robert Iannucci5a9d75f62018-03-02 05:28:20495 @staticmethod
496 def _AddBaseSoftware(cmd):
497 # HACK(iannucci): These packages SHOULD NOT BE HERE.
498 # Remove method once Swarming Pool Task Templates are implemented.
499 # crbug.com/812428
500
501 # Add in required base software. This should be kept in sync with the
John Budorick9d9175372019-04-01 19:04:24502 # `chromium_swarming` recipe module in build.git. All references to
503 # `swarming_module` below are purely due to this.
Robert Iannucci5a9d75f62018-03-02 05:28:20504 cipd_packages = [
505 ('infra/python/cpython/${platform}',
smut22dcd68e2019-06-25 23:33:27506 'version:2.7.15.chromium14'),
Robert Iannucci5a9d75f62018-03-02 05:28:20507 ('infra/tools/luci/logdog/butler/${platform}',
508 'git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c'),
509 ('infra/tools/luci/vpython-native/${platform}',
Andrii Shyshkalovb35c4cb2019-10-24 03:16:24510 'git_revision:e317c7d2c17d4c3460ee37524dfce4e1dee4306a'),
Robert Iannucci5a9d75f62018-03-02 05:28:20511 ('infra/tools/luci/vpython/${platform}',
Andrii Shyshkalovb35c4cb2019-10-24 03:16:24512 'git_revision:e317c7d2c17d4c3460ee37524dfce4e1dee4306a'),
Robert Iannucci5a9d75f62018-03-02 05:28:20513 ]
514 for pkg, vers in cipd_packages:
515 cmd.append('--cipd-package=.swarming_module:%s:%s' % (pkg, vers))
516
517 # Add packages to $PATH
518 cmd.extend([
519 '--env-prefix=PATH', '.swarming_module',
520 '--env-prefix=PATH', '.swarming_module/bin',
521 ])
522
523 # Add cache directives for vpython.
524 vpython_cache_path = '.swarming_module_cache/vpython'
525 cmd.extend([
526 '--named-cache=swarming_module_cache_vpython', vpython_cache_path,
527 '--env-prefix=VPYTHON_VIRTUALENV_ROOT', vpython_cache_path,
528 ])
529
Dirk Pranke8cb6aa782017-12-16 02:31:33530 def _RunUnderSwarming(self, build_dir, target):
Marc-Antoine Ruel559cc4732019-03-19 22:20:46531 isolate_server = 'isolateserver.appspot.com'
532 namespace = 'default-gzip'
533 swarming_server = 'chromium-swarm.appspot.com'
Dirk Pranke8cb6aa782017-12-16 02:31:33534 # TODO(dpranke): Look up the information for the target in
535 # the //testing/buildbot.json file, if possible, so that we
536 # can determine the isolate target, command line, and additional
537 # swarming parameters, if possible.
538 #
539 # TODO(dpranke): Also, add support for sharding and merging results.
540 dimensions = []
541 for k, v in self._DefaultDimensions() + self.args.dimensions:
542 dimensions += ['-d', k, v]
543
544 cmd = [
545 self.executable,
546 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
547 'archive',
Marc-Antoine Ruel559cc4732019-03-19 22:20:46548 '-s', self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
549 '-I', isolate_server,
550 '--namespace', namespace,
Dirk Pranke8cb6aa782017-12-16 02:31:33551 ]
Dirk Pranke5f22a822019-05-23 22:55:25552
553 # Talking to the isolateserver may fail because we're not logged in.
554 # We trap the command explicitly and rewrite the error output so that
555 # the error message is actually correct for a Chromium check out.
556 self.PrintCmd(cmd, env=None)
557 ret, out, err = self.Run(cmd, force_verbose=False)
Dirk Pranke8cb6aa782017-12-16 02:31:33558 if ret:
Dirk Pranke5f22a822019-05-23 22:55:25559 self.Print(' -> returned %d' % ret)
560 if out:
561 self.Print(out, end='')
562 if err:
563 # The swarming client will return an exit code of 2 (via
564 # argparse.ArgumentParser.error()) and print a message to indicate
565 # that auth failed, so we have to parse the message to check.
566 if (ret == 2 and 'Please login to' in err):
567 err = err.replace(' auth.py', ' tools/swarming_client/auth.py')
568 self.Print(err, end='', file=sys.stderr)
569
Dirk Pranke8cb6aa782017-12-16 02:31:33570 return ret
571
572 isolated_hash = out.splitlines()[0].split()[0]
573 cmd = [
574 self.executable,
575 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
576 'run',
577 '-s', isolated_hash,
Marc-Antoine Ruel559cc4732019-03-19 22:20:46578 '-I', isolate_server,
579 '--namespace', namespace,
580 '-S', swarming_server,
Stephen Martinis43ab3032019-09-11 20:07:41581 '--tags=purpose:user-debug-mb',
Dirk Pranke8cb6aa782017-12-16 02:31:33582 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20583 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33584 if self.args.extra_args:
585 cmd += ['--'] + self.args.extra_args
Dirk Pranke5f22a822019-05-23 22:55:25586 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33587 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
588 return ret
589
590 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50591 cmd = [
dpranke751516a2015-10-03 01:11:34592 self.executable,
593 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
594 'run',
595 '-s',
dpranke030d7a6d2016-03-26 17:23:50596 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33597 ]
dpranke030d7a6d2016-03-26 17:23:50598 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33599 cmd += ['--'] + self.args.extra_args
600 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34601 return ret
602
Dirk Pranke8cb6aa782017-12-16 02:31:33603 def _DefaultDimensions(self):
604 if not self.args.default_dimensions:
605 return []
606
607 # This code is naive and just picks reasonable defaults per platform.
608 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40609 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33610 elif self.platform.startswith('linux'):
Takuto Ikuta169663b2019-08-05 16:21:32611 os_dim = ('os', 'Ubuntu-16.04')
Dirk Pranke8cb6aa782017-12-16 02:31:33612 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40613 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33614 else:
615 raise MBErr('unrecognized platform string "%s"' % self.platform)
616
John Budorick9cf2d4c62019-11-11 23:56:12617 return [('pool', 'chromium.tests'),
Dirk Pranke8cb6aa782017-12-16 02:31:33618 ('cpu', 'x86-64'),
619 os_dim]
620
dpranke0cafc162016-03-19 00:41:10621 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35622 errs = []
623
624 # Read the file to make sure it parses.
625 self.ReadConfigFile()
626
dpranke3be00142016-03-17 22:46:04627 # Build a list of all of the configs referenced by builders.
dprankefe4602312015-04-08 16:20:35628 all_configs = {}
dprankefe4602312015-04-08 16:20:35629 for master in self.masters:
dpranke3be00142016-03-17 22:46:04630 for config in self.masters[master].values():
shenghuazhang804b21542016-10-11 02:06:49631 if isinstance(config, dict):
632 for c in config.values():
dprankeb9380a12016-07-21 21:44:09633 all_configs[c] = master
634 else:
635 all_configs[config] = master
dprankefe4602312015-04-08 16:20:35636
dpranke9dd5e252016-04-14 04:23:09637 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35638 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09639 if config.startswith('//'):
640 if not self.Exists(self.ToAbsPath(config)):
641 errs.append('Unknown args file "%s" referenced from "%s".' %
642 (config, loc))
643 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35644 errs.append('Unknown config "%s" referenced from "%s".' %
645 (config, loc))
646
647 # Check that every actual config is actually referenced.
648 for config in self.configs:
649 if not config in all_configs:
650 errs.append('Unused config "%s".' % config)
651
652 # Figure out the whole list of mixins, and check that every mixin
653 # listed by a config or another mixin actually exists.
654 referenced_mixins = set()
655 for config, mixins in self.configs.items():
656 for mixin in mixins:
657 if not mixin in self.mixins:
658 errs.append('Unknown mixin "%s" referenced by config "%s".' %
659 (mixin, config))
660 referenced_mixins.add(mixin)
661
662 for mixin in self.mixins:
663 for sub_mixin in self.mixins[mixin].get('mixins', []):
664 if not sub_mixin in self.mixins:
665 errs.append('Unknown mixin "%s" referenced by mixin "%s".' %
666 (sub_mixin, mixin))
667 referenced_mixins.add(sub_mixin)
668
669 # Check that every mixin defined is actually referenced somewhere.
670 for mixin in self.mixins:
671 if not mixin in referenced_mixins:
672 errs.append('Unreferenced mixin "%s".' % mixin)
673
dpranke255085e2016-03-16 05:23:59674 # If we're checking the Chromium config, check that the 'chromium' bots
675 # which build public artifacts do not include the chrome_with_codecs mixin.
676 if self.args.config_file == self.default_config:
677 if 'chromium' in self.masters:
678 for builder in self.masters['chromium']:
679 config = self.masters['chromium'][builder]
680 def RecurseMixins(current_mixin):
681 if current_mixin == 'chrome_with_codecs':
682 errs.append('Public artifact builder "%s" can not contain the '
683 '"chrome_with_codecs" mixin.' % builder)
684 return
685 if not 'mixins' in self.mixins[current_mixin]:
686 return
687 for mixin in self.mixins[current_mixin]['mixins']:
688 RecurseMixins(mixin)
dalecurtis56fd27e2016-03-09 23:06:41689
dpranke255085e2016-03-16 05:23:59690 for mixin in self.configs[config]:
691 RecurseMixins(mixin)
692 else:
693 errs.append('Missing "chromium" master. Please update this '
694 'proprietary codecs check with the name of the master '
695 'responsible for public build artifacts.')
dalecurtis56fd27e2016-03-09 23:06:41696
dprankefe4602312015-04-08 16:20:35697 if errs:
dpranke4323c80632015-08-10 22:53:54698 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17699 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35700
dpranke0cafc162016-03-19 00:41:10701 if print_ok:
702 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35703 return 0
704
705 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30706 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34707
dprankef37aebb92016-09-23 01:14:49708 vals = self.DefaultVals()
dpranke751516a2015-10-03 01:11:34709 if self.args.builder or self.args.master or self.args.config:
710 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11711 # Re-run gn gen in order to ensure the config is consistent with the
712 # build dir.
713 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34714 return vals
715
Dirk Pranked181a1a2017-12-14 01:47:11716 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
717 'toolchain.ninja')
718 if not self.Exists(toolchain_path):
719 self.Print('Must either specify a path to an existing GN build dir '
720 'or pass in a -m/-b pair or a -c flag to specify the '
721 'configuration')
722 return {}
dpranke751516a2015-10-03 01:11:34723
Dirk Pranked181a1a2017-12-14 01:47:11724 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34725 return vals
726
dprankef37aebb92016-09-23 01:14:49727 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58728 args_contents = ""
729 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
730 if self.Exists(gn_args_path):
731 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34732 gn_args = []
733 for l in args_contents.splitlines():
734 fields = l.split(' ')
735 name = fields[0]
736 val = ' '.join(fields[2:])
737 gn_args.append('%s=%s' % (name, val))
738
dprankef37aebb92016-09-23 01:14:49739 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34740
741 def Lookup(self):
Erik Chen238f4ac2019-04-12 19:02:50742 vals = self.ReadIOSBotConfig()
743 if not vals:
744 self.ReadConfigFile()
745 config = self.ConfigFromArgs()
746 if config.startswith('//'):
747 if not self.Exists(self.ToAbsPath(config)):
748 raise MBErr('args file "%s" not found' % config)
749 vals = self.DefaultVals()
750 vals['args_file'] = config
751 else:
752 if not config in self.configs:
753 raise MBErr('Config "%s" not found in %s' %
754 (config, self.args.config_file))
755 vals = self.FlattenConfig(config)
756 return vals
757
758 def ReadIOSBotConfig(self):
759 if not self.args.master or not self.args.builder:
760 return {}
761 path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
762 self.args.master, self.args.builder + '.json')
763 if not self.Exists(path):
764 return {}
765
766 contents = json.loads(self.ReadFile(path))
767 gn_args = ' '.join(contents.get('gn_args', []))
768
769 vals = self.DefaultVals()
770 vals['gn_args'] = gn_args
dprankef37aebb92016-09-23 01:14:49771 return vals
dprankee0f486f2015-11-19 23:42:00772
dprankefe4602312015-04-08 16:20:35773 def ReadConfigFile(self):
774 if not self.Exists(self.args.config_file):
775 raise MBErr('config file not found at %s' % self.args.config_file)
776
777 try:
778 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
779 except SyntaxError as e:
780 raise MBErr('Failed to parse config file "%s": %s' %
781 (self.args.config_file, e))
782
dprankefe4602312015-04-08 16:20:35783 self.configs = contents['configs']
784 self.masters = contents['masters']
785 self.mixins = contents['mixins']
dprankefe4602312015-04-08 16:20:35786
dprankecb4a2e242016-09-19 01:13:14787 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20788 if not self.args.isolate_map_files:
789 self.args.isolate_map_files = [self.default_isolate_map]
790
791 for f in self.args.isolate_map_files:
792 if not self.Exists(f):
793 raise MBErr('isolate map file not found at %s' % f)
794 isolate_maps = {}
795 for isolate_map in self.args.isolate_map_files:
796 try:
797 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
798 duplicates = set(isolate_map).intersection(isolate_maps)
799 if duplicates:
800 raise MBErr(
801 'Duplicate targets in isolate map files: %s.' %
802 ', '.join(duplicates))
803 isolate_maps.update(isolate_map)
804 except SyntaxError as e:
805 raise MBErr(
806 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
807 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14808
dprankefe4602312015-04-08 16:20:35809 def ConfigFromArgs(self):
810 if self.args.config:
811 if self.args.master or self.args.builder:
812 raise MBErr('Can not specific both -c/--config and -m/--master or '
813 '-b/--builder')
814
815 return self.args.config
816
817 if not self.args.master or not self.args.builder:
818 raise MBErr('Must specify either -c/--config or '
819 '(-m/--master and -b/--builder)')
820
821 if not self.args.master in self.masters:
822 raise MBErr('Master name "%s" not found in "%s"' %
823 (self.args.master, self.args.config_file))
824
825 if not self.args.builder in self.masters[self.args.master]:
826 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
827 (self.args.builder, self.args.master, self.args.config_file))
828
dprankeb9380a12016-07-21 21:44:09829 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49830 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09831 if self.args.phase is None:
832 raise MBErr('Must specify a build --phase for %s on %s' %
833 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49834 phase = str(self.args.phase)
835 if phase not in config:
836 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09837 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49838 return config[phase]
dprankeb9380a12016-07-21 21:44:09839
840 if self.args.phase is not None:
841 raise MBErr('Must not specify a build --phase for %s on %s' %
842 (self.args.builder, self.args.master))
843 return config
dprankefe4602312015-04-08 16:20:35844
845 def FlattenConfig(self, config):
846 mixins = self.configs[config]
dprankef37aebb92016-09-23 01:14:49847 vals = self.DefaultVals()
dprankefe4602312015-04-08 16:20:35848
849 visited = []
850 self.FlattenMixins(mixins, vals, visited)
851 return vals
852
dprankef37aebb92016-09-23 01:14:49853 def DefaultVals(self):
854 return {
855 'args_file': '',
856 'cros_passthrough': False,
857 'gn_args': '',
dprankef37aebb92016-09-23 01:14:49858 }
859
dprankefe4602312015-04-08 16:20:35860 def FlattenMixins(self, mixins, vals, visited):
861 for m in mixins:
862 if m not in self.mixins:
863 raise MBErr('Unknown mixin "%s"' % m)
dprankeee5b51f62015-04-09 00:03:22864
dprankefe4602312015-04-08 16:20:35865 visited.append(m)
866
867 mixin_vals = self.mixins[m]
dpranke73ed0d62016-04-25 19:18:34868
869 if 'cros_passthrough' in mixin_vals:
870 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
Dirk Pranke6b99f072017-04-05 00:58:30871 if 'args_file' in mixin_vals:
872 if vals['args_file']:
Yun Liuc0f2f732019-09-18 17:06:31873 raise MBErr('args_file specified multiple times in mixins '
874 'for %s on %s' % (self.args.builder, self.args.master))
Dirk Pranke6b99f072017-04-05 00:58:30875 vals['args_file'] = mixin_vals['args_file']
dprankefe4602312015-04-08 16:20:35876 if 'gn_args' in mixin_vals:
877 if vals['gn_args']:
878 vals['gn_args'] += ' ' + mixin_vals['gn_args']
879 else:
880 vals['gn_args'] = mixin_vals['gn_args']
dpranke73ed0d62016-04-25 19:18:34881
dprankefe4602312015-04-08 16:20:35882 if 'mixins' in mixin_vals:
883 self.FlattenMixins(mixin_vals['mixins'], vals, visited)
884 return vals
885
Takuto Ikuta9dffd7e2018-09-05 01:04:00886 def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
Dirk Prankef24e6b22018-03-27 20:12:30887 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50888
Takuto Ikuta9dffd7e2018-09-05 01:04:00889 if check:
890 cmd = self.GNCmd('gen', build_dir, '--check')
891 else:
892 cmd = self.GNCmd('gen', build_dir)
dprankeeca4a782016-04-14 01:42:38893 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:09894 if compute_inputs_for_analyze:
895 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:38896
897 # Since GN hasn't run yet, the build directory may not even exist.
898 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
899
900 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:54901 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:39902
dpranke751516a2015-10-03 01:11:34903 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:39904 # We need GN to generate the list of runtime dependencies for
905 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:14906 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:39907 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:00908 path = self.args.swarming_targets_file
909 if not self.Exists(path):
910 self.WriteFailureAndRaise('"%s" does not exist' % path,
911 output_path=None)
912 contents = self.ReadFile(path)
Erik Chen42df41d2018-08-21 17:13:31913 isolate_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:00914
dprankecb4a2e242016-09-19 01:13:14915 isolate_map = self.ReadIsolateMap()
Dirk Pranke7a7e9b62019-02-17 01:46:25916 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
917 isolate_map, build_dir)
918
Erik Chen42df41d2018-08-21 17:13:31919 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:00920 if err:
Dirk Pranke7a7e9b62019-02-17 01:46:25921 raise MBErr(err)
dpranke74559b52015-06-10 21:20:39922
dpranke751516a2015-10-03 01:11:34923 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:14924 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:39925 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
926
Debrian Figueroaae582232019-07-17 01:54:45927 ret, output, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:40928 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:11929 if self.args.json_output:
Debrian Figueroaae582232019-07-17 01:54:45930 # write errors to json.output
931 self.WriteJSON({'output': output}, self.args.json_output)
Dirk Pranke7a7e9b62019-02-17 01:46:25932 # If `gn gen` failed, we should exit early rather than trying to
933 # generate isolates. Run() will have already logged any error output.
934 self.Print('GN gen failed: %d' % ret)
935 return ret
dpranke74559b52015-06-10 21:20:39936
Erik Chen42df41d2018-08-21 17:13:31937 if getattr(self.args, 'swarming_targets_file', None):
Nico Weber0fd016762019-08-25 14:48:14938 ret = self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
Erik Chen42df41d2018-08-21 17:13:31939
Nico Weber0fd016762019-08-25 14:48:14940 return ret
Erik Chen42df41d2018-08-21 17:13:31941
942 def RunGNGenAllIsolates(self, vals):
943 """
944 This command generates all .isolate files.
945
946 This command assumes that "mb.py gen" has already been run, as it relies on
947 "gn ls" to fetch all gn targets. If uses that output, combined with the
948 isolate_map, to determine all isolates that can be generated for the current
949 gn configuration.
950 """
951 build_dir = self.args.path
952 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
953 force_verbose=False)
954 if ret:
Yun Liuc0f2f732019-09-18 17:06:31955 # If `gn ls` failed, we should exit early rather than trying to
956 # generate isolates.
957 self.Print('GN ls failed: %d' % ret)
958 return ret
Erik Chen42df41d2018-08-21 17:13:31959
960 # Create a reverse map from isolate label to isolate dict.
961 isolate_map = self.ReadIsolateMap()
962 isolate_dict_map = {}
963 for key, isolate_dict in isolate_map.iteritems():
964 isolate_dict_map[isolate_dict['label']] = isolate_dict
965 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
966
967 runtime_deps = []
968
969 isolate_targets = []
970 # For every GN target, look up the isolate dict.
971 for line in output.splitlines():
972 target = line.strip()
973 if target in isolate_dict_map:
974 if isolate_dict_map[target]['type'] == 'additional_compile_target':
975 # By definition, additional_compile_targets are not tests, so we
976 # shouldn't generate isolates for them.
977 continue
978
979 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
980 runtime_deps.append(target)
981
Dirk Pranke7a7e9b62019-02-17 01:46:25982 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
983 isolate_map, build_dir)
984
Erik Chen42df41d2018-08-21 17:13:31985 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
986 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
987 cmd = self.GNCmd('gen', build_dir)
988 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
989 self.Run(cmd)
990
991 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
992
Dirk Pranke7a7e9b62019-02-17 01:46:25993 def RemovePossiblyStaleRuntimeDepsFiles(self, vals, targets, isolate_map,
994 build_dir):
995 # TODO(crbug.com/932700): Because `gn gen --runtime-deps-list-file`
996 # puts the runtime_deps file in different locations based on the actual
997 # type of a target, we may end up with multiple possible runtime_deps
998 # files in a given build directory, where some of the entries might be
999 # stale (since we might be reusing an existing build directory).
1000 #
1001 # We need to be able to get the right one reliably; you might think
1002 # we can just pick the newest file, but because GN won't update timestamps
1003 # if the contents of the files change, an older runtime_deps
1004 # file might actually be the one we should use over a newer one (see
1005 # crbug.com/932387 for a more complete explanation and example).
1006 #
1007 # In order to avoid this, we need to delete any possible runtime_deps
1008 # files *prior* to running GN. As long as the files aren't actually
1009 # needed during the build, this hopefully will not cause unnecessary
1010 # build work, and so it should be safe.
1011 #
1012 # Ultimately, we should just make sure we get the runtime_deps files
1013 # in predictable locations so we don't have this issue at all, and
1014 # that's what crbug.com/932700 is for.
1015 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, targets, isolate_map)
1016 for rpaths in possible_rpaths.values():
1017 for rpath in rpaths:
1018 path = self.ToAbsPath(build_dir, rpath)
1019 if self.Exists(path):
1020 self.RemoveFile(path)
1021
Erik Chen42df41d2018-08-21 17:13:311022 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
1023 """
1024 Generates isolates for a list of ninja targets.
1025
1026 Ninja targets are transformed to GN targets via isolate_map.
1027
1028 This function assumes that a previous invocation of "mb.py gen" has
1029 generated runtime deps for all targets.
1030 """
Dirk Pranke7a7e9b62019-02-17 01:46:251031 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, ninja_targets,
1032 isolate_map)
1033
1034 for target, rpaths in possible_rpaths.items():
1035 # TODO(crbug.com/932700): We don't know where each .runtime_deps
1036 # file might be, but assuming we called
1037 # RemovePossiblyStaleRuntimeDepsFiles prior to calling `gn gen`,
1038 # there should only be one file.
1039 found_one = False
1040 path_to_use = None
1041 for r in rpaths:
1042 path = self.ToAbsPath(build_dir, r)
1043 if self.Exists(path):
1044 if found_one:
1045 raise MBErr('Found more than one of %s' % ', '.join(rpaths))
1046 path_to_use = path
1047 found_one = True
1048
1049 if not found_one:
1050 raise MBErr('Did not find any of %s' % ', '.join(rpaths))
1051
1052 command, extra_files = self.GetIsolateCommand(target, vals)
1053 runtime_deps = self.ReadFile(path_to_use).splitlines()
1054
1055 canonical_target = target.replace(':','_').replace('/','_')
Nico Weber0fd016762019-08-25 14:48:141056 ret = self.WriteIsolateFiles(build_dir, command, canonical_target,
1057 runtime_deps, vals, extra_files)
1058 if ret:
1059 return ret
1060 return 0
Dirk Pranke7a7e9b62019-02-17 01:46:251061
1062 def PossibleRuntimeDepsPaths(self, vals, ninja_targets, isolate_map):
1063 """Returns a map of targets to possible .runtime_deps paths.
1064
1065 Each ninja target maps on to a GN label, but depending on the type
1066 of the GN target, `gn gen --runtime-deps-list-file` will write
1067 the .runtime_deps files into different locations. Unfortunately, in
1068 some cases we don't actually know which of multiple locations will
1069 actually be used, so we return all plausible candidates.
1070
1071 The paths that are returned are relative to the build directory.
1072 """
1073
jbudoricke3c4f95e2016-04-28 23:17:381074 android = 'target_os="android"' in vals['gn_args']
Dirk Pranke26de05aec2019-04-03 19:18:381075 ios = 'target_os="ios"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:421076 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:301077 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Dirk Pranke7a7e9b62019-02-17 01:46:251078 possible_runtime_deps_rpaths = {}
Erik Chen42df41d2018-08-21 17:13:311079 for target in ninja_targets:
John Budorick39f14962019-04-11 23:03:201080 target_type = isolate_map[target]['type']
1081 label = isolate_map[target]['label']
1082 stamp_runtime_deps = 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')
Erik Chen42df41d2018-08-21 17:13:311083 # TODO(https://crbug.com/876065): 'official_tests' use
1084 # type='additional_compile_target' to isolate tests. This is not the
1085 # intended use for 'additional_compile_target'.
John Budorick39f14962019-04-11 23:03:201086 if (target_type == 'additional_compile_target' and
Erik Chen42df41d2018-08-21 17:13:311087 target != 'official_tests'):
1088 # By definition, additional_compile_targets are not tests, so we
1089 # shouldn't generate isolates for them.
Dirk Pranke7a7e9b62019-02-17 01:46:251090 raise MBErr('Cannot generate isolate for %s since it is an '
1091 'additional_compile_target.' % target)
John Budorick39f14962019-04-11 23:03:201092 elif fuchsia or ios or target_type == 'generated_script':
1093 # iOS and Fuchsia targets end up as groups.
1094 # generated_script targets are always actions.
1095 rpaths = [stamp_runtime_deps]
Erik Chen42df41d2018-08-21 17:13:311096 elif android:
jbudoricke3c4f95e2016-04-28 23:17:381097 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:021098 # will result in runtime_deps associated with the stamp file, while the
1099 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:441100 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:251101 rpaths = [
dprankecb4a2e242016-09-19 01:13:141102 target + '.runtime_deps',
John Budorick39f14962019-04-11 23:03:201103 stamp_runtime_deps]
1104 elif (target_type == 'script' or
1105 target_type == 'fuzzer' or
dprankecb4a2e242016-09-19 01:13:141106 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:111107 # For script targets, the build target is usually a group,
1108 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:491109 # for the label, which lives under the obj/ directory, but it may
1110 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:441111 label = isolate_map[target]['label']
John Budorick39f14962019-04-11 23:03:201112 rpaths = [stamp_runtime_deps]
Nico Weberd94b71a2018-02-22 22:00:301113 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:251114 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:491115 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251116 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:301117 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:251118 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:521119 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251120 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:021121
Dirk Pranke7a7e9b62019-02-17 01:46:251122 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:411123
Dirk Pranke7a7e9b62019-02-17 01:46:251124 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:341125
1126 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:301127 target = self.args.target
dprankecb4a2e242016-09-19 01:13:141128 isolate_map = self.ReadIsolateMap()
1129 err, labels = self.MapTargetsToLabels(isolate_map, [target])
1130 if err:
1131 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:251132
dprankecb4a2e242016-09-19 01:13:141133 label = labels[0]
dpranke751516a2015-10-03 01:11:341134
Dirk Prankef24e6b22018-03-27 20:12:301135 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:141136 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:341137
dprankeeca4a782016-04-14 01:42:381138 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:201139 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:341140 if ret:
dpranke030d7a6d2016-03-26 17:23:501141 if out:
1142 self.Print(out)
dpranke751516a2015-10-03 01:11:341143 return ret
1144
1145 runtime_deps = out.splitlines()
1146
Nico Weber0fd016762019-08-25 14:48:141147 ret = self.WriteIsolateFiles(build_dir, command, target, runtime_deps, vals,
1148 extra_files)
1149 if ret:
1150 return ret
dpranke751516a2015-10-03 01:11:341151
1152 ret, _, _ = self.Run([
1153 self.executable,
1154 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1155 'check',
1156 '-i',
1157 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1158 '-s',
1159 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1160 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301161
dprankefe4602312015-04-08 16:20:351162 return ret
1163
Nico Weber0fd016762019-08-25 14:48:141164 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps, vals,
dpranke751516a2015-10-03 01:11:341165 extra_files):
1166 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
Nico Weber0fd016762019-08-25 14:48:141167 files = sorted(set(runtime_deps + extra_files))
1168
1169 # Complain if any file is a directory that's inside the build directory,
1170 # since that makes incremental builds incorrect. See
1171 # https://crbug.com/912946
1172 is_android = 'target_os="android"' in vals['gn_args']
1173 is_cros = ('target_os="chromeos"' in vals['gn_args'] or
1174 vals.get('cros_passthrough', False))
1175 is_mac = self.platform == 'darwin'
Nico Weber0fd016762019-08-25 14:48:141176 is_msan = 'is_msan=true' in vals['gn_args']
1177
1178 err = ''
1179 for f in files:
1180 # Skip a few configs that need extra cleanup for now.
1181 # TODO(https://crbug.com/912946): Fix everything on all platforms and
1182 # enable check everywhere.
Nico Weberd9886b92019-09-10 17:52:171183 if is_android:
Nico Weber0fd016762019-08-25 14:48:141184 break
1185
1186 # Skip a few existing violations that need to be cleaned up. Each of
1187 # these will lead to incorrect incremental builds if their directory
1188 # contents change. Do not add to this list.
1189 # TODO(https://crbug.com/912946): Remove this if statement.
Nico Weber89895822019-08-27 18:59:031190 if ((is_msan and f == 'instrumented_libraries_prebuilt/') or
Clifford Chenge1244822019-08-27 17:26:551191 f == 'mr_extension/' or # https://crbug.com/997947
Nico Weber0fd016762019-08-25 14:48:141192 f == 'locales/' or
1193 f.startswith('nacl_test_data/') or
Nico Weber5eee4522019-09-05 23:28:051194 f.startswith('ppapi_nacl_tests_libs/') or
Nico Weberd9886b92019-09-10 17:52:171195 (is_cros and f in ( # https://crbug.com/1002509
1196 'chromevox_test_data/',
1197 'gen/ui/file_manager/file_manager/',
1198 'resources/chromeos/',
1199 'resources/chromeos/autoclick/',
1200 'resources/chromeos/chromevox/',
1201 'resources/chromeos/select_to_speak/',
1202 'test_data/chrome/browser/resources/chromeos/autoclick/',
1203 'test_data/chrome/browser/resources/chromeos/chromevox/',
1204 'test_data/chrome/browser/resources/chromeos/select_to_speak/',
1205 )) or
Nico Weber5eee4522019-09-05 23:28:051206 (is_mac and f in ( # https://crbug.com/1000667
Nico Weber5eee4522019-09-05 23:28:051207 'AlertNotificationService.xpc/',
Nico Weber5eee4522019-09-05 23:28:051208 'Chromium Framework.framework/',
1209 'Chromium Helper.app/',
1210 'Chromium.app/',
Nico Weber5eee4522019-09-05 23:28:051211 'Content Shell.app/',
Nico Weber5eee4522019-09-05 23:28:051212 'Google Chrome Framework.framework/',
1213 'Google Chrome Helper (GPU).app/',
Nico Weber5eee4522019-09-05 23:28:051214 'Google Chrome Helper (Plugin).app/',
Nico Weber5eee4522019-09-05 23:28:051215 'Google Chrome Helper (Renderer).app/',
Nico Weber5eee4522019-09-05 23:28:051216 'Google Chrome Helper.app/',
Nico Weber5eee4522019-09-05 23:28:051217 'Google Chrome.app/',
Nico Weber5eee4522019-09-05 23:28:051218 'blink_deprecated_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051219 'blink_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051220 'corb_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051221 'obj/tools/grit/brotli_mac_asan_workaround/',
1222 'power_saver_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051223 'ppapi_tests.plugin/',
Nico Weber5eee4522019-09-05 23:28:051224 'ui_unittests Framework.framework/',
1225 ))):
Nico Weber0fd016762019-08-25 14:48:141226 continue
1227
Nico Weber24e54f992019-08-26 14:33:321228 # This runs before the build, so we can't use isdir(f). But
Nico Weber0fd016762019-08-25 14:48:141229 # isolate.py luckily requires data directories to end with '/', so we
Nico Weber24e54f992019-08-26 14:33:321230 # can check for that.
Nico Weber57dbc9952019-09-04 13:33:581231 if not f.startswith('../../') and f.endswith('/'):
Nico Weber24e54f992019-08-26 14:33:321232 # Don't use self.PathJoin() -- all involved paths consistently use
1233 # forward slashes, so don't add one single backslash on Windows.
1234 err += '\n' + build_dir + '/' + f
Nico Weber0fd016762019-08-25 14:48:141235
1236 if err:
1237 self.Print('error: gn `data` items may not list generated directories; '
Nico Weber24e54f992019-08-26 14:33:321238 'list files in directory instead for:' + err)
Nico Weber0fd016762019-08-25 14:48:141239 return 1
1240
dpranke751516a2015-10-03 01:11:341241 self.WriteFile(isolate_path,
1242 pprint.pformat({
1243 'variables': {
1244 'command': command,
Nico Weber0fd016762019-08-25 14:48:141245 'files': files,
dpranke751516a2015-10-03 01:11:341246 }
1247 }) + '\n')
1248
1249 self.WriteJSON(
1250 {
1251 'args': [
1252 '--isolated',
1253 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1254 '--isolate',
1255 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1256 ],
1257 'dir': self.chromium_src_dir,
1258 'version': 1,
1259 },
1260 isolate_path + 'd.gen.json',
1261 )
1262
dprankecb4a2e242016-09-19 01:13:141263 def MapTargetsToLabels(self, isolate_map, targets):
1264 labels = []
1265 err = ''
1266
dprankecb4a2e242016-09-19 01:13:141267 for target in targets:
1268 if target == 'all':
1269 labels.append(target)
1270 elif target.startswith('//'):
1271 labels.append(target)
1272 else:
1273 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421274 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141275 err += ('test target "%s" type is unknown\n' % target)
1276 else:
thakis024d6f32017-05-16 23:21:421277 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141278 else:
1279 err += ('target "%s" not found in '
1280 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1281
1282 return err, labels
1283
dprankeeca4a782016-04-14 01:42:381284 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461285 if self.platform == 'linux2':
1286 subdir, exe = 'linux64', 'gn'
1287 elif self.platform == 'darwin':
1288 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251289 elif self.platform == 'aix6':
1290 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461291 else:
1292 subdir, exe = 'win', 'gn.exe'
1293
1294 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081295 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521296
dprankecb4a2e242016-09-19 01:13:141297
Garrett Beatyb6cee042019-04-22 18:42:091298 def GNArgs(self, vals, expand_imports=False):
dpranke73ed0d62016-04-25 19:18:341299 if vals['cros_passthrough']:
1300 if not 'GN_ARGS' in os.environ:
1301 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1302 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161303 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301304 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341305 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351306 if vals['gn_args']:
1307 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341308 else:
1309 gn_args = vals['gn_args']
1310
dpranked0c138b2016-04-13 18:28:471311 if self.args.goma_dir:
1312 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381313
agrieve41d21a72016-04-14 18:02:261314 android_version_code = self.args.android_version_code
1315 if android_version_code:
1316 gn_args += ' android_default_version_code="%s"' % android_version_code
1317
1318 android_version_name = self.args.android_version_name
1319 if android_version_name:
1320 gn_args += ' android_default_version_name="%s"' % android_version_name
1321
Garrett Beatyb6cee042019-04-22 18:42:091322 args_gn_lines = []
1323 parsed_gn_args = {}
dprankeeca4a782016-04-14 01:42:381324
Ben Pastene65ccf6132018-11-08 00:47:591325 # If we're using the Simple Chrome SDK, add a comment at the top that
1326 # points to the doc. This must happen after the gn_helpers.ToGNString()
1327 # call above since gn_helpers strips comments.
1328 if vals['cros_passthrough']:
Garrett Beatyb6cee042019-04-22 18:42:091329 args_gn_lines.extend([
Ben Pastene65ccf6132018-11-08 00:47:591330 '# These args are generated via the Simple Chrome SDK. See the link',
1331 '# below for more details:',
1332 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
Garrett Beatyb6cee042019-04-22 18:42:091333 ])
Ben Pastene65ccf6132018-11-08 00:47:591334
dpranke9dd5e252016-04-14 04:23:091335 args_file = vals.get('args_file', None)
1336 if args_file:
Garrett Beatyb6cee042019-04-22 18:42:091337 if expand_imports:
1338 content = self.ReadFile(self.ToAbsPath(args_file))
1339 parsed_gn_args = gn_helpers.FromGNArgs(content)
1340 else:
1341 args_gn_lines.append('import("%s")' % args_file)
1342
1343 # Canonicalize the arg string into a sorted, newline-separated list
1344 # of key-value pairs, and de-dup the keys if need be so that only
1345 # the last instance of each arg is listed.
1346 parsed_gn_args.update(gn_helpers.FromGNArgs(gn_args))
1347 args_gn_lines.append(gn_helpers.ToGNString(parsed_gn_args))
1348
1349 return '\n'.join(args_gn_lines)
dprankefe4602312015-04-08 16:20:351350
dprankecb4a2e242016-09-19 01:13:141351 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071352 isolate_map = self.ReadIsolateMap()
1353
Scott Graham3be4b4162017-09-12 00:41:411354 is_android = 'target_os="android"' in vals['gn_args']
1355 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Caleb Raittof983d102019-06-21 23:05:021356 is_cros = 'target_os="chromeos"' in vals['gn_args']
Nico Webera7bc1cb2019-06-15 17:42:391357 is_simplechrome = vals.get('cros_passthrough', False)
1358 is_mac = self.platform == 'darwin'
Nico Weberd94b71a2018-02-22 22:00:301359 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061360
kylechar39705682017-01-19 14:37:231361 # This should be true if tests with type='windowed_test_launcher' are
1362 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081363 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1364 # backends. Currently, tests are executed for the headless and X11 backends
1365 # and both can run under Xvfb.
1366 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1367 # backend.
Scott Graham3be4b4162017-09-12 00:41:411368 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251369
1370 asan = 'is_asan=true' in vals['gn_args']
1371 msan = 'is_msan=true' in vals['gn_args']
1372 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411373 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
Yun Liu5764e0dc2019-10-24 01:50:221374 clang_coverage = 'use_clang_coverage=true' in vals['gn_args']
Yun Liuc0f2f732019-09-18 17:06:311375 java_coverage = 'use_jacoco_coverage=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251376
dprankecb4a2e242016-09-19 01:13:141377 test_type = isolate_map[target]['type']
Brian Sheedy234580e52019-09-10 17:42:511378 use_python3 = isolate_map[target].get('use_python3', False)
dprankefe0d35e2016-02-05 02:43:591379
dprankecb4a2e242016-09-19 01:13:141380 executable = isolate_map[target].get('executable', target)
bsheedy9c16ed62019-04-10 20:32:111381 executable_suffix = isolate_map[target].get(
1382 'executable_suffix', '.exe' if is_win else '')
dprankefe0d35e2016-02-05 02:43:591383
Brian Sheedy234580e52019-09-10 17:42:511384 if use_python3:
1385 cmdline = [ 'vpython3' ]
1386 extra_files = [ '../../.vpython3' ]
1387 else:
1388 cmdline = [ 'vpython' ]
1389 extra_files = [ '../../.vpython' ]
1390 extra_files += [
Andrii Shyshkalovc158e0102018-01-10 05:52:001391 '../../testing/test_env.py',
1392 ]
dpranked8113582015-06-05 20:08:251393
dprankecb4a2e242016-09-19 01:13:141394 if test_type == 'nontest':
1395 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1396 output_path=None)
1397
John Budorick93e88ac82019-04-12 18:39:111398 if test_type == 'generated_script':
Ben Pastenecb0fb412019-06-11 02:31:541399 script = isolate_map[target]['script']
1400 if self.platform == 'win32':
1401 script += '.bat'
Brian Sheedy234580e52019-09-10 17:42:511402 cmdline += [
John Budorick93e88ac82019-04-12 18:39:111403 '../../testing/test_env.py',
Ben Pastenecb0fb412019-06-11 02:31:541404 script,
John Budorick93e88ac82019-04-12 18:39:111405 ]
1406 elif test_type == 'fuzzer':
Brian Sheedy234580e52019-09-10 17:42:511407 cmdline += [
Roberto Carrillo1460da852018-12-14 17:10:391408 '../../testing/test_env.py',
1409 '../../tools/code_coverage/run_fuzz_target.py',
1410 '--fuzzer', './' + target,
1411 '--output-dir', '${ISOLATED_OUTDIR}',
1412 '--timeout', '3600']
1413 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011414 if asan:
John Budorick31cdce62019-04-03 20:56:111415 cmdline += [os.path.join('bin', 'run_with_asan'), '--']
John Budorick8c4203042019-03-19 17:22:011416 cmdline += [
John Budorickfb97a852017-12-20 20:10:191417 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041418 '../../build/android/test_wrapper/logdog_wrapper.py',
1419 '--target', target,
hzl9ae14452017-04-04 23:38:021420 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481421 '--store-tombstones']
Yun Liu5764e0dc2019-10-24 01:50:221422 if clang_coverage or java_coverage:
Yun Liu7cef1072019-06-27 21:22:191423 cmdline += ['--coverage-dir', '${ISOLATED_OUTDIR}']
Scott Graham3be4b4162017-09-12 00:41:411424 elif is_fuchsia and test_type != 'script':
Brian Sheedy234580e52019-09-10 17:42:511425 cmdline += [
John Budorickfb97a852017-12-20 20:10:191426 '../../testing/test_env.py',
1427 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441428 '--test-launcher-bot-mode',
Sergey Ulanovd851243b2019-06-25 00:33:471429 '--system-log-file', '${ISOLATED_OUTDIR}/system_log'
John Budorickfb97a852017-12-20 20:10:191430 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321431 elif is_simplechrome and test_type != 'script':
Brian Sheedy234580e52019-09-10 17:42:511432 cmdline += [
Benjamin Pastene3bce864e2018-04-14 01:16:321433 '../../testing/test_env.py',
1434 os.path.join('bin', 'run_%s' % target),
1435 ]
kylechar39705682017-01-19 14:37:231436 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001437 extra_files.append('../../testing/xvfb.py')
Brian Sheedy234580e52019-09-10 17:42:511438 cmdline += [
Nico Webera7bc1cb2019-06-15 17:42:391439 '../../testing/xvfb.py',
1440 './' + str(executable) + executable_suffix,
1441 '--test-launcher-bot-mode',
1442 '--asan=%d' % asan,
1443 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1444 # supported.
1445 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021446 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1447 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
Nico Webera7bc1cb2019-06-15 17:42:391448 '--msan=%d' % msan,
1449 '--tsan=%d' % tsan,
1450 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471451 ]
1452 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
Brian Sheedy234580e52019-09-10 17:42:511453 cmdline += [
dprankea55584f12015-07-22 00:52:471454 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591455 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251456 '--test-launcher-bot-mode',
1457 '--asan=%d' % asan,
Caleb Raitto1fb2cc9e2019-06-14 01:04:231458 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1459 # supported.
Nico Webera7bc1cb2019-06-15 17:42:391460 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021461 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1462 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
dpranked8113582015-06-05 20:08:251463 '--msan=%d' % msan,
1464 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411465 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471466 ]
dpranke6abd8652015-08-28 03:21:111467 elif test_type == 'script':
Ben Pastene4534c39e2019-07-08 22:55:341468 # If we're testing a CrOS simplechrome build, assume we need to prepare a
1469 # DUT for testing. So prepend the command to run with the test wrapper.
Ben Pastene8ab6954d2018-05-04 04:08:241470 if is_simplechrome:
Ben Pastene908863c2019-07-25 16:20:031471 cmdline = [
1472 os.path.join('bin', 'cros_test_wrapper'),
1473 '--logs-dir=${ISOLATED_OUTDIR}',
1474 ]
Ben Pastene8ab6954d2018-05-04 04:08:241475 cmdline += [
dpranke6abd8652015-08-28 03:21:111476 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141477 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591478 ]
Dirk Prankef24e6b22018-03-27 20:12:301479 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471480 cmdline = [
1481 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591482 ]
dprankea55584f12015-07-22 00:52:471483 else:
1484 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1485 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251486
dprankecb4a2e242016-09-19 01:13:141487 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591488
dpranked8113582015-06-05 20:08:251489 return cmdline, extra_files
1490
dpranke74559b52015-06-10 21:20:391491 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331492 return self.PathJoin(self.chromium_src_dir,
1493 self.ToSrcRelPath(build_path),
1494 *comps)
dpranked8113582015-06-05 20:08:251495
dprankeee5b51f62015-04-09 00:03:221496 def ToSrcRelPath(self, path):
1497 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501498 if path.startswith('//'):
1499 return path[2:].replace('/', self.sep)
1500 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351501
Dirk Pranke0fd41bcd2015-06-19 00:05:501502 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141503 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501504 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001505 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501506 if ret:
1507 return ret
1508
Dirk Prankef24e6b22018-03-27 20:12:301509 build_path = self.args.path
1510 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141511 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301512 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141513 gn_output_path = output_path + '.gn'
1514
dpranke7837fc362015-11-19 03:54:161515 inp = self.ReadInputJSON(['files', 'test_targets',
1516 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321517 if self.args.verbose:
1518 self.Print()
1519 self.Print('analyze input:')
1520 self.PrintJSON(inp)
1521 self.Print()
1522
dpranke76734662015-04-16 02:17:501523
dpranke7c5f614d2015-07-22 23:43:391524 # This shouldn't normally happen, but could due to unusual race conditions,
1525 # like a try job that gets scheduled before a patch lands but runs after
1526 # the patch has landed.
1527 if not inp['files']:
1528 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161529 self.WriteJSON({
1530 'status': 'No dependency',
1531 'compile_targets': [],
1532 'test_targets': [],
1533 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391534 return 0
1535
dprankecb4a2e242016-09-19 01:13:141536 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161537 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561538
dprankecb4a2e242016-09-19 01:13:141539 isolate_map = self.ReadIsolateMap()
1540 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1541 isolate_map, inp['additional_compile_targets'])
1542 if err:
1543 raise MBErr(err)
1544
1545 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1546 isolate_map, inp['test_targets'])
1547 if err:
1548 raise MBErr(err)
1549 labels_to_targets = {}
1550 for i, label in enumerate(gn_inp['test_targets']):
1551 labels_to_targets[label] = inp['test_targets'][i]
1552
dprankef61de2f2015-05-14 04:09:561553 try:
dprankecb4a2e242016-09-19 01:13:141554 self.WriteJSON(gn_inp, gn_input_path)
1555 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
Debrian Figueroaae51d0d2019-07-22 18:04:111556 ret, output, _ = self.Run(cmd, force_verbose=True)
dprankecb4a2e242016-09-19 01:13:141557 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:111558 if self.args.json_output:
1559 # write errors to json.output
1560 self.WriteJSON({'output': output}, self.args.json_output)
dprankecb4a2e242016-09-19 01:13:141561 return ret
dpranke067d0142015-05-14 22:52:451562
dprankecb4a2e242016-09-19 01:13:141563 gn_outp_str = self.ReadFile(gn_output_path)
1564 try:
1565 gn_outp = json.loads(gn_outp_str)
1566 except Exception as e:
1567 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1568 % (repr(gn_outp_str), str(e)))
1569 raise
1570
1571 outp = {}
1572 if 'status' in gn_outp:
1573 outp['status'] = gn_outp['status']
1574 if 'error' in gn_outp:
1575 outp['error'] = gn_outp['error']
1576 if 'invalid_targets' in gn_outp:
1577 outp['invalid_targets'] = gn_outp['invalid_targets']
1578 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491579 all_input_compile_targets = sorted(
1580 set(inp['test_targets'] + inp['additional_compile_targets']))
1581
1582 # If we're building 'all', we can throw away the rest of the targets
1583 # since they're redundant.
dpranke385a3102016-09-20 22:04:081584 if 'all' in gn_outp['compile_targets']:
1585 outp['compile_targets'] = ['all']
1586 else:
Dirk Pranke45165072017-11-08 04:57:491587 outp['compile_targets'] = gn_outp['compile_targets']
1588
1589 # crbug.com/736215: When GN returns targets back, for targets in
1590 # the default toolchain, GN will have generated a phony ninja
1591 # target matching the label, and so we can safely (and easily)
1592 # transform any GN label into the matching ninja target. For
1593 # targets in other toolchains, though, GN doesn't generate the
1594 # phony targets, and we don't know how to turn the labels into
1595 # compile targets. In this case, we also conservatively give up
1596 # and build everything. Probably the right thing to do here is
1597 # to have GN return the compile targets directly.
1598 if any("(" in target for target in outp['compile_targets']):
1599 self.Print('WARNING: targets with non-default toolchains were '
1600 'found, building everything instead.')
1601 outp['compile_targets'] = all_input_compile_targets
1602 else:
dpranke385a3102016-09-20 22:04:081603 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491604 label.replace('//', '') for label in outp['compile_targets']]
1605
1606 # Windows has a maximum command line length of 8k; even Linux
1607 # maxes out at 128k; if analyze returns a *really long* list of
1608 # targets, we just give up and conservatively build everything instead.
1609 # Probably the right thing here is for ninja to support response
1610 # files as input on the command line
1611 # (see https://github.com/ninja-build/ninja/issues/1355).
1612 if len(' '.join(outp['compile_targets'])) > 7*1024:
1613 self.Print('WARNING: Too many compile targets were affected.')
1614 self.Print('WARNING: Building everything instead to avoid '
1615 'command-line length issues.')
1616 outp['compile_targets'] = all_input_compile_targets
1617
1618
dprankecb4a2e242016-09-19 01:13:141619 if 'test_targets' in gn_outp:
1620 outp['test_targets'] = [
1621 labels_to_targets[label] for label in gn_outp['test_targets']]
1622
1623 if self.args.verbose:
1624 self.Print()
1625 self.Print('analyze output:')
1626 self.PrintJSON(outp)
1627 self.Print()
1628
1629 self.WriteJSON(outp, output_path)
1630
dprankef61de2f2015-05-14 04:09:561631 finally:
dprankecb4a2e242016-09-19 01:13:141632 if self.Exists(gn_input_path):
1633 self.RemoveFile(gn_input_path)
1634 if self.Exists(gn_output_path):
1635 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351636
1637 return 0
1638
dpranked8113582015-06-05 20:08:251639 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301640 path = self.args.input_path
1641 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351642 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321643 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351644
1645 try:
1646 inp = json.loads(self.ReadFile(path))
1647 except Exception as e:
1648 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321649 (path, e), output_path)
dpranked8113582015-06-05 20:08:251650
1651 for k in required_keys:
1652 if not k in inp:
1653 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1654 output_path)
dprankefe4602312015-04-08 16:20:351655
1656 return inp
1657
dpranked5b2b9432015-06-23 16:55:301658 def WriteFailureAndRaise(self, msg, output_path):
1659 if output_path:
dprankee0547cd2015-09-15 01:27:401660 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351661 raise MBErr(msg)
1662
dprankee0547cd2015-09-15 01:27:401663 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321664 try:
dprankee0547cd2015-09-15 01:27:401665 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1666 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321667 except Exception as e:
1668 raise MBErr('Error %s writing to the output path "%s"' %
1669 (e, path))
dprankefe4602312015-04-08 16:20:351670
aneeshmde50f472016-04-01 01:13:101671 def CheckCompile(self, master, builder):
1672 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1673 url = urllib2.quote(url_template.format(master=master, builder=builder),
1674 safe=':/()?=')
1675 try:
1676 builds = json.loads(self.Fetch(url))
1677 except Exception as e:
1678 return str(e)
1679 successes = sorted(
1680 [int(x) for x in builds.keys() if "text" in builds[x] and
1681 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1682 reverse=True)
1683 if not successes:
1684 return "no successful builds"
1685 build = builds[str(successes[0])]
1686 step_names = set([step["name"] for step in build["steps"]])
1687 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1688 if compile_indicators & step_names:
1689 return "compiles"
1690 return "does not compile"
1691
dpranke3cec199c2015-09-22 23:29:021692 def PrintCmd(self, cmd, env):
1693 if self.platform == 'win32':
1694 env_prefix = 'set '
1695 env_quoter = QuoteForSet
1696 shell_quoter = QuoteForCmd
1697 else:
1698 env_prefix = ''
1699 env_quoter = pipes.quote
1700 shell_quoter = pipes.quote
1701
1702 def print_env(var):
1703 if env and var in env:
1704 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1705
dprankeec079262016-06-07 02:21:201706 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021707
dpranke8c2cfd32015-09-17 20:12:331708 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351709 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021710 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351711
dprankecda00332015-04-11 04:18:321712 def PrintJSON(self, obj):
1713 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1714
dpranke751516a2015-10-03 01:11:341715 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301716 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381717 if self.platform == 'win32':
1718 # On Windows use the batch script since there is no exe
1719 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1720 else:
1721 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341722 if self.args.jobs:
1723 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1724 ninja_cmd.append(target)
Dirk Pranke5f22a822019-05-23 22:55:251725 ret, _, _ = self.Run(ninja_cmd, buffer_output=False)
dpranke751516a2015-10-03 01:11:341726 return ret
1727
Stephen Martiniscd377012019-10-18 17:40:461728 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True,
1729 stdin=None):
dprankefe4602312015-04-08 16:20:351730 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401731 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021732 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351733 if self.args.dryrun:
1734 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401735
Stephen Martiniscd377012019-10-18 17:40:461736 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output,
1737 stdin=stdin)
dprankee0547cd2015-09-15 01:27:401738 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341739 if ret:
1740 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351741 if out:
Debrian Figueroaae582232019-07-17 01:54:451742 # This is the error seen on the logs
dprankeee5b51f62015-04-09 00:03:221743 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351744 if err:
dprankeee5b51f62015-04-09 00:03:221745 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351746 return ret, out, err
1747
Stephen Martiniscd377012019-10-18 17:40:461748 def Call(self, cmd, env=None, buffer_output=True, stdin=None):
dpranke751516a2015-10-03 01:11:341749 if buffer_output:
1750 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1751 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
Stephen Martiniscd377012019-10-18 17:40:461752 env=env, stdin=subprocess.PIPE)
1753 out, err = p.communicate(input=stdin)
dpranke751516a2015-10-03 01:11:341754 else:
1755 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1756 env=env)
1757 p.wait()
1758 out = err = ''
dprankefe4602312015-04-08 16:20:351759 return p.returncode, out, err
1760
1761 def ExpandUser(self, path):
1762 # This function largely exists so it can be overridden for testing.
1763 return os.path.expanduser(path)
1764
1765 def Exists(self, path):
1766 # This function largely exists so it can be overridden for testing.
1767 return os.path.exists(path)
1768
dpranke867bcf4a2016-03-14 22:28:321769 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501770 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321771 f = urllib2.urlopen(url)
1772 contents = f.read()
1773 f.close()
1774 return contents
1775
dprankec3441d12015-06-23 23:01:351776 def MaybeMakeDirectory(self, path):
1777 try:
1778 os.makedirs(path)
1779 except OSError, e:
1780 if e.errno != errno.EEXIST:
1781 raise
1782
dpranke8c2cfd32015-09-17 20:12:331783 def PathJoin(self, *comps):
1784 # This function largely exists so it can be overriden for testing.
1785 return os.path.join(*comps)
1786
dpranke030d7a6d2016-03-26 17:23:501787 def Print(self, *args, **kwargs):
1788 # This function largely exists so it can be overridden for testing.
1789 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101790 if kwargs.get('stream', sys.stdout) == sys.stdout:
1791 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501792
dprankefe4602312015-04-08 16:20:351793 def ReadFile(self, path):
1794 # This function largely exists so it can be overriden for testing.
1795 with open(path) as fp:
1796 return fp.read()
1797
dpranke030d7a6d2016-03-26 17:23:501798 def RelPath(self, path, start='.'):
1799 # This function largely exists so it can be overriden for testing.
1800 return os.path.relpath(path, start)
1801
dprankef61de2f2015-05-14 04:09:561802 def RemoveFile(self, path):
1803 # This function largely exists so it can be overriden for testing.
1804 os.remove(path)
1805
dprankec161aa92015-09-14 20:21:131806 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331807 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131808 # In other places in chromium, we often have to retry this command
1809 # because we're worried about other processes still holding on to
1810 # file handles, but when MB is invoked, it will be early enough in the
1811 # build that their should be no other processes to interfere. We
1812 # can change this if need be.
1813 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1814 else:
1815 shutil.rmtree(abs_path, ignore_errors=True)
1816
Dirk Prankef24e6b22018-03-27 20:12:301817 def TempDir(self):
1818 # This function largely exists so it can be overriden for testing.
1819 return tempfile.mkdtemp(prefix='mb_')
1820
dprankef61de2f2015-05-14 04:09:561821 def TempFile(self, mode='w'):
1822 # This function largely exists so it can be overriden for testing.
1823 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1824
dprankee0547cd2015-09-15 01:27:401825 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351826 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401827 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301828 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351829 with open(path, 'w') as fp:
1830 return fp.write(contents)
1831
dprankef61de2f2015-05-14 04:09:561832
Stephen Martiniscd377012019-10-18 17:40:461833class LedResult(object):
1834 """Holds the result of a led operation. Can be chained using |then|."""
1835
1836 def __init__(self, result, run_cmd):
1837 self._result = result
1838 self._run_cmd = run_cmd
1839
1840 @property
1841 def result(self):
1842 """The mutable result data of the previous led call as decoded JSON."""
1843 return self._result
1844
1845 def then(self, *cmd):
1846 """Invoke led, passing it the current `result` data as input.
1847
1848 Returns another LedResult object with the output of the command.
1849 """
1850 return self.__class__(
1851 self._run_cmd(self._result, cmd), self._run_cmd)
1852
1853
1854
dprankefe4602312015-04-08 16:20:351855class MBErr(Exception):
1856 pass
1857
1858
dpranke3cec199c2015-09-22 23:29:021859# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1860# details of this next section, which handles escaping command lines
1861# so that they can be copied and pasted into a cmd window.
1862UNSAFE_FOR_SET = set('^<>&|')
1863UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1864ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1865
1866
1867def QuoteForSet(arg):
1868 if any(a in UNSAFE_FOR_SET for a in arg):
1869 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1870 return arg
1871
1872
1873def QuoteForCmd(arg):
1874 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021875 if arg == '' or ' ' in arg or '"' in arg:
1876 quote_re = re.compile(r'(\\*)"')
1877 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1878
1879 # Then check to see if the arg contains any metacharacters other than
1880 # double quotes; if it does, quote everything (including the double
1881 # quotes) for safety.
1882 if any(a in UNSAFE_FOR_CMD for a in arg):
1883 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1884 return arg
1885
1886
dprankefe4602312015-04-08 16:20:351887if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591888 sys.exit(main(sys.argv[1:]))