blob: da847969ebefb880d5e9b377fba58a1cf72f343e [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',
Erik Chenb7068242019-11-27 21:52:04172 help='generates runtime dependencies for targets listed '
173 'in file as .isolate and .isolated.gen.json files. '
174 'Targets should be listed by name, separated by '
175 'newline.')
Debrian Figueroaae51d0d2019-07-22 18:04:11176 subp.add_argument('--json-output',
Debrian Figueroaae582232019-07-17 01:54:45177 help='Write errors to json.output')
Dirk Prankef24e6b22018-03-27 20:12:30178 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35179 help='path to generate build into')
180 subp.set_defaults(func=self.CmdGen)
181
Erik Chen42df41d2018-08-21 17:13:31182 subp = subps.add_parser('isolate-everything',
Stephen Martinis239c35a2019-07-22 19:34:40183 description='Generates a .isolate for all targets. '
184 'Requires that mb.py gen has already '
185 'been run.')
Erik Chen42df41d2018-08-21 17:13:31186 AddCommonOptions(subp)
187 subp.set_defaults(func=self.CmdIsolateEverything)
188 subp.add_argument('path',
189 help='path build was generated into')
190
dpranke751516a2015-10-03 01:11:34191 subp = subps.add_parser('isolate',
Stephen Martinis239c35a2019-07-22 19:34:40192 description='Generate the .isolate files for a '
193 'given binary.')
dpranke751516a2015-10-03 01:11:34194 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30195 subp.add_argument('--no-build', dest='build', default=True,
196 action='store_false',
197 help='Do not build, just isolate')
198 subp.add_argument('-j', '--jobs', type=int,
199 help='Number of jobs to pass to ninja')
200 subp.add_argument('path',
dpranke751516a2015-10-03 01:11:34201 help='path build was generated into')
Dirk Prankef24e6b22018-03-27 20:12:30202 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34203 help='ninja target to generate the isolate for')
204 subp.set_defaults(func=self.CmdIsolate)
205
dprankefe4602312015-04-08 16:20:35206 subp = subps.add_parser('lookup',
Stephen Martinis239c35a2019-07-22 19:34:40207 description='Look up the command for a given '
208 'config or builder.')
dprankefe4602312015-04-08 16:20:35209 AddCommonOptions(subp)
Garrett Beatyb6cee042019-04-22 18:42:09210 subp.add_argument('--quiet', default=False, action='store_true',
211 help='Print out just the arguments, '
212 'do not emulate the output of the gen subcommand.')
213 subp.add_argument('--recursive', default=False, action='store_true',
214 help='Lookup arguments from imported files, '
215 'implies --quiet')
dprankefe4602312015-04-08 16:20:35216 subp.set_defaults(func=self.CmdLookup)
217
Stephen Martiniscd377012019-10-18 17:40:46218 subp = subps.add_parser('try',
219 description='Try your change on a remote builder')
220 AddCommonOptions(subp)
221 subp.add_argument('target',
222 help='ninja target to build and run')
Stephen Martinis3016084b2019-11-20 20:26:22223 subp.add_argument('--force', default=False, action='store_true',
224 help='Force the job to run. Ignores local checkout state;'
225 ' by default, the tool doesn\'t trigger jobs if there are'
226 ' local changes which are not present on Gerrit.')
Stephen Martiniscd377012019-10-18 17:40:46227 subp.set_defaults(func=self.CmdTry)
228
dpranke030d7a6d2016-03-26 17:23:50229 subp = subps.add_parser(
Stephen Martinis239c35a2019-07-22 19:34:40230 'run', formatter_class=argparse.RawDescriptionHelpFormatter)
dpranke030d7a6d2016-03-26 17:23:50231 subp.description = (
232 'Build, isolate, and run the given binary with the command line\n'
233 'listed in the isolate. You may pass extra arguments after the\n'
234 'target; use "--" if the extra arguments need to include switches.\n'
235 '\n'
236 'Examples:\n'
237 '\n'
238 ' % tools/mb/mb.py run -m chromium.linux -b "Linux Builder" \\\n'
239 ' //out/Default content_browsertests\n'
240 '\n'
241 ' % tools/mb/mb.py run out/Default content_browsertests\n'
242 '\n'
243 ' % tools/mb/mb.py run out/Default content_browsertests -- \\\n'
244 ' --test-launcher-retry-limit=0'
245 '\n'
246 )
dpranke751516a2015-10-03 01:11:34247 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30248 subp.add_argument('-j', '--jobs', type=int,
dpranke751516a2015-10-03 01:11:34249 help='Number of jobs to pass to ninja')
250 subp.add_argument('--no-build', dest='build', default=True,
251 action='store_false',
252 help='Do not build, just isolate and run')
Dirk Prankef24e6b22018-03-27 20:12:30253 subp.add_argument('path',
dpranke030d7a6d2016-03-26 17:23:50254 help=('path to generate build into (or use).'
255 ' This can be either a regular path or a '
256 'GN-style source-relative path like '
257 '//out/Default.'))
Dirk Pranke8cb6aa782017-12-16 02:31:33258 subp.add_argument('-s', '--swarmed', action='store_true',
259 help='Run under swarming with the default dimensions')
260 subp.add_argument('-d', '--dimension', default=[], action='append', nargs=2,
261 dest='dimensions', metavar='FOO bar',
262 help='dimension to filter on')
263 subp.add_argument('--no-default-dimensions', action='store_false',
264 dest='default_dimensions', default=True,
265 help='Do not automatically add dimensions to the task')
Dirk Prankef24e6b22018-03-27 20:12:30266 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34267 help='ninja target to build and run')
dpranke030d7a6d2016-03-26 17:23:50268 subp.add_argument('extra_args', nargs='*',
269 help=('extra args to pass to the isolate to run. Use '
270 '"--" as the first arg if you need to pass '
271 'switches'))
dpranke751516a2015-10-03 01:11:34272 subp.set_defaults(func=self.CmdRun)
273
dprankefe4602312015-04-08 16:20:35274 subp = subps.add_parser('validate',
Stephen Martinis239c35a2019-07-22 19:34:40275 description='Validate the config file.')
dprankea5a77ca2015-07-16 23:24:17276 subp.add_argument('-f', '--config-file', metavar='PATH',
277 default=self.default_config,
kjellander902bcb62016-10-26 06:20:50278 help='path to config file (default is %(default)s)')
dprankefe4602312015-04-08 16:20:35279 subp.set_defaults(func=self.CmdValidate)
280
Dirk Prankef24e6b22018-03-27 20:12:30281 subp = subps.add_parser('zip',
Stephen Martinis239c35a2019-07-22 19:34:40282 description='Generate a .zip containing the files '
283 'needed for a given binary.')
Dirk Prankef24e6b22018-03-27 20:12:30284 AddCommonOptions(subp)
285 subp.add_argument('--no-build', dest='build', default=True,
286 action='store_false',
287 help='Do not build, just isolate')
288 subp.add_argument('-j', '--jobs', type=int,
289 help='Number of jobs to pass to ninja')
290 subp.add_argument('path',
291 help='path build was generated into')
292 subp.add_argument('target',
293 help='ninja target to generate the isolate for')
294 subp.add_argument('zip_path',
295 help='path to zip file to create')
296 subp.set_defaults(func=self.CmdZip)
297
dprankefe4602312015-04-08 16:20:35298 subp = subps.add_parser('help',
299 help='Get help on a subcommand.')
300 subp.add_argument(nargs='?', action='store', dest='subcommand',
301 help='The command to get help for.')
302 subp.set_defaults(func=self.CmdHelp)
303
304 self.args = parser.parse_args(argv)
305
dprankeb2be10a2016-02-22 17:11:00306 def DumpInputFiles(self):
307
dprankef7b7eb7a2016-03-28 22:42:59308 def DumpContentsOfFilePassedTo(arg_name, path):
dprankeb2be10a2016-02-22 17:11:00309 if path and self.Exists(path):
dprankef7b7eb7a2016-03-28 22:42:59310 self.Print("\n# To recreate the file passed to %s:" % arg_name)
dprankecb4a2e242016-09-19 01:13:14311 self.Print("%% cat > %s <<EOF" % path)
dprankeb2be10a2016-02-22 17:11:00312 contents = self.ReadFile(path)
dprankef7b7eb7a2016-03-28 22:42:59313 self.Print(contents)
314 self.Print("EOF\n%\n")
dprankeb2be10a2016-02-22 17:11:00315
dprankef7b7eb7a2016-03-28 22:42:59316 if getattr(self.args, 'input_path', None):
317 DumpContentsOfFilePassedTo(
Dirk Prankef24e6b22018-03-27 20:12:30318 'argv[0] (input_path)', self.args.input_path)
dprankef7b7eb7a2016-03-28 22:42:59319 if getattr(self.args, 'swarming_targets_file', None):
320 DumpContentsOfFilePassedTo(
321 '--swarming-targets-file', self.args.swarming_targets_file)
dprankeb2be10a2016-02-22 17:11:00322
dprankefe4602312015-04-08 16:20:35323 def CmdAnalyze(self):
dpranke751516a2015-10-03 01:11:34324 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11325 return self.RunGNAnalyze(vals)
dprankefe4602312015-04-08 16:20:35326
dprankef37aebb92016-09-23 01:14:49327 def CmdExport(self):
328 self.ReadConfigFile()
329 obj = {}
330 for master, builders in self.masters.items():
331 obj[master] = {}
332 for builder in builders:
333 config = self.masters[master][builder]
334 if not config:
335 continue
336
shenghuazhang804b21542016-10-11 02:06:49337 if isinstance(config, dict):
338 args = {k: self.FlattenConfig(v)['gn_args']
339 for k, v in config.items()}
dprankef37aebb92016-09-23 01:14:49340 elif config.startswith('//'):
341 args = config
342 else:
343 args = self.FlattenConfig(config)['gn_args']
344 if 'error' in args:
345 continue
346
347 obj[master][builder] = args
348
349 # Dump object and trim trailing whitespace.
350 s = '\n'.join(l.rstrip() for l in
351 json.dumps(obj, sort_keys=True, indent=2).splitlines())
352 self.Print(s)
353 return 0
354
dprankefe4602312015-04-08 16:20:35355 def CmdGen(self):
dpranke751516a2015-10-03 01:11:34356 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11357 return self.RunGNGen(vals)
dprankefe4602312015-04-08 16:20:35358
Erik Chen42df41d2018-08-21 17:13:31359 def CmdIsolateEverything(self):
360 vals = self.Lookup()
361 return self.RunGNGenAllIsolates(vals)
362
dprankefe4602312015-04-08 16:20:35363 def CmdHelp(self):
364 if self.args.subcommand:
365 self.ParseArgs([self.args.subcommand, '--help'])
366 else:
367 self.ParseArgs(['--help'])
368
dpranke751516a2015-10-03 01:11:34369 def CmdIsolate(self):
370 vals = self.GetConfig()
371 if not vals:
372 return 1
Dirk Prankef24e6b22018-03-27 20:12:30373 if self.args.build:
374 ret = self.Build(self.args.target)
375 if ret:
376 return ret
Dirk Pranked181a1a2017-12-14 01:47:11377 return self.RunGNIsolate(vals)
dpranke751516a2015-10-03 01:11:34378
379 def CmdLookup(self):
380 vals = self.Lookup()
Garrett Beatyb6cee042019-04-22 18:42:09381 gn_args = self.GNArgs(vals, expand_imports=self.args.recursive)
382 if self.args.quiet or self.args.recursive:
383 self.Print(gn_args, end='')
384 else:
385 cmd = self.GNCmd('gen', '_path_')
386 self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args)
387 env = None
dpranke751516a2015-10-03 01:11:34388
Garrett Beatyb6cee042019-04-22 18:42:09389 self.PrintCmd(cmd, env)
dpranke751516a2015-10-03 01:11:34390 return 0
391
Stephen Martiniscd377012019-10-18 17:40:46392 def CmdTry(self):
Stephen Martinis9388ffc2019-10-19 00:15:08393 ninja_target = self.args.target
394 if ninja_target.startswith('//'):
Stephen Martinis3016084b2019-11-20 20:26:22395 self.Print("Expected a ninja target like base_unittests, got %s" % (
396 ninja_target))
Stephen Martiniscd377012019-10-18 17:40:46397 return 1
398
Stephen Martinis3016084b2019-11-20 20:26:22399 _, out, _ = self.Run(['git', 'cl', 'diff', '--stat'], force_verbose=False)
400 if out:
401 self.Print("Your checkout appears to local changes which are not uploaded"
402 " to Gerrit. Changes must be committed and uploaded to Gerrit"
403 " to be tested using this tool.")
404 if not self.args.force:
405 return 1
406
Stephen Martiniscd377012019-10-18 17:40:46407 json_path = self.PathJoin(self.chromium_src_dir, 'out.json')
408 try:
409 ret, out, err = self.Run(
410 ['git', 'cl', 'issue', '--json=out.json'], force_verbose=False)
411 if ret != 0:
412 self.Print(
413 "Unable to fetch current issue. Output and error:\n%s\n%s" % (
414 out, err
415 ))
416 return ret
417 with open(json_path) as f:
418 issue_data = json.load(f)
419 finally:
420 if self.Exists(json_path):
421 os.unlink(json_path)
422
423 if not issue_data['issue']:
424 self.Print("Missing issue data. Upload your CL to Gerrit and try again.")
425 return 1
426
427 def run_cmd(previous_res, cmd):
428 res, out, err = self.Run(cmd, force_verbose=False, stdin=previous_res)
429 if res != 0:
430 self.Print("Err while running", cmd)
431 self.Print("Output", out)
432 raise Exception(err)
433 return out
434
435 result = LedResult(None, run_cmd).then(
436 # TODO(martiniss): maybe don't always assume the bucket?
437 'led', 'get-builder', 'luci.chromium.try:%s' % self.args.builder).then(
438 'led', 'edit', '-r', 'chromium_trybot_experimental',
Stephen Martinis9388ffc2019-10-19 00:15:08439 '-p', 'tests=["%s"]' % ninja_target).then(
Stephen Martinis24dbfb22019-10-21 21:30:03440 'led', 'edit-system', '--tag=purpose:user-debug-mb-try').then(
Stephen Martiniscd377012019-10-18 17:40:46441 'led', 'edit-cr-cl', issue_data['issue_url']).then(
442 'led', 'launch').result
443
444 swarming_data = json.loads(result)['swarming']
445 self.Print("Launched task at https://%s/task?id=%s" % (
446 swarming_data['host_name'], swarming_data['task_id']))
447
dpranke751516a2015-10-03 01:11:34448 def CmdRun(self):
449 vals = self.GetConfig()
450 if not vals:
451 return 1
Dirk Pranked181a1a2017-12-14 01:47:11452 if self.args.build:
Dirk Pranke5f22a822019-05-23 22:55:25453 self.Print('')
Dirk Prankef24e6b22018-03-27 20:12:30454 ret = self.Build(self.args.target)
dpranke751516a2015-10-03 01:11:34455 if ret:
456 return ret
Dirk Pranke5f22a822019-05-23 22:55:25457
458 self.Print('')
Dirk Pranked181a1a2017-12-14 01:47:11459 ret = self.RunGNIsolate(vals)
460 if ret:
461 return ret
dpranke751516a2015-10-03 01:11:34462
Dirk Pranke5f22a822019-05-23 22:55:25463 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33464 if self.args.swarmed:
Dirk Prankef24e6b22018-03-27 20:12:30465 return self._RunUnderSwarming(self.args.path, self.args.target)
Dirk Pranke8cb6aa782017-12-16 02:31:33466 else:
Dirk Prankef24e6b22018-03-27 20:12:30467 return self._RunLocallyIsolated(self.args.path, self.args.target)
468
469 def CmdZip(self):
Yun Liuc0f2f732019-09-18 17:06:31470 ret = self.CmdIsolate()
471 if ret:
472 return ret
Dirk Prankef24e6b22018-03-27 20:12:30473
Yun Liuc0f2f732019-09-18 17:06:31474 zip_dir = None
475 try:
476 zip_dir = self.TempDir()
477 remap_cmd = [
478 self.executable,
479 self.PathJoin(self.chromium_src_dir, 'tools', 'swarming_client',
480 'isolate.py'), 'remap', '--collapse_symlinks', '-s',
481 self.PathJoin(self.args.path, self.args.target + '.isolated'), '-o',
482 zip_dir
483 ]
484 self.Run(remap_cmd)
Dirk Prankef24e6b22018-03-27 20:12:30485
Yun Liuc0f2f732019-09-18 17:06:31486 zip_path = self.args.zip_path
487 with zipfile.ZipFile(
488 zip_path, 'w', zipfile.ZIP_DEFLATED, allowZip64=True) as fp:
489 for root, _, files in os.walk(zip_dir):
490 for filename in files:
491 path = self.PathJoin(root, filename)
492 fp.write(path, self.RelPath(path, zip_dir))
493 finally:
494 if zip_dir:
495 self.RemoveDirectory(zip_dir)
Dirk Pranke8cb6aa782017-12-16 02:31:33496
Robert Iannucci5a9d75f62018-03-02 05:28:20497 @staticmethod
498 def _AddBaseSoftware(cmd):
499 # HACK(iannucci): These packages SHOULD NOT BE HERE.
500 # Remove method once Swarming Pool Task Templates are implemented.
501 # crbug.com/812428
502
503 # Add in required base software. This should be kept in sync with the
John Budorick9d9175372019-04-01 19:04:24504 # `chromium_swarming` recipe module in build.git. All references to
505 # `swarming_module` below are purely due to this.
Robert Iannucci5a9d75f62018-03-02 05:28:20506 cipd_packages = [
507 ('infra/python/cpython/${platform}',
smut22dcd68e2019-06-25 23:33:27508 'version:2.7.15.chromium14'),
Robert Iannucci5a9d75f62018-03-02 05:28:20509 ('infra/tools/luci/logdog/butler/${platform}',
510 'git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c'),
511 ('infra/tools/luci/vpython-native/${platform}',
Andrii Shyshkalovb35c4cb2019-10-24 03:16:24512 'git_revision:e317c7d2c17d4c3460ee37524dfce4e1dee4306a'),
Robert Iannucci5a9d75f62018-03-02 05:28:20513 ('infra/tools/luci/vpython/${platform}',
Andrii Shyshkalovb35c4cb2019-10-24 03:16:24514 'git_revision:e317c7d2c17d4c3460ee37524dfce4e1dee4306a'),
Robert Iannucci5a9d75f62018-03-02 05:28:20515 ]
516 for pkg, vers in cipd_packages:
517 cmd.append('--cipd-package=.swarming_module:%s:%s' % (pkg, vers))
518
519 # Add packages to $PATH
520 cmd.extend([
521 '--env-prefix=PATH', '.swarming_module',
522 '--env-prefix=PATH', '.swarming_module/bin',
523 ])
524
525 # Add cache directives for vpython.
526 vpython_cache_path = '.swarming_module_cache/vpython'
527 cmd.extend([
528 '--named-cache=swarming_module_cache_vpython', vpython_cache_path,
529 '--env-prefix=VPYTHON_VIRTUALENV_ROOT', vpython_cache_path,
530 ])
531
Dirk Pranke8cb6aa782017-12-16 02:31:33532 def _RunUnderSwarming(self, build_dir, target):
Marc-Antoine Ruel559cc4732019-03-19 22:20:46533 isolate_server = 'isolateserver.appspot.com'
534 namespace = 'default-gzip'
535 swarming_server = 'chromium-swarm.appspot.com'
Dirk Pranke8cb6aa782017-12-16 02:31:33536 # TODO(dpranke): Look up the information for the target in
537 # the //testing/buildbot.json file, if possible, so that we
538 # can determine the isolate target, command line, and additional
539 # swarming parameters, if possible.
540 #
541 # TODO(dpranke): Also, add support for sharding and merging results.
542 dimensions = []
543 for k, v in self._DefaultDimensions() + self.args.dimensions:
544 dimensions += ['-d', k, v]
545
546 cmd = [
547 self.executable,
548 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
549 'archive',
Marc-Antoine Ruel559cc4732019-03-19 22:20:46550 '-s', self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
551 '-I', isolate_server,
552 '--namespace', namespace,
Dirk Pranke8cb6aa782017-12-16 02:31:33553 ]
Dirk Pranke5f22a822019-05-23 22:55:25554
555 # Talking to the isolateserver may fail because we're not logged in.
556 # We trap the command explicitly and rewrite the error output so that
557 # the error message is actually correct for a Chromium check out.
558 self.PrintCmd(cmd, env=None)
559 ret, out, err = self.Run(cmd, force_verbose=False)
Dirk Pranke8cb6aa782017-12-16 02:31:33560 if ret:
Dirk Pranke5f22a822019-05-23 22:55:25561 self.Print(' -> returned %d' % ret)
562 if out:
563 self.Print(out, end='')
564 if err:
565 # The swarming client will return an exit code of 2 (via
566 # argparse.ArgumentParser.error()) and print a message to indicate
567 # that auth failed, so we have to parse the message to check.
568 if (ret == 2 and 'Please login to' in err):
569 err = err.replace(' auth.py', ' tools/swarming_client/auth.py')
570 self.Print(err, end='', file=sys.stderr)
571
Dirk Pranke8cb6aa782017-12-16 02:31:33572 return ret
573
574 isolated_hash = out.splitlines()[0].split()[0]
575 cmd = [
576 self.executable,
577 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
578 'run',
579 '-s', isolated_hash,
Marc-Antoine Ruel559cc4732019-03-19 22:20:46580 '-I', isolate_server,
581 '--namespace', namespace,
582 '-S', swarming_server,
Stephen Martinis43ab3032019-09-11 20:07:41583 '--tags=purpose:user-debug-mb',
Dirk Pranke8cb6aa782017-12-16 02:31:33584 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20585 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33586 if self.args.extra_args:
587 cmd += ['--'] + self.args.extra_args
Dirk Pranke5f22a822019-05-23 22:55:25588 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33589 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
590 return ret
591
592 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50593 cmd = [
dpranke751516a2015-10-03 01:11:34594 self.executable,
595 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
596 'run',
597 '-s',
dpranke030d7a6d2016-03-26 17:23:50598 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33599 ]
dpranke030d7a6d2016-03-26 17:23:50600 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33601 cmd += ['--'] + self.args.extra_args
602 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34603 return ret
604
Dirk Pranke8cb6aa782017-12-16 02:31:33605 def _DefaultDimensions(self):
606 if not self.args.default_dimensions:
607 return []
608
609 # This code is naive and just picks reasonable defaults per platform.
610 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40611 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33612 elif self.platform.startswith('linux'):
Takuto Ikuta169663b2019-08-05 16:21:32613 os_dim = ('os', 'Ubuntu-16.04')
Dirk Pranke8cb6aa782017-12-16 02:31:33614 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40615 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33616 else:
617 raise MBErr('unrecognized platform string "%s"' % self.platform)
618
John Budorick9cf2d4c62019-11-11 23:56:12619 return [('pool', 'chromium.tests'),
Dirk Pranke8cb6aa782017-12-16 02:31:33620 ('cpu', 'x86-64'),
621 os_dim]
622
dpranke0cafc162016-03-19 00:41:10623 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35624 errs = []
625
626 # Read the file to make sure it parses.
627 self.ReadConfigFile()
628
dpranke3be00142016-03-17 22:46:04629 # Build a list of all of the configs referenced by builders.
dprankefe4602312015-04-08 16:20:35630 all_configs = {}
dprankefe4602312015-04-08 16:20:35631 for master in self.masters:
dpranke3be00142016-03-17 22:46:04632 for config in self.masters[master].values():
shenghuazhang804b21542016-10-11 02:06:49633 if isinstance(config, dict):
634 for c in config.values():
dprankeb9380a12016-07-21 21:44:09635 all_configs[c] = master
636 else:
637 all_configs[config] = master
dprankefe4602312015-04-08 16:20:35638
dpranke9dd5e252016-04-14 04:23:09639 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35640 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09641 if config.startswith('//'):
642 if not self.Exists(self.ToAbsPath(config)):
643 errs.append('Unknown args file "%s" referenced from "%s".' %
644 (config, loc))
645 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35646 errs.append('Unknown config "%s" referenced from "%s".' %
647 (config, loc))
648
649 # Check that every actual config is actually referenced.
650 for config in self.configs:
651 if not config in all_configs:
652 errs.append('Unused config "%s".' % config)
653
654 # Figure out the whole list of mixins, and check that every mixin
655 # listed by a config or another mixin actually exists.
656 referenced_mixins = set()
657 for config, mixins in self.configs.items():
658 for mixin in mixins:
659 if not mixin in self.mixins:
660 errs.append('Unknown mixin "%s" referenced by config "%s".' %
661 (mixin, config))
662 referenced_mixins.add(mixin)
663
664 for mixin in self.mixins:
665 for sub_mixin in self.mixins[mixin].get('mixins', []):
666 if not sub_mixin in self.mixins:
667 errs.append('Unknown mixin "%s" referenced by mixin "%s".' %
668 (sub_mixin, mixin))
669 referenced_mixins.add(sub_mixin)
670
671 # Check that every mixin defined is actually referenced somewhere.
672 for mixin in self.mixins:
673 if not mixin in referenced_mixins:
674 errs.append('Unreferenced mixin "%s".' % mixin)
675
dpranke255085e2016-03-16 05:23:59676 # If we're checking the Chromium config, check that the 'chromium' bots
677 # which build public artifacts do not include the chrome_with_codecs mixin.
678 if self.args.config_file == self.default_config:
679 if 'chromium' in self.masters:
680 for builder in self.masters['chromium']:
681 config = self.masters['chromium'][builder]
682 def RecurseMixins(current_mixin):
683 if current_mixin == 'chrome_with_codecs':
684 errs.append('Public artifact builder "%s" can not contain the '
685 '"chrome_with_codecs" mixin.' % builder)
686 return
687 if not 'mixins' in self.mixins[current_mixin]:
688 return
689 for mixin in self.mixins[current_mixin]['mixins']:
690 RecurseMixins(mixin)
dalecurtis56fd27e2016-03-09 23:06:41691
dpranke255085e2016-03-16 05:23:59692 for mixin in self.configs[config]:
693 RecurseMixins(mixin)
694 else:
695 errs.append('Missing "chromium" master. Please update this '
696 'proprietary codecs check with the name of the master '
697 'responsible for public build artifacts.')
dalecurtis56fd27e2016-03-09 23:06:41698
dprankefe4602312015-04-08 16:20:35699 if errs:
dpranke4323c80632015-08-10 22:53:54700 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17701 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35702
dpranke0cafc162016-03-19 00:41:10703 if print_ok:
704 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35705 return 0
706
707 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30708 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34709
dprankef37aebb92016-09-23 01:14:49710 vals = self.DefaultVals()
dpranke751516a2015-10-03 01:11:34711 if self.args.builder or self.args.master or self.args.config:
712 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11713 # Re-run gn gen in order to ensure the config is consistent with the
714 # build dir.
715 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34716 return vals
717
Dirk Pranked181a1a2017-12-14 01:47:11718 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
719 'toolchain.ninja')
720 if not self.Exists(toolchain_path):
721 self.Print('Must either specify a path to an existing GN build dir '
722 'or pass in a -m/-b pair or a -c flag to specify the '
723 'configuration')
724 return {}
dpranke751516a2015-10-03 01:11:34725
Dirk Pranked181a1a2017-12-14 01:47:11726 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34727 return vals
728
dprankef37aebb92016-09-23 01:14:49729 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58730 args_contents = ""
731 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
732 if self.Exists(gn_args_path):
733 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34734 gn_args = []
735 for l in args_contents.splitlines():
736 fields = l.split(' ')
737 name = fields[0]
738 val = ' '.join(fields[2:])
739 gn_args.append('%s=%s' % (name, val))
740
dprankef37aebb92016-09-23 01:14:49741 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34742
743 def Lookup(self):
Erik Chen238f4ac2019-04-12 19:02:50744 vals = self.ReadIOSBotConfig()
745 if not vals:
746 self.ReadConfigFile()
747 config = self.ConfigFromArgs()
748 if config.startswith('//'):
749 if not self.Exists(self.ToAbsPath(config)):
750 raise MBErr('args file "%s" not found' % config)
751 vals = self.DefaultVals()
752 vals['args_file'] = config
753 else:
754 if not config in self.configs:
755 raise MBErr('Config "%s" not found in %s' %
756 (config, self.args.config_file))
757 vals = self.FlattenConfig(config)
758 return vals
759
760 def ReadIOSBotConfig(self):
761 if not self.args.master or not self.args.builder:
762 return {}
763 path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
764 self.args.master, self.args.builder + '.json')
765 if not self.Exists(path):
766 return {}
767
768 contents = json.loads(self.ReadFile(path))
769 gn_args = ' '.join(contents.get('gn_args', []))
770
771 vals = self.DefaultVals()
772 vals['gn_args'] = gn_args
dprankef37aebb92016-09-23 01:14:49773 return vals
dprankee0f486f2015-11-19 23:42:00774
dprankefe4602312015-04-08 16:20:35775 def ReadConfigFile(self):
776 if not self.Exists(self.args.config_file):
777 raise MBErr('config file not found at %s' % self.args.config_file)
778
779 try:
780 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
781 except SyntaxError as e:
782 raise MBErr('Failed to parse config file "%s": %s' %
783 (self.args.config_file, e))
784
dprankefe4602312015-04-08 16:20:35785 self.configs = contents['configs']
786 self.masters = contents['masters']
787 self.mixins = contents['mixins']
dprankefe4602312015-04-08 16:20:35788
dprankecb4a2e242016-09-19 01:13:14789 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20790 if not self.args.isolate_map_files:
791 self.args.isolate_map_files = [self.default_isolate_map]
792
793 for f in self.args.isolate_map_files:
794 if not self.Exists(f):
795 raise MBErr('isolate map file not found at %s' % f)
796 isolate_maps = {}
797 for isolate_map in self.args.isolate_map_files:
798 try:
799 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
800 duplicates = set(isolate_map).intersection(isolate_maps)
801 if duplicates:
802 raise MBErr(
803 'Duplicate targets in isolate map files: %s.' %
804 ', '.join(duplicates))
805 isolate_maps.update(isolate_map)
806 except SyntaxError as e:
807 raise MBErr(
808 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
809 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14810
dprankefe4602312015-04-08 16:20:35811 def ConfigFromArgs(self):
812 if self.args.config:
813 if self.args.master or self.args.builder:
814 raise MBErr('Can not specific both -c/--config and -m/--master or '
815 '-b/--builder')
816
817 return self.args.config
818
819 if not self.args.master or not self.args.builder:
820 raise MBErr('Must specify either -c/--config or '
821 '(-m/--master and -b/--builder)')
822
823 if not self.args.master in self.masters:
824 raise MBErr('Master name "%s" not found in "%s"' %
825 (self.args.master, self.args.config_file))
826
827 if not self.args.builder in self.masters[self.args.master]:
828 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
829 (self.args.builder, self.args.master, self.args.config_file))
830
dprankeb9380a12016-07-21 21:44:09831 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49832 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09833 if self.args.phase is None:
834 raise MBErr('Must specify a build --phase for %s on %s' %
835 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49836 phase = str(self.args.phase)
837 if phase not in config:
838 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09839 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49840 return config[phase]
dprankeb9380a12016-07-21 21:44:09841
842 if self.args.phase is not None:
843 raise MBErr('Must not specify a build --phase for %s on %s' %
844 (self.args.builder, self.args.master))
845 return config
dprankefe4602312015-04-08 16:20:35846
847 def FlattenConfig(self, config):
848 mixins = self.configs[config]
dprankef37aebb92016-09-23 01:14:49849 vals = self.DefaultVals()
dprankefe4602312015-04-08 16:20:35850
851 visited = []
852 self.FlattenMixins(mixins, vals, visited)
853 return vals
854
dprankef37aebb92016-09-23 01:14:49855 def DefaultVals(self):
856 return {
857 'args_file': '',
858 'cros_passthrough': False,
859 'gn_args': '',
dprankef37aebb92016-09-23 01:14:49860 }
861
dprankefe4602312015-04-08 16:20:35862 def FlattenMixins(self, mixins, vals, visited):
863 for m in mixins:
864 if m not in self.mixins:
865 raise MBErr('Unknown mixin "%s"' % m)
dprankeee5b51f62015-04-09 00:03:22866
dprankefe4602312015-04-08 16:20:35867 visited.append(m)
868
869 mixin_vals = self.mixins[m]
dpranke73ed0d62016-04-25 19:18:34870
871 if 'cros_passthrough' in mixin_vals:
872 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
Dirk Pranke6b99f072017-04-05 00:58:30873 if 'args_file' in mixin_vals:
874 if vals['args_file']:
Yun Liuc0f2f732019-09-18 17:06:31875 raise MBErr('args_file specified multiple times in mixins '
876 'for %s on %s' % (self.args.builder, self.args.master))
Dirk Pranke6b99f072017-04-05 00:58:30877 vals['args_file'] = mixin_vals['args_file']
dprankefe4602312015-04-08 16:20:35878 if 'gn_args' in mixin_vals:
879 if vals['gn_args']:
880 vals['gn_args'] += ' ' + mixin_vals['gn_args']
881 else:
882 vals['gn_args'] = mixin_vals['gn_args']
dpranke73ed0d62016-04-25 19:18:34883
dprankefe4602312015-04-08 16:20:35884 if 'mixins' in mixin_vals:
885 self.FlattenMixins(mixin_vals['mixins'], vals, visited)
886 return vals
887
Takuto Ikuta9dffd7e2018-09-05 01:04:00888 def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
Dirk Prankef24e6b22018-03-27 20:12:30889 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50890
Takuto Ikuta9dffd7e2018-09-05 01:04:00891 if check:
892 cmd = self.GNCmd('gen', build_dir, '--check')
893 else:
894 cmd = self.GNCmd('gen', build_dir)
dprankeeca4a782016-04-14 01:42:38895 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:09896 if compute_inputs_for_analyze:
897 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:38898
899 # Since GN hasn't run yet, the build directory may not even exist.
900 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
901
902 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:54903 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:39904
dpranke751516a2015-10-03 01:11:34905 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:39906 # We need GN to generate the list of runtime dependencies for
907 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:14908 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:39909 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:00910 path = self.args.swarming_targets_file
911 if not self.Exists(path):
912 self.WriteFailureAndRaise('"%s" does not exist' % path,
913 output_path=None)
914 contents = self.ReadFile(path)
Erik Chen42df41d2018-08-21 17:13:31915 isolate_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:00916
dprankecb4a2e242016-09-19 01:13:14917 isolate_map = self.ReadIsolateMap()
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 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:00922 if err:
Dirk Pranke7a7e9b62019-02-17 01:46:25923 raise MBErr(err)
dpranke74559b52015-06-10 21:20:39924
dpranke751516a2015-10-03 01:11:34925 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:14926 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:39927 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
928
Debrian Figueroaae582232019-07-17 01:54:45929 ret, output, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:40930 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:11931 if self.args.json_output:
Debrian Figueroaae582232019-07-17 01:54:45932 # write errors to json.output
933 self.WriteJSON({'output': output}, self.args.json_output)
Dirk Pranke7a7e9b62019-02-17 01:46:25934 # If `gn gen` failed, we should exit early rather than trying to
935 # generate isolates. Run() will have already logged any error output.
936 self.Print('GN gen failed: %d' % ret)
937 return ret
dpranke74559b52015-06-10 21:20:39938
Erik Chen42df41d2018-08-21 17:13:31939 if getattr(self.args, 'swarming_targets_file', None):
Nico Weber0fd016762019-08-25 14:48:14940 ret = self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
Erik Chen42df41d2018-08-21 17:13:31941
Nico Weber0fd016762019-08-25 14:48:14942 return ret
Erik Chen42df41d2018-08-21 17:13:31943
944 def RunGNGenAllIsolates(self, vals):
945 """
946 This command generates all .isolate files.
947
948 This command assumes that "mb.py gen" has already been run, as it relies on
949 "gn ls" to fetch all gn targets. If uses that output, combined with the
950 isolate_map, to determine all isolates that can be generated for the current
951 gn configuration.
952 """
953 build_dir = self.args.path
954 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
955 force_verbose=False)
956 if ret:
Yun Liuc0f2f732019-09-18 17:06:31957 # If `gn ls` failed, we should exit early rather than trying to
958 # generate isolates.
959 self.Print('GN ls failed: %d' % ret)
960 return ret
Erik Chen42df41d2018-08-21 17:13:31961
962 # Create a reverse map from isolate label to isolate dict.
963 isolate_map = self.ReadIsolateMap()
964 isolate_dict_map = {}
965 for key, isolate_dict in isolate_map.iteritems():
966 isolate_dict_map[isolate_dict['label']] = isolate_dict
967 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
968
969 runtime_deps = []
970
971 isolate_targets = []
972 # For every GN target, look up the isolate dict.
973 for line in output.splitlines():
974 target = line.strip()
975 if target in isolate_dict_map:
976 if isolate_dict_map[target]['type'] == 'additional_compile_target':
977 # By definition, additional_compile_targets are not tests, so we
978 # shouldn't generate isolates for them.
979 continue
980
981 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
982 runtime_deps.append(target)
983
Dirk Pranke7a7e9b62019-02-17 01:46:25984 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
985 isolate_map, build_dir)
986
Erik Chen42df41d2018-08-21 17:13:31987 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
988 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
989 cmd = self.GNCmd('gen', build_dir)
990 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
991 self.Run(cmd)
992
993 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
994
Dirk Pranke7a7e9b62019-02-17 01:46:25995 def RemovePossiblyStaleRuntimeDepsFiles(self, vals, targets, isolate_map,
996 build_dir):
997 # TODO(crbug.com/932700): Because `gn gen --runtime-deps-list-file`
998 # puts the runtime_deps file in different locations based on the actual
999 # type of a target, we may end up with multiple possible runtime_deps
1000 # files in a given build directory, where some of the entries might be
1001 # stale (since we might be reusing an existing build directory).
1002 #
1003 # We need to be able to get the right one reliably; you might think
1004 # we can just pick the newest file, but because GN won't update timestamps
1005 # if the contents of the files change, an older runtime_deps
1006 # file might actually be the one we should use over a newer one (see
1007 # crbug.com/932387 for a more complete explanation and example).
1008 #
1009 # In order to avoid this, we need to delete any possible runtime_deps
1010 # files *prior* to running GN. As long as the files aren't actually
1011 # needed during the build, this hopefully will not cause unnecessary
1012 # build work, and so it should be safe.
1013 #
1014 # Ultimately, we should just make sure we get the runtime_deps files
1015 # in predictable locations so we don't have this issue at all, and
1016 # that's what crbug.com/932700 is for.
1017 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, targets, isolate_map)
1018 for rpaths in possible_rpaths.values():
1019 for rpath in rpaths:
1020 path = self.ToAbsPath(build_dir, rpath)
1021 if self.Exists(path):
1022 self.RemoveFile(path)
1023
Erik Chen42df41d2018-08-21 17:13:311024 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
1025 """
1026 Generates isolates for a list of ninja targets.
1027
1028 Ninja targets are transformed to GN targets via isolate_map.
1029
1030 This function assumes that a previous invocation of "mb.py gen" has
1031 generated runtime deps for all targets.
1032 """
Dirk Pranke7a7e9b62019-02-17 01:46:251033 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, ninja_targets,
1034 isolate_map)
1035
1036 for target, rpaths in possible_rpaths.items():
1037 # TODO(crbug.com/932700): We don't know where each .runtime_deps
1038 # file might be, but assuming we called
1039 # RemovePossiblyStaleRuntimeDepsFiles prior to calling `gn gen`,
1040 # there should only be one file.
1041 found_one = False
1042 path_to_use = None
1043 for r in rpaths:
1044 path = self.ToAbsPath(build_dir, r)
1045 if self.Exists(path):
1046 if found_one:
1047 raise MBErr('Found more than one of %s' % ', '.join(rpaths))
1048 path_to_use = path
1049 found_one = True
1050
1051 if not found_one:
1052 raise MBErr('Did not find any of %s' % ', '.join(rpaths))
1053
1054 command, extra_files = self.GetIsolateCommand(target, vals)
1055 runtime_deps = self.ReadFile(path_to_use).splitlines()
1056
1057 canonical_target = target.replace(':','_').replace('/','_')
Nico Weber0fd016762019-08-25 14:48:141058 ret = self.WriteIsolateFiles(build_dir, command, canonical_target,
1059 runtime_deps, vals, extra_files)
1060 if ret:
1061 return ret
1062 return 0
Dirk Pranke7a7e9b62019-02-17 01:46:251063
1064 def PossibleRuntimeDepsPaths(self, vals, ninja_targets, isolate_map):
1065 """Returns a map of targets to possible .runtime_deps paths.
1066
1067 Each ninja target maps on to a GN label, but depending on the type
1068 of the GN target, `gn gen --runtime-deps-list-file` will write
1069 the .runtime_deps files into different locations. Unfortunately, in
1070 some cases we don't actually know which of multiple locations will
1071 actually be used, so we return all plausible candidates.
1072
1073 The paths that are returned are relative to the build directory.
1074 """
1075
jbudoricke3c4f95e2016-04-28 23:17:381076 android = 'target_os="android"' in vals['gn_args']
Dirk Pranke26de05aec2019-04-03 19:18:381077 ios = 'target_os="ios"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:421078 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:301079 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Dirk Pranke7a7e9b62019-02-17 01:46:251080 possible_runtime_deps_rpaths = {}
Erik Chen42df41d2018-08-21 17:13:311081 for target in ninja_targets:
John Budorick39f14962019-04-11 23:03:201082 target_type = isolate_map[target]['type']
1083 label = isolate_map[target]['label']
1084 stamp_runtime_deps = 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')
Erik Chen42df41d2018-08-21 17:13:311085 # TODO(https://crbug.com/876065): 'official_tests' use
1086 # type='additional_compile_target' to isolate tests. This is not the
1087 # intended use for 'additional_compile_target'.
John Budorick39f14962019-04-11 23:03:201088 if (target_type == 'additional_compile_target' and
Erik Chen42df41d2018-08-21 17:13:311089 target != 'official_tests'):
1090 # By definition, additional_compile_targets are not tests, so we
1091 # shouldn't generate isolates for them.
Dirk Pranke7a7e9b62019-02-17 01:46:251092 raise MBErr('Cannot generate isolate for %s since it is an '
1093 'additional_compile_target.' % target)
John Budorick39f14962019-04-11 23:03:201094 elif fuchsia or ios or target_type == 'generated_script':
1095 # iOS and Fuchsia targets end up as groups.
1096 # generated_script targets are always actions.
1097 rpaths = [stamp_runtime_deps]
Erik Chen42df41d2018-08-21 17:13:311098 elif android:
jbudoricke3c4f95e2016-04-28 23:17:381099 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:021100 # will result in runtime_deps associated with the stamp file, while the
1101 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:441102 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:251103 rpaths = [
dprankecb4a2e242016-09-19 01:13:141104 target + '.runtime_deps',
John Budorick39f14962019-04-11 23:03:201105 stamp_runtime_deps]
1106 elif (target_type == 'script' or
1107 target_type == 'fuzzer' or
dprankecb4a2e242016-09-19 01:13:141108 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:111109 # For script targets, the build target is usually a group,
1110 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:491111 # for the label, which lives under the obj/ directory, but it may
1112 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:441113 label = isolate_map[target]['label']
John Budorick39f14962019-04-11 23:03:201114 rpaths = [stamp_runtime_deps]
Nico Weberd94b71a2018-02-22 22:00:301115 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:251116 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:491117 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251118 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:301119 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:251120 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:521121 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251122 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:021123
Dirk Pranke7a7e9b62019-02-17 01:46:251124 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:411125
Dirk Pranke7a7e9b62019-02-17 01:46:251126 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:341127
1128 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:301129 target = self.args.target
dprankecb4a2e242016-09-19 01:13:141130 isolate_map = self.ReadIsolateMap()
1131 err, labels = self.MapTargetsToLabels(isolate_map, [target])
1132 if err:
1133 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:251134
dprankecb4a2e242016-09-19 01:13:141135 label = labels[0]
dpranke751516a2015-10-03 01:11:341136
Dirk Prankef24e6b22018-03-27 20:12:301137 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:141138 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:341139
dprankeeca4a782016-04-14 01:42:381140 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:201141 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:341142 if ret:
dpranke030d7a6d2016-03-26 17:23:501143 if out:
1144 self.Print(out)
dpranke751516a2015-10-03 01:11:341145 return ret
1146
1147 runtime_deps = out.splitlines()
1148
Nico Weber0fd016762019-08-25 14:48:141149 ret = self.WriteIsolateFiles(build_dir, command, target, runtime_deps, vals,
1150 extra_files)
1151 if ret:
1152 return ret
dpranke751516a2015-10-03 01:11:341153
1154 ret, _, _ = self.Run([
1155 self.executable,
1156 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1157 'check',
1158 '-i',
1159 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1160 '-s',
1161 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1162 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301163
dprankefe4602312015-04-08 16:20:351164 return ret
1165
Nico Weber0fd016762019-08-25 14:48:141166 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps, vals,
dpranke751516a2015-10-03 01:11:341167 extra_files):
1168 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
Nico Weber0fd016762019-08-25 14:48:141169 files = sorted(set(runtime_deps + extra_files))
1170
1171 # Complain if any file is a directory that's inside the build directory,
1172 # since that makes incremental builds incorrect. See
1173 # https://crbug.com/912946
1174 is_android = 'target_os="android"' in vals['gn_args']
1175 is_cros = ('target_os="chromeos"' in vals['gn_args'] or
1176 vals.get('cros_passthrough', False))
1177 is_mac = self.platform == 'darwin'
Nico Weber0fd016762019-08-25 14:48:141178 is_msan = 'is_msan=true' in vals['gn_args']
1179
1180 err = ''
1181 for f in files:
1182 # Skip a few configs that need extra cleanup for now.
1183 # TODO(https://crbug.com/912946): Fix everything on all platforms and
1184 # enable check everywhere.
Nico Weberd9886b92019-09-10 17:52:171185 if is_android:
Nico Weber0fd016762019-08-25 14:48:141186 break
1187
1188 # Skip a few existing violations that need to be cleaned up. Each of
1189 # these will lead to incorrect incremental builds if their directory
1190 # contents change. Do not add to this list.
1191 # TODO(https://crbug.com/912946): Remove this if statement.
Nico Weber89895822019-08-27 18:59:031192 if ((is_msan and f == 'instrumented_libraries_prebuilt/') or
Clifford Chenge1244822019-08-27 17:26:551193 f == 'mr_extension/' or # https://crbug.com/997947
Nico Weber0fd016762019-08-25 14:48:141194 f == 'locales/' or
1195 f.startswith('nacl_test_data/') or
Nico Weber5eee4522019-09-05 23:28:051196 f.startswith('ppapi_nacl_tests_libs/') or
Nico Weberd9886b92019-09-10 17:52:171197 (is_cros and f in ( # https://crbug.com/1002509
1198 'chromevox_test_data/',
1199 'gen/ui/file_manager/file_manager/',
1200 'resources/chromeos/',
Anastasia Helfinstein4bb719552019-11-21 19:02:511201 'resources/chromeos/accessibility/autoclick/',
1202 'resources/chromeos/accessibility/chromevox/',
1203 'resources/chromeos/accessibility/select_to_speak/',
1204 'test_data/chrome/browser/resources/chromeos/accessibility/'
1205 'autoclick/',
1206 'test_data/chrome/browser/resources/chromeos/accessibility/'
1207 'chromevox/',
1208 'test_data/chrome/browser/resources/chromeos/accessibility/'
1209 'select_to_speak/',
Nico Weberd9886b92019-09-10 17:52:171210 )) or
Nico Weber5eee4522019-09-05 23:28:051211 (is_mac and f in ( # https://crbug.com/1000667
Nico Weber5eee4522019-09-05 23:28:051212 'AlertNotificationService.xpc/',
Nico Weber5eee4522019-09-05 23:28:051213 'Chromium Framework.framework/',
1214 'Chromium Helper.app/',
1215 'Chromium.app/',
Nico Weber5eee4522019-09-05 23:28:051216 'Content Shell.app/',
Nico Weber5eee4522019-09-05 23:28:051217 'Google Chrome Framework.framework/',
1218 'Google Chrome Helper (GPU).app/',
Nico Weber5eee4522019-09-05 23:28:051219 'Google Chrome Helper (Plugin).app/',
Nico Weber5eee4522019-09-05 23:28:051220 'Google Chrome Helper (Renderer).app/',
Nico Weber5eee4522019-09-05 23:28:051221 'Google Chrome Helper.app/',
Nico Weber5eee4522019-09-05 23:28:051222 'Google Chrome.app/',
Nico Weber5eee4522019-09-05 23:28:051223 'blink_deprecated_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051224 'blink_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051225 'corb_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051226 'obj/tools/grit/brotli_mac_asan_workaround/',
1227 'power_saver_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051228 'ppapi_tests.plugin/',
Nico Weber5eee4522019-09-05 23:28:051229 'ui_unittests Framework.framework/',
1230 ))):
Nico Weber0fd016762019-08-25 14:48:141231 continue
1232
Nico Weber24e54f992019-08-26 14:33:321233 # This runs before the build, so we can't use isdir(f). But
Nico Weber0fd016762019-08-25 14:48:141234 # isolate.py luckily requires data directories to end with '/', so we
Nico Weber24e54f992019-08-26 14:33:321235 # can check for that.
Nico Weber57dbc9952019-09-04 13:33:581236 if not f.startswith('../../') and f.endswith('/'):
Nico Weber24e54f992019-08-26 14:33:321237 # Don't use self.PathJoin() -- all involved paths consistently use
1238 # forward slashes, so don't add one single backslash on Windows.
1239 err += '\n' + build_dir + '/' + f
Nico Weber0fd016762019-08-25 14:48:141240
1241 if err:
1242 self.Print('error: gn `data` items may not list generated directories; '
Nico Weber24e54f992019-08-26 14:33:321243 'list files in directory instead for:' + err)
Nico Weber0fd016762019-08-25 14:48:141244 return 1
1245
dpranke751516a2015-10-03 01:11:341246 self.WriteFile(isolate_path,
1247 pprint.pformat({
1248 'variables': {
1249 'command': command,
Nico Weber0fd016762019-08-25 14:48:141250 'files': files,
dpranke751516a2015-10-03 01:11:341251 }
1252 }) + '\n')
1253
1254 self.WriteJSON(
1255 {
1256 'args': [
1257 '--isolated',
1258 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1259 '--isolate',
1260 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1261 ],
1262 'dir': self.chromium_src_dir,
1263 'version': 1,
1264 },
1265 isolate_path + 'd.gen.json',
1266 )
1267
dprankecb4a2e242016-09-19 01:13:141268 def MapTargetsToLabels(self, isolate_map, targets):
1269 labels = []
1270 err = ''
1271
dprankecb4a2e242016-09-19 01:13:141272 for target in targets:
1273 if target == 'all':
1274 labels.append(target)
1275 elif target.startswith('//'):
1276 labels.append(target)
1277 else:
1278 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421279 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141280 err += ('test target "%s" type is unknown\n' % target)
1281 else:
thakis024d6f32017-05-16 23:21:421282 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141283 else:
1284 err += ('target "%s" not found in '
1285 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1286
1287 return err, labels
1288
dprankeeca4a782016-04-14 01:42:381289 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461290 if self.platform == 'linux2':
1291 subdir, exe = 'linux64', 'gn'
1292 elif self.platform == 'darwin':
1293 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251294 elif self.platform == 'aix6':
1295 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461296 else:
1297 subdir, exe = 'win', 'gn.exe'
1298
1299 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081300 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521301
dprankecb4a2e242016-09-19 01:13:141302
Garrett Beatyb6cee042019-04-22 18:42:091303 def GNArgs(self, vals, expand_imports=False):
dpranke73ed0d62016-04-25 19:18:341304 if vals['cros_passthrough']:
1305 if not 'GN_ARGS' in os.environ:
1306 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1307 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161308 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301309 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341310 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351311 if vals['gn_args']:
1312 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341313 else:
1314 gn_args = vals['gn_args']
1315
dpranked0c138b2016-04-13 18:28:471316 if self.args.goma_dir:
1317 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381318
agrieve41d21a72016-04-14 18:02:261319 android_version_code = self.args.android_version_code
1320 if android_version_code:
1321 gn_args += ' android_default_version_code="%s"' % android_version_code
1322
1323 android_version_name = self.args.android_version_name
1324 if android_version_name:
1325 gn_args += ' android_default_version_name="%s"' % android_version_name
1326
Garrett Beatyb6cee042019-04-22 18:42:091327 args_gn_lines = []
1328 parsed_gn_args = {}
dprankeeca4a782016-04-14 01:42:381329
Ben Pastene65ccf6132018-11-08 00:47:591330 # If we're using the Simple Chrome SDK, add a comment at the top that
1331 # points to the doc. This must happen after the gn_helpers.ToGNString()
1332 # call above since gn_helpers strips comments.
1333 if vals['cros_passthrough']:
Garrett Beatyb6cee042019-04-22 18:42:091334 args_gn_lines.extend([
Ben Pastene65ccf6132018-11-08 00:47:591335 '# These args are generated via the Simple Chrome SDK. See the link',
1336 '# below for more details:',
1337 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
Garrett Beatyb6cee042019-04-22 18:42:091338 ])
Ben Pastene65ccf6132018-11-08 00:47:591339
dpranke9dd5e252016-04-14 04:23:091340 args_file = vals.get('args_file', None)
1341 if args_file:
Garrett Beatyb6cee042019-04-22 18:42:091342 if expand_imports:
1343 content = self.ReadFile(self.ToAbsPath(args_file))
1344 parsed_gn_args = gn_helpers.FromGNArgs(content)
1345 else:
1346 args_gn_lines.append('import("%s")' % args_file)
1347
1348 # Canonicalize the arg string into a sorted, newline-separated list
1349 # of key-value pairs, and de-dup the keys if need be so that only
1350 # the last instance of each arg is listed.
1351 parsed_gn_args.update(gn_helpers.FromGNArgs(gn_args))
1352 args_gn_lines.append(gn_helpers.ToGNString(parsed_gn_args))
1353
1354 return '\n'.join(args_gn_lines)
dprankefe4602312015-04-08 16:20:351355
dprankecb4a2e242016-09-19 01:13:141356 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071357 isolate_map = self.ReadIsolateMap()
1358
Scott Graham3be4b4162017-09-12 00:41:411359 is_android = 'target_os="android"' in vals['gn_args']
1360 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Caleb Raittof983d102019-06-21 23:05:021361 is_cros = 'target_os="chromeos"' in vals['gn_args']
Nico Webera7bc1cb2019-06-15 17:42:391362 is_simplechrome = vals.get('cros_passthrough', False)
1363 is_mac = self.platform == 'darwin'
Nico Weberd94b71a2018-02-22 22:00:301364 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061365
kylechar39705682017-01-19 14:37:231366 # This should be true if tests with type='windowed_test_launcher' are
1367 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081368 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1369 # backends. Currently, tests are executed for the headless and X11 backends
1370 # and both can run under Xvfb.
1371 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1372 # backend.
Scott Graham3be4b4162017-09-12 00:41:411373 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251374
1375 asan = 'is_asan=true' in vals['gn_args']
1376 msan = 'is_msan=true' in vals['gn_args']
1377 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411378 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
Yun Liu5764e0dc2019-10-24 01:50:221379 clang_coverage = 'use_clang_coverage=true' in vals['gn_args']
Yun Liuc0f2f732019-09-18 17:06:311380 java_coverage = 'use_jacoco_coverage=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251381
dprankecb4a2e242016-09-19 01:13:141382 test_type = isolate_map[target]['type']
Brian Sheedy234580e52019-09-10 17:42:511383 use_python3 = isolate_map[target].get('use_python3', False)
dprankefe0d35e2016-02-05 02:43:591384
dprankecb4a2e242016-09-19 01:13:141385 executable = isolate_map[target].get('executable', target)
bsheedy9c16ed62019-04-10 20:32:111386 executable_suffix = isolate_map[target].get(
1387 'executable_suffix', '.exe' if is_win else '')
dprankefe0d35e2016-02-05 02:43:591388
Brian Sheedy234580e52019-09-10 17:42:511389 if use_python3:
1390 cmdline = [ 'vpython3' ]
1391 extra_files = [ '../../.vpython3' ]
1392 else:
1393 cmdline = [ 'vpython' ]
1394 extra_files = [ '../../.vpython' ]
1395 extra_files += [
Andrii Shyshkalovc158e0102018-01-10 05:52:001396 '../../testing/test_env.py',
1397 ]
dpranked8113582015-06-05 20:08:251398
dprankecb4a2e242016-09-19 01:13:141399 if test_type == 'nontest':
1400 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1401 output_path=None)
1402
John Budorick93e88ac82019-04-12 18:39:111403 if test_type == 'generated_script':
Ben Pastenecb0fb412019-06-11 02:31:541404 script = isolate_map[target]['script']
1405 if self.platform == 'win32':
1406 script += '.bat'
Brian Sheedy234580e52019-09-10 17:42:511407 cmdline += [
John Budorick93e88ac82019-04-12 18:39:111408 '../../testing/test_env.py',
Ben Pastenecb0fb412019-06-11 02:31:541409 script,
John Budorick93e88ac82019-04-12 18:39:111410 ]
1411 elif test_type == 'fuzzer':
Brian Sheedy234580e52019-09-10 17:42:511412 cmdline += [
Roberto Carrillo1460da852018-12-14 17:10:391413 '../../testing/test_env.py',
1414 '../../tools/code_coverage/run_fuzz_target.py',
1415 '--fuzzer', './' + target,
1416 '--output-dir', '${ISOLATED_OUTDIR}',
1417 '--timeout', '3600']
1418 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011419 if asan:
John Budorick31cdce62019-04-03 20:56:111420 cmdline += [os.path.join('bin', 'run_with_asan'), '--']
John Budorick8c4203042019-03-19 17:22:011421 cmdline += [
John Budorickfb97a852017-12-20 20:10:191422 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041423 '../../build/android/test_wrapper/logdog_wrapper.py',
1424 '--target', target,
hzl9ae14452017-04-04 23:38:021425 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481426 '--store-tombstones']
Yun Liu5764e0dc2019-10-24 01:50:221427 if clang_coverage or java_coverage:
Yun Liu7cef1072019-06-27 21:22:191428 cmdline += ['--coverage-dir', '${ISOLATED_OUTDIR}']
Scott Graham3be4b4162017-09-12 00:41:411429 elif is_fuchsia and test_type != 'script':
Brian Sheedy234580e52019-09-10 17:42:511430 cmdline += [
John Budorickfb97a852017-12-20 20:10:191431 '../../testing/test_env.py',
1432 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441433 '--test-launcher-bot-mode',
Sergey Ulanovd851243b2019-06-25 00:33:471434 '--system-log-file', '${ISOLATED_OUTDIR}/system_log'
John Budorickfb97a852017-12-20 20:10:191435 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321436 elif is_simplechrome and test_type != 'script':
Brian Sheedy234580e52019-09-10 17:42:511437 cmdline += [
Benjamin Pastene3bce864e2018-04-14 01:16:321438 '../../testing/test_env.py',
1439 os.path.join('bin', 'run_%s' % target),
1440 ]
kylechar39705682017-01-19 14:37:231441 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001442 extra_files.append('../../testing/xvfb.py')
Brian Sheedy234580e52019-09-10 17:42:511443 cmdline += [
Nico Webera7bc1cb2019-06-15 17:42:391444 '../../testing/xvfb.py',
1445 './' + str(executable) + executable_suffix,
1446 '--test-launcher-bot-mode',
1447 '--asan=%d' % asan,
1448 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1449 # supported.
1450 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021451 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1452 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
Nico Webera7bc1cb2019-06-15 17:42:391453 '--msan=%d' % msan,
1454 '--tsan=%d' % tsan,
1455 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471456 ]
1457 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
Brian Sheedy234580e52019-09-10 17:42:511458 cmdline += [
dprankea55584f12015-07-22 00:52:471459 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591460 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251461 '--test-launcher-bot-mode',
1462 '--asan=%d' % asan,
Caleb Raitto1fb2cc9e2019-06-14 01:04:231463 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1464 # supported.
Nico Webera7bc1cb2019-06-15 17:42:391465 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021466 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1467 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
dpranked8113582015-06-05 20:08:251468 '--msan=%d' % msan,
1469 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411470 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471471 ]
dpranke6abd8652015-08-28 03:21:111472 elif test_type == 'script':
Ben Pastene4534c39e2019-07-08 22:55:341473 # If we're testing a CrOS simplechrome build, assume we need to prepare a
1474 # DUT for testing. So prepend the command to run with the test wrapper.
Ben Pastene8ab6954d2018-05-04 04:08:241475 if is_simplechrome:
Ben Pastene908863c2019-07-25 16:20:031476 cmdline = [
1477 os.path.join('bin', 'cros_test_wrapper'),
1478 '--logs-dir=${ISOLATED_OUTDIR}',
1479 ]
Ben Pastene8ab6954d2018-05-04 04:08:241480 cmdline += [
dpranke6abd8652015-08-28 03:21:111481 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141482 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591483 ]
Dirk Prankef24e6b22018-03-27 20:12:301484 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471485 cmdline = [
1486 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591487 ]
dprankea55584f12015-07-22 00:52:471488 else:
1489 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1490 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251491
dprankecb4a2e242016-09-19 01:13:141492 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591493
dpranked8113582015-06-05 20:08:251494 return cmdline, extra_files
1495
dpranke74559b52015-06-10 21:20:391496 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331497 return self.PathJoin(self.chromium_src_dir,
1498 self.ToSrcRelPath(build_path),
1499 *comps)
dpranked8113582015-06-05 20:08:251500
dprankeee5b51f62015-04-09 00:03:221501 def ToSrcRelPath(self, path):
1502 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501503 if path.startswith('//'):
1504 return path[2:].replace('/', self.sep)
1505 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351506
Dirk Pranke0fd41bcd2015-06-19 00:05:501507 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141508 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501509 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001510 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501511 if ret:
1512 return ret
1513
Dirk Prankef24e6b22018-03-27 20:12:301514 build_path = self.args.path
1515 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141516 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301517 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141518 gn_output_path = output_path + '.gn'
1519
dpranke7837fc362015-11-19 03:54:161520 inp = self.ReadInputJSON(['files', 'test_targets',
1521 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321522 if self.args.verbose:
1523 self.Print()
1524 self.Print('analyze input:')
1525 self.PrintJSON(inp)
1526 self.Print()
1527
dpranke76734662015-04-16 02:17:501528
dpranke7c5f614d2015-07-22 23:43:391529 # This shouldn't normally happen, but could due to unusual race conditions,
1530 # like a try job that gets scheduled before a patch lands but runs after
1531 # the patch has landed.
1532 if not inp['files']:
1533 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161534 self.WriteJSON({
1535 'status': 'No dependency',
1536 'compile_targets': [],
1537 'test_targets': [],
1538 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391539 return 0
1540
dprankecb4a2e242016-09-19 01:13:141541 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161542 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561543
dprankecb4a2e242016-09-19 01:13:141544 isolate_map = self.ReadIsolateMap()
1545 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1546 isolate_map, inp['additional_compile_targets'])
1547 if err:
1548 raise MBErr(err)
1549
1550 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1551 isolate_map, inp['test_targets'])
1552 if err:
1553 raise MBErr(err)
1554 labels_to_targets = {}
1555 for i, label in enumerate(gn_inp['test_targets']):
1556 labels_to_targets[label] = inp['test_targets'][i]
1557
dprankef61de2f2015-05-14 04:09:561558 try:
dprankecb4a2e242016-09-19 01:13:141559 self.WriteJSON(gn_inp, gn_input_path)
1560 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
Debrian Figueroaae51d0d2019-07-22 18:04:111561 ret, output, _ = self.Run(cmd, force_verbose=True)
dprankecb4a2e242016-09-19 01:13:141562 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:111563 if self.args.json_output:
1564 # write errors to json.output
1565 self.WriteJSON({'output': output}, self.args.json_output)
dprankecb4a2e242016-09-19 01:13:141566 return ret
dpranke067d0142015-05-14 22:52:451567
dprankecb4a2e242016-09-19 01:13:141568 gn_outp_str = self.ReadFile(gn_output_path)
1569 try:
1570 gn_outp = json.loads(gn_outp_str)
1571 except Exception as e:
1572 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1573 % (repr(gn_outp_str), str(e)))
1574 raise
1575
1576 outp = {}
1577 if 'status' in gn_outp:
1578 outp['status'] = gn_outp['status']
1579 if 'error' in gn_outp:
1580 outp['error'] = gn_outp['error']
1581 if 'invalid_targets' in gn_outp:
1582 outp['invalid_targets'] = gn_outp['invalid_targets']
1583 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491584 all_input_compile_targets = sorted(
1585 set(inp['test_targets'] + inp['additional_compile_targets']))
1586
1587 # If we're building 'all', we can throw away the rest of the targets
1588 # since they're redundant.
dpranke385a3102016-09-20 22:04:081589 if 'all' in gn_outp['compile_targets']:
1590 outp['compile_targets'] = ['all']
1591 else:
Dirk Pranke45165072017-11-08 04:57:491592 outp['compile_targets'] = gn_outp['compile_targets']
1593
1594 # crbug.com/736215: When GN returns targets back, for targets in
1595 # the default toolchain, GN will have generated a phony ninja
1596 # target matching the label, and so we can safely (and easily)
1597 # transform any GN label into the matching ninja target. For
1598 # targets in other toolchains, though, GN doesn't generate the
1599 # phony targets, and we don't know how to turn the labels into
1600 # compile targets. In this case, we also conservatively give up
1601 # and build everything. Probably the right thing to do here is
1602 # to have GN return the compile targets directly.
1603 if any("(" in target for target in outp['compile_targets']):
1604 self.Print('WARNING: targets with non-default toolchains were '
1605 'found, building everything instead.')
1606 outp['compile_targets'] = all_input_compile_targets
1607 else:
dpranke385a3102016-09-20 22:04:081608 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491609 label.replace('//', '') for label in outp['compile_targets']]
1610
1611 # Windows has a maximum command line length of 8k; even Linux
1612 # maxes out at 128k; if analyze returns a *really long* list of
1613 # targets, we just give up and conservatively build everything instead.
1614 # Probably the right thing here is for ninja to support response
1615 # files as input on the command line
1616 # (see https://github.com/ninja-build/ninja/issues/1355).
1617 if len(' '.join(outp['compile_targets'])) > 7*1024:
1618 self.Print('WARNING: Too many compile targets were affected.')
1619 self.Print('WARNING: Building everything instead to avoid '
1620 'command-line length issues.')
1621 outp['compile_targets'] = all_input_compile_targets
1622
1623
dprankecb4a2e242016-09-19 01:13:141624 if 'test_targets' in gn_outp:
1625 outp['test_targets'] = [
1626 labels_to_targets[label] for label in gn_outp['test_targets']]
1627
1628 if self.args.verbose:
1629 self.Print()
1630 self.Print('analyze output:')
1631 self.PrintJSON(outp)
1632 self.Print()
1633
1634 self.WriteJSON(outp, output_path)
1635
dprankef61de2f2015-05-14 04:09:561636 finally:
dprankecb4a2e242016-09-19 01:13:141637 if self.Exists(gn_input_path):
1638 self.RemoveFile(gn_input_path)
1639 if self.Exists(gn_output_path):
1640 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351641
1642 return 0
1643
dpranked8113582015-06-05 20:08:251644 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301645 path = self.args.input_path
1646 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351647 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321648 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351649
1650 try:
1651 inp = json.loads(self.ReadFile(path))
1652 except Exception as e:
1653 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321654 (path, e), output_path)
dpranked8113582015-06-05 20:08:251655
1656 for k in required_keys:
1657 if not k in inp:
1658 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1659 output_path)
dprankefe4602312015-04-08 16:20:351660
1661 return inp
1662
dpranked5b2b9432015-06-23 16:55:301663 def WriteFailureAndRaise(self, msg, output_path):
1664 if output_path:
dprankee0547cd2015-09-15 01:27:401665 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351666 raise MBErr(msg)
1667
dprankee0547cd2015-09-15 01:27:401668 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321669 try:
dprankee0547cd2015-09-15 01:27:401670 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1671 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321672 except Exception as e:
1673 raise MBErr('Error %s writing to the output path "%s"' %
1674 (e, path))
dprankefe4602312015-04-08 16:20:351675
aneeshmde50f472016-04-01 01:13:101676 def CheckCompile(self, master, builder):
1677 url_template = self.args.url_template + '/{builder}/builds/_all?as_text=1'
1678 url = urllib2.quote(url_template.format(master=master, builder=builder),
1679 safe=':/()?=')
1680 try:
1681 builds = json.loads(self.Fetch(url))
1682 except Exception as e:
1683 return str(e)
1684 successes = sorted(
1685 [int(x) for x in builds.keys() if "text" in builds[x] and
1686 cmp(builds[x]["text"][:2], ["build", "successful"]) == 0],
1687 reverse=True)
1688 if not successes:
1689 return "no successful builds"
1690 build = builds[str(successes[0])]
1691 step_names = set([step["name"] for step in build["steps"]])
1692 compile_indicators = set(["compile", "compile (with patch)", "analyze"])
1693 if compile_indicators & step_names:
1694 return "compiles"
1695 return "does not compile"
1696
dpranke3cec199c2015-09-22 23:29:021697 def PrintCmd(self, cmd, env):
1698 if self.platform == 'win32':
1699 env_prefix = 'set '
1700 env_quoter = QuoteForSet
1701 shell_quoter = QuoteForCmd
1702 else:
1703 env_prefix = ''
1704 env_quoter = pipes.quote
1705 shell_quoter = pipes.quote
1706
1707 def print_env(var):
1708 if env and var in env:
1709 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1710
dprankeec079262016-06-07 02:21:201711 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021712
dpranke8c2cfd32015-09-17 20:12:331713 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351714 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021715 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351716
dprankecda00332015-04-11 04:18:321717 def PrintJSON(self, obj):
1718 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1719
dpranke751516a2015-10-03 01:11:341720 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301721 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381722 if self.platform == 'win32':
1723 # On Windows use the batch script since there is no exe
1724 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1725 else:
1726 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341727 if self.args.jobs:
1728 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1729 ninja_cmd.append(target)
Dirk Pranke5f22a822019-05-23 22:55:251730 ret, _, _ = self.Run(ninja_cmd, buffer_output=False)
dpranke751516a2015-10-03 01:11:341731 return ret
1732
Stephen Martiniscd377012019-10-18 17:40:461733 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True,
1734 stdin=None):
dprankefe4602312015-04-08 16:20:351735 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401736 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021737 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351738 if self.args.dryrun:
1739 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401740
Stephen Martiniscd377012019-10-18 17:40:461741 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output,
1742 stdin=stdin)
dprankee0547cd2015-09-15 01:27:401743 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341744 if ret:
1745 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351746 if out:
Debrian Figueroaae582232019-07-17 01:54:451747 # This is the error seen on the logs
dprankeee5b51f62015-04-09 00:03:221748 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351749 if err:
dprankeee5b51f62015-04-09 00:03:221750 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351751 return ret, out, err
1752
Stephen Martiniscd377012019-10-18 17:40:461753 def Call(self, cmd, env=None, buffer_output=True, stdin=None):
dpranke751516a2015-10-03 01:11:341754 if buffer_output:
1755 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1756 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
Stephen Martiniscd377012019-10-18 17:40:461757 env=env, stdin=subprocess.PIPE)
1758 out, err = p.communicate(input=stdin)
dpranke751516a2015-10-03 01:11:341759 else:
1760 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1761 env=env)
1762 p.wait()
1763 out = err = ''
dprankefe4602312015-04-08 16:20:351764 return p.returncode, out, err
1765
1766 def ExpandUser(self, path):
1767 # This function largely exists so it can be overridden for testing.
1768 return os.path.expanduser(path)
1769
1770 def Exists(self, path):
1771 # This function largely exists so it can be overridden for testing.
1772 return os.path.exists(path)
1773
dpranke867bcf4a2016-03-14 22:28:321774 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501775 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321776 f = urllib2.urlopen(url)
1777 contents = f.read()
1778 f.close()
1779 return contents
1780
dprankec3441d12015-06-23 23:01:351781 def MaybeMakeDirectory(self, path):
1782 try:
1783 os.makedirs(path)
1784 except OSError, e:
1785 if e.errno != errno.EEXIST:
1786 raise
1787
dpranke8c2cfd32015-09-17 20:12:331788 def PathJoin(self, *comps):
1789 # This function largely exists so it can be overriden for testing.
1790 return os.path.join(*comps)
1791
dpranke030d7a6d2016-03-26 17:23:501792 def Print(self, *args, **kwargs):
1793 # This function largely exists so it can be overridden for testing.
1794 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101795 if kwargs.get('stream', sys.stdout) == sys.stdout:
1796 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501797
dprankefe4602312015-04-08 16:20:351798 def ReadFile(self, path):
1799 # This function largely exists so it can be overriden for testing.
1800 with open(path) as fp:
1801 return fp.read()
1802
dpranke030d7a6d2016-03-26 17:23:501803 def RelPath(self, path, start='.'):
1804 # This function largely exists so it can be overriden for testing.
1805 return os.path.relpath(path, start)
1806
dprankef61de2f2015-05-14 04:09:561807 def RemoveFile(self, path):
1808 # This function largely exists so it can be overriden for testing.
1809 os.remove(path)
1810
dprankec161aa92015-09-14 20:21:131811 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331812 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131813 # In other places in chromium, we often have to retry this command
1814 # because we're worried about other processes still holding on to
1815 # file handles, but when MB is invoked, it will be early enough in the
1816 # build that their should be no other processes to interfere. We
1817 # can change this if need be.
1818 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1819 else:
1820 shutil.rmtree(abs_path, ignore_errors=True)
1821
Dirk Prankef24e6b22018-03-27 20:12:301822 def TempDir(self):
1823 # This function largely exists so it can be overriden for testing.
1824 return tempfile.mkdtemp(prefix='mb_')
1825
dprankef61de2f2015-05-14 04:09:561826 def TempFile(self, mode='w'):
1827 # This function largely exists so it can be overriden for testing.
1828 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1829
dprankee0547cd2015-09-15 01:27:401830 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351831 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401832 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301833 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351834 with open(path, 'w') as fp:
1835 return fp.write(contents)
1836
dprankef61de2f2015-05-14 04:09:561837
Stephen Martiniscd377012019-10-18 17:40:461838class LedResult(object):
1839 """Holds the result of a led operation. Can be chained using |then|."""
1840
1841 def __init__(self, result, run_cmd):
1842 self._result = result
1843 self._run_cmd = run_cmd
1844
1845 @property
1846 def result(self):
1847 """The mutable result data of the previous led call as decoded JSON."""
1848 return self._result
1849
1850 def then(self, *cmd):
1851 """Invoke led, passing it the current `result` data as input.
1852
1853 Returns another LedResult object with the output of the command.
1854 """
1855 return self.__class__(
1856 self._run_cmd(self._result, cmd), self._run_cmd)
1857
1858
1859
dprankefe4602312015-04-08 16:20:351860class MBErr(Exception):
1861 pass
1862
1863
dpranke3cec199c2015-09-22 23:29:021864# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1865# details of this next section, which handles escaping command lines
1866# so that they can be copied and pasted into a cmd window.
1867UNSAFE_FOR_SET = set('^<>&|')
1868UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1869ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1870
1871
1872def QuoteForSet(arg):
1873 if any(a in UNSAFE_FOR_SET for a in arg):
1874 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1875 return arg
1876
1877
1878def QuoteForCmd(arg):
1879 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021880 if arg == '' or ' ' in arg or '"' in arg:
1881 quote_re = re.compile(r'(\\*)"')
1882 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1883
1884 # Then check to see if the arg contains any metacharacters other than
1885 # double quotes; if it does, quote everything (including the double
1886 # quotes) for safety.
1887 if any(a in UNSAFE_FOR_CMD for a in arg):
1888 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1889 return arg
1890
1891
dprankefe4602312015-04-08 16:20:351892if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591893 sys.exit(main(sys.argv[1:]))