blob: a708edaf71685af75584a6ee548d39d666ed4368 [file] [log] [blame]
dprankefe4602312015-04-08 16:20:351#!/usr/bin/env python
Greg Guterman1492aeb42020-01-14 22:59:142# Copyright 2020 The Chromium Authors. All rights reserved.
dprankefe4602312015-04-08 16:20:353# 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
Greg Guterman1492aeb42020-01-14 22:59:1412from __future__ import absolute_import
dprankefe4602312015-04-08 16:20:3513from __future__ import print_function
14
15import argparse
16import ast
Stephen Martinis1db25be82019-12-14 02:58:0617import collections
dprankec3441d12015-06-23 23:01:3518import errno
dprankefe4602312015-04-08 16:20:3519import json
20import os
dpranke68d1cb182015-09-17 23:30:0021import pipes
Dirk Pranke8cb6aa782017-12-16 02:31:3322import platform
dpranked8113582015-06-05 20:08:2523import pprint
dpranke3cec199c2015-09-22 23:29:0224import re
dprankefe4602312015-04-08 16:20:3525import shutil
26import sys
27import subprocess
dprankef61de2f2015-05-14 04:09:5628import tempfile
dprankebbe6d4672016-04-19 06:56:5729import traceback
dpranke867bcf4a2016-03-14 22:28:3230import urllib2
Dirk Prankef24e6b22018-03-27 20:12:3031import zipfile
dpranke867bcf4a2016-03-14 22:28:3232
33from collections import OrderedDict
dprankefe4602312015-04-08 16:20:3534
dprankeeca4a782016-04-14 01:42:3835CHROMIUM_SRC_DIR = os.path.dirname(os.path.dirname(os.path.dirname(
36 os.path.abspath(__file__))))
37sys.path = [os.path.join(CHROMIUM_SRC_DIR, 'build')] + sys.path
Greg Guterman1492aeb42020-01-14 22:59:1438sys.path.insert(0, os.path.join(
39 os.path.dirname(os.path.abspath(__file__)), '..'))
dprankeeca4a782016-04-14 01:42:3840
41import gn_helpers
Greg Guterman1492aeb42020-01-14 22:59:1442from mb.lib import validation
43
44
45def DefaultVals():
46 """Default mixin values"""
47 return {
48 'args_file': '',
49 'cros_passthrough': False,
50 'gn_args': '',
51 }
52
dprankeeca4a782016-04-14 01:42:3853
Karen Qian92ffd1a2019-09-11 01:09:2354def PruneVirtualEnv():
55 # Set by VirtualEnv, no need to keep it.
56 os.environ.pop('VIRTUAL_ENV', None)
57
58 # Set by VPython, if scripts want it back they have to set it explicitly.
59 os.environ.pop('PYTHONNOUSERSITE', None)
60
61 # Look for "activate_this.py" in this path, which is installed by VirtualEnv.
62 # This mechanism is used by vpython as well to sanitize VirtualEnvs from
63 # $PATH.
64 os.environ['PATH'] = os.pathsep.join([
65 p for p in os.environ.get('PATH', '').split(os.pathsep)
66 if not os.path.isfile(os.path.join(p, 'activate_this.py'))
67 ])
68
dprankeeca4a782016-04-14 01:42:3869
dprankefe4602312015-04-08 16:20:3570def main(args):
Karen Qian92ffd1a2019-09-11 01:09:2371 # Prune all evidence of VPython/VirtualEnv out of the environment. This means
72 # that we 'unwrap' vpython VirtualEnv path/env manipulation. Invocations of
73 # `python` from GN should never inherit the gn.py's own VirtualEnv. This also
74 # helps to ensure that generated ninja files do not reference python.exe from
75 # the VirtualEnv generated from depot_tools' own .vpython file (or lack
76 # thereof), but instead reference the default python from the PATH.
77 PruneVirtualEnv()
78
dprankeee5b51f62015-04-09 00:03:2279 mbw = MetaBuildWrapper()
dpranke255085e2016-03-16 05:23:5980 return mbw.Main(args)
dprankefe4602312015-04-08 16:20:3581
dprankefe4602312015-04-08 16:20:3582class MetaBuildWrapper(object):
83 def __init__(self):
dprankeeca4a782016-04-14 01:42:3884 self.chromium_src_dir = CHROMIUM_SRC_DIR
Greg Guterman1492aeb42020-01-14 22:59:1485 self.default_config_master = os.path.join(self.chromium_src_dir, 'tools',
86 'mb', 'mb_config.pyl')
87 self.default_config_bucket = os.path.join(self.chromium_src_dir, 'tools',
88 'mb', 'mb_config_buckets.pyl')
kjellander902bcb62016-10-26 06:20:5089 self.default_isolate_map = os.path.join(self.chromium_src_dir, 'testing',
90 'buildbot', 'gn_isolate_map.pyl')
Greg Guterman1492aeb42020-01-14 22:59:1491 self.group_by_bucket = False
dpranke8c2cfd32015-09-17 20:12:3392 self.executable = sys.executable
dpranked1fba482015-04-14 20:54:5193 self.platform = sys.platform
dpranke8c2cfd32015-09-17 20:12:3394 self.sep = os.sep
dprankefe4602312015-04-08 16:20:3595 self.args = argparse.Namespace()
96 self.configs = {}
Greg Guterman1492aeb42020-01-14 22:59:1497 self.public_artifact_builders = None
dprankefe4602312015-04-08 16:20:3598 self.masters = {}
Greg Guterman1492aeb42020-01-14 22:59:1499 self.buckets = {}
dprankefe4602312015-04-08 16:20:35100 self.mixins = {}
dprankefe4602312015-04-08 16:20:35101
dpranke255085e2016-03-16 05:23:59102 def Main(self, args):
103 self.ParseArgs(args)
104 try:
105 ret = self.args.func()
106 if ret:
107 self.DumpInputFiles()
108 return ret
109 except KeyboardInterrupt:
dprankecb4a2e242016-09-19 01:13:14110 self.Print('interrupted, exiting')
dpranke255085e2016-03-16 05:23:59111 return 130
dprankebbe6d4672016-04-19 06:56:57112 except Exception:
dpranke255085e2016-03-16 05:23:59113 self.DumpInputFiles()
dprankebbe6d4672016-04-19 06:56:57114 s = traceback.format_exc()
115 for l in s.splitlines():
116 self.Print(l)
dpranke255085e2016-03-16 05:23:59117 return 1
118
dprankefe4602312015-04-08 16:20:35119 def ParseArgs(self, argv):
120 def AddCommonOptions(subp):
Greg Guterman1492aeb42020-01-14 22:59:14121 group = subp.add_mutually_exclusive_group()
122 group.add_argument(
123 '-m', '--master', help='master name to look up config from')
124 group.add_argument('-u', '--bucket', help='bucket to look up config from')
dprankefe4602312015-04-08 16:20:35125 subp.add_argument('-b', '--builder',
126 help='builder name to look up config from')
dprankefe4602312015-04-08 16:20:35127 subp.add_argument('-c', '--config',
128 help='configuration to analyze')
shenghuazhang804b21542016-10-11 02:06:49129 subp.add_argument('--phase',
130 help='optional phase name (used when builders '
131 'do multiple compiles with different '
132 'arguments in a single build)')
Greg Guterman1492aeb42020-01-14 22:59:14133 subp.add_argument(
134 '-f',
135 '--config-file',
136 metavar='PATH',
137 help=('path to config file '
138 '(default is mb_config[_bucket].pyl'))
kjellander902bcb62016-10-26 06:20:50139 subp.add_argument('-i', '--isolate-map-file', metavar='PATH',
kjellander902bcb62016-10-26 06:20:50140 help='path to isolate map file '
Zhiling Huang66958462018-02-03 00:28:20141 '(default is %(default)s)',
142 default=[],
143 action='append',
144 dest='isolate_map_files')
dpranked0c138b2016-04-13 18:28:47145 subp.add_argument('-g', '--goma-dir',
146 help='path to goma directory')
agrieve41d21a72016-04-14 18:02:26147 subp.add_argument('--android-version-code',
Dirk Pranked181a1a2017-12-14 01:47:11148 help='Sets GN arg android_default_version_code')
agrieve41d21a72016-04-14 18:02:26149 subp.add_argument('--android-version-name',
Dirk Pranked181a1a2017-12-14 01:47:11150 help='Sets GN arg android_default_version_name')
dprankefe4602312015-04-08 16:20:35151 subp.add_argument('-n', '--dryrun', action='store_true',
152 help='Do a dry run (i.e., do nothing, just print '
153 'the commands that will run)')
dprankee0547cd2015-09-15 01:27:40154 subp.add_argument('-v', '--verbose', action='store_true',
155 help='verbose logging')
dprankefe4602312015-04-08 16:20:35156
Stephen Martinisb40a6852019-07-23 01:48:30157 parser = argparse.ArgumentParser(
158 prog='mb', description='mb (meta-build) is a python wrapper around GN. '
159 'See the user guide in '
160 '//tools/mb/docs/user_guide.md for detailed usage '
161 'instructions.')
162
dprankefe4602312015-04-08 16:20:35163 subps = parser.add_subparsers()
164
165 subp = subps.add_parser('analyze',
Stephen Martinis239c35a2019-07-22 19:34:40166 description='Analyze whether changes to a set of '
167 'files will cause a set of binaries to '
168 'be rebuilt.')
dprankefe4602312015-04-08 16:20:35169 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30170 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35171 help='path build was generated into.')
Dirk Prankef24e6b22018-03-27 20:12:30172 subp.add_argument('input_path',
dprankefe4602312015-04-08 16:20:35173 help='path to a file containing the input arguments '
174 'as a JSON object.')
Dirk Prankef24e6b22018-03-27 20:12:30175 subp.add_argument('output_path',
dprankefe4602312015-04-08 16:20:35176 help='path to a file containing the output arguments '
177 'as a JSON object.')
Debrian Figueroaae51d0d2019-07-22 18:04:11178 subp.add_argument('--json-output',
Debrian Figueroaae582232019-07-17 01:54:45179 help='Write errors to json.output')
dprankefe4602312015-04-08 16:20:35180 subp.set_defaults(func=self.CmdAnalyze)
181
dprankef37aebb92016-09-23 01:14:49182 subp = subps.add_parser('export',
Stephen Martinis239c35a2019-07-22 19:34:40183 description='Print out the expanded configuration '
184 'for each builder as a JSON object.')
Greg Guterman1492aeb42020-01-14 22:59:14185 subp.add_argument(
186 '-f',
187 '--config-file',
188 metavar='PATH',
189 help=('path to config file '
190 '(default is mb_config[_bucket].pyl'))
dprankef37aebb92016-09-23 01:14:49191 subp.add_argument('-g', '--goma-dir',
192 help='path to goma directory')
193 subp.set_defaults(func=self.CmdExport)
194
dprankefe4602312015-04-08 16:20:35195 subp = subps.add_parser('gen',
Stephen Martinis239c35a2019-07-22 19:34:40196 description='Generate a new set of build files.')
dprankefe4602312015-04-08 16:20:35197 AddCommonOptions(subp)
dpranke74559b52015-06-10 21:20:39198 subp.add_argument('--swarming-targets-file',
Erik Chenb7068242019-11-27 21:52:04199 help='generates runtime dependencies for targets listed '
200 'in file as .isolate and .isolated.gen.json files. '
201 'Targets should be listed by name, separated by '
202 'newline.')
Debrian Figueroaae51d0d2019-07-22 18:04:11203 subp.add_argument('--json-output',
Debrian Figueroaae582232019-07-17 01:54:45204 help='Write errors to json.output')
Dirk Prankef24e6b22018-03-27 20:12:30205 subp.add_argument('path',
dprankefe4602312015-04-08 16:20:35206 help='path to generate build into')
207 subp.set_defaults(func=self.CmdGen)
208
Erik Chen42df41d2018-08-21 17:13:31209 subp = subps.add_parser('isolate-everything',
Stephen Martinis239c35a2019-07-22 19:34:40210 description='Generates a .isolate for all targets. '
211 'Requires that mb.py gen has already '
212 'been run.')
Erik Chen42df41d2018-08-21 17:13:31213 AddCommonOptions(subp)
214 subp.set_defaults(func=self.CmdIsolateEverything)
215 subp.add_argument('path',
216 help='path build was generated into')
217
dpranke751516a2015-10-03 01:11:34218 subp = subps.add_parser('isolate',
Stephen Martinis239c35a2019-07-22 19:34:40219 description='Generate the .isolate files for a '
220 'given binary.')
dpranke751516a2015-10-03 01:11:34221 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30222 subp.add_argument('--no-build', dest='build', default=True,
223 action='store_false',
224 help='Do not build, just isolate')
225 subp.add_argument('-j', '--jobs', type=int,
226 help='Number of jobs to pass to ninja')
227 subp.add_argument('path',
dpranke751516a2015-10-03 01:11:34228 help='path build was generated into')
Dirk Prankef24e6b22018-03-27 20:12:30229 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34230 help='ninja target to generate the isolate for')
231 subp.set_defaults(func=self.CmdIsolate)
232
dprankefe4602312015-04-08 16:20:35233 subp = subps.add_parser('lookup',
Stephen Martinis239c35a2019-07-22 19:34:40234 description='Look up the command for a given '
235 'config or builder.')
dprankefe4602312015-04-08 16:20:35236 AddCommonOptions(subp)
Garrett Beatyb6cee042019-04-22 18:42:09237 subp.add_argument('--quiet', default=False, action='store_true',
238 help='Print out just the arguments, '
239 'do not emulate the output of the gen subcommand.')
240 subp.add_argument('--recursive', default=False, action='store_true',
241 help='Lookup arguments from imported files, '
242 'implies --quiet')
dprankefe4602312015-04-08 16:20:35243 subp.set_defaults(func=self.CmdLookup)
244
Stephen Martiniscd377012019-10-18 17:40:46245 subp = subps.add_parser('try',
246 description='Try your change on a remote builder')
247 AddCommonOptions(subp)
248 subp.add_argument('target',
249 help='ninja target to build and run')
Stephen Martinis3016084b2019-11-20 20:26:22250 subp.add_argument('--force', default=False, action='store_true',
251 help='Force the job to run. Ignores local checkout state;'
252 ' by default, the tool doesn\'t trigger jobs if there are'
253 ' local changes which are not present on Gerrit.')
Stephen Martiniscd377012019-10-18 17:40:46254 subp.set_defaults(func=self.CmdTry)
255
dpranke030d7a6d2016-03-26 17:23:50256 subp = subps.add_parser(
Stephen Martinis239c35a2019-07-22 19:34:40257 'run', formatter_class=argparse.RawDescriptionHelpFormatter)
dpranke030d7a6d2016-03-26 17:23:50258 subp.description = (
259 'Build, isolate, and run the given binary with the command line\n'
260 'listed in the isolate. You may pass extra arguments after the\n'
261 'target; use "--" if the extra arguments need to include switches.\n'
262 '\n'
263 'Examples:\n'
264 '\n'
265 ' % tools/mb/mb.py run -m chromium.linux -b "Linux Builder" \\\n'
266 ' //out/Default content_browsertests\n'
267 '\n'
268 ' % tools/mb/mb.py run out/Default content_browsertests\n'
269 '\n'
270 ' % tools/mb/mb.py run out/Default content_browsertests -- \\\n'
271 ' --test-launcher-retry-limit=0'
272 '\n'
273 )
dpranke751516a2015-10-03 01:11:34274 AddCommonOptions(subp)
Dirk Prankef24e6b22018-03-27 20:12:30275 subp.add_argument('-j', '--jobs', type=int,
dpranke751516a2015-10-03 01:11:34276 help='Number of jobs to pass to ninja')
277 subp.add_argument('--no-build', dest='build', default=True,
278 action='store_false',
279 help='Do not build, just isolate and run')
Dirk Prankef24e6b22018-03-27 20:12:30280 subp.add_argument('path',
dpranke030d7a6d2016-03-26 17:23:50281 help=('path to generate build into (or use).'
282 ' This can be either a regular path or a '
283 'GN-style source-relative path like '
284 '//out/Default.'))
Dirk Pranke8cb6aa782017-12-16 02:31:33285 subp.add_argument('-s', '--swarmed', action='store_true',
286 help='Run under swarming with the default dimensions')
287 subp.add_argument('-d', '--dimension', default=[], action='append', nargs=2,
288 dest='dimensions', metavar='FOO bar',
289 help='dimension to filter on')
290 subp.add_argument('--no-default-dimensions', action='store_false',
291 dest='default_dimensions', default=True,
292 help='Do not automatically add dimensions to the task')
Dirk Prankef24e6b22018-03-27 20:12:30293 subp.add_argument('target',
dpranke751516a2015-10-03 01:11:34294 help='ninja target to build and run')
dpranke030d7a6d2016-03-26 17:23:50295 subp.add_argument('extra_args', nargs='*',
296 help=('extra args to pass to the isolate to run. Use '
297 '"--" as the first arg if you need to pass '
298 'switches'))
dpranke751516a2015-10-03 01:11:34299 subp.set_defaults(func=self.CmdRun)
300
dprankefe4602312015-04-08 16:20:35301 subp = subps.add_parser('validate',
Stephen Martinis239c35a2019-07-22 19:34:40302 description='Validate the config file.')
dprankea5a77ca2015-07-16 23:24:17303 subp.add_argument('-f', '--config-file', metavar='PATH',
kjellander902bcb62016-10-26 06:20:50304 help='path to config file (default is %(default)s)')
dprankefe4602312015-04-08 16:20:35305 subp.set_defaults(func=self.CmdValidate)
306
Dirk Prankef24e6b22018-03-27 20:12:30307 subp = subps.add_parser('zip',
Stephen Martinis239c35a2019-07-22 19:34:40308 description='Generate a .zip containing the files '
309 'needed for a given binary.')
Dirk Prankef24e6b22018-03-27 20:12:30310 AddCommonOptions(subp)
311 subp.add_argument('--no-build', dest='build', default=True,
312 action='store_false',
313 help='Do not build, just isolate')
314 subp.add_argument('-j', '--jobs', type=int,
315 help='Number of jobs to pass to ninja')
316 subp.add_argument('path',
317 help='path build was generated into')
318 subp.add_argument('target',
319 help='ninja target to generate the isolate for')
320 subp.add_argument('zip_path',
321 help='path to zip file to create')
322 subp.set_defaults(func=self.CmdZip)
323
dprankefe4602312015-04-08 16:20:35324 subp = subps.add_parser('help',
325 help='Get help on a subcommand.')
326 subp.add_argument(nargs='?', action='store', dest='subcommand',
327 help='The command to get help for.')
328 subp.set_defaults(func=self.CmdHelp)
329
330 self.args = parser.parse_args(argv)
331
Greg Guterman1492aeb42020-01-14 22:59:14332 self.group_by_bucket = getattr(self.args, 'master', None) is None
333
334 # Use the correct default config file
335 # Not using hasattr here because it would still require a None check
336 if (self.args.func != self.CmdValidate
337 and getattr(self.args, 'config_file', None) is None):
338 # The default bucket config should be the same in all except replacing
339 # master with bucket and handling proprietary chrome mixins
340 if self.group_by_bucket:
341 self.args.config_file = self.default_config_bucket
342 else:
343 self.args.config_file = self.default_config_master
344
345
dprankeb2be10a2016-02-22 17:11:00346 def DumpInputFiles(self):
347
dprankef7b7eb7a2016-03-28 22:42:59348 def DumpContentsOfFilePassedTo(arg_name, path):
dprankeb2be10a2016-02-22 17:11:00349 if path and self.Exists(path):
dprankef7b7eb7a2016-03-28 22:42:59350 self.Print("\n# To recreate the file passed to %s:" % arg_name)
dprankecb4a2e242016-09-19 01:13:14351 self.Print("%% cat > %s <<EOF" % path)
dprankeb2be10a2016-02-22 17:11:00352 contents = self.ReadFile(path)
dprankef7b7eb7a2016-03-28 22:42:59353 self.Print(contents)
354 self.Print("EOF\n%\n")
dprankeb2be10a2016-02-22 17:11:00355
dprankef7b7eb7a2016-03-28 22:42:59356 if getattr(self.args, 'input_path', None):
357 DumpContentsOfFilePassedTo(
Dirk Prankef24e6b22018-03-27 20:12:30358 'argv[0] (input_path)', self.args.input_path)
dprankef7b7eb7a2016-03-28 22:42:59359 if getattr(self.args, 'swarming_targets_file', None):
360 DumpContentsOfFilePassedTo(
361 '--swarming-targets-file', self.args.swarming_targets_file)
dprankeb2be10a2016-02-22 17:11:00362
dprankefe4602312015-04-08 16:20:35363 def CmdAnalyze(self):
dpranke751516a2015-10-03 01:11:34364 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11365 return self.RunGNAnalyze(vals)
dprankefe4602312015-04-08 16:20:35366
Greg Guterman1492aeb42020-01-14 22:59:14367 def CmdExportBucket(self):
368 self.ReadConfigFile()
369 obj = {}
370 for bucket, builders in self.buckets.items():
371 obj[bucket] = {}
372 for builder in builders:
373 config = self.buckets[bucket][builder]
374 if not config:
375 continue
376
377 if isinstance(config, dict):
378 args = {
379 k: FlattenConfig(self.configs, self.mixins, v)['gn_args']
380 for k, v in config.items()
381 }
382 elif config.startswith('//'):
383 args = config
384 else:
385 args = FlattenConfig(self.configs, self.mixins, config)['gn_args']
386 if 'error' in args:
387 continue
388
389 obj[bucket][builder] = args
390
391 # Dump object and trim trailing whitespace.
392 s = '\n'.join(
393 l.rstrip()
394 for l in json.dumps(obj, sort_keys=True, indent=2).splitlines())
395 self.Print(s)
396 return 0
397
dprankef37aebb92016-09-23 01:14:49398 def CmdExport(self):
Greg Guterman1492aeb42020-01-14 22:59:14399 ''' Deprecated in favor of CmdExportBucket '''
400 if self.group_by_bucket:
401 return self.CmdExportBucket()
402
dprankef37aebb92016-09-23 01:14:49403 self.ReadConfigFile()
404 obj = {}
405 for master, builders in self.masters.items():
406 obj[master] = {}
407 for builder in builders:
408 config = self.masters[master][builder]
409 if not config:
410 continue
411
shenghuazhang804b21542016-10-11 02:06:49412 if isinstance(config, dict):
Greg Guterman1492aeb42020-01-14 22:59:14413 args = {
414 k: FlattenConfig(self.configs, self.mixins, v)['gn_args']
415 for k, v in config.items()
416 }
dprankef37aebb92016-09-23 01:14:49417 elif config.startswith('//'):
418 args = config
419 else:
Greg Guterman1492aeb42020-01-14 22:59:14420 args = FlattenConfig(self.configs, self.mixins, config)['gn_args']
dprankef37aebb92016-09-23 01:14:49421 if 'error' in args:
422 continue
423
424 obj[master][builder] = args
425
426 # Dump object and trim trailing whitespace.
427 s = '\n'.join(l.rstrip() for l in
428 json.dumps(obj, sort_keys=True, indent=2).splitlines())
429 self.Print(s)
430 return 0
431
dprankefe4602312015-04-08 16:20:35432 def CmdGen(self):
dpranke751516a2015-10-03 01:11:34433 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11434 return self.RunGNGen(vals)
dprankefe4602312015-04-08 16:20:35435
Erik Chen42df41d2018-08-21 17:13:31436 def CmdIsolateEverything(self):
437 vals = self.Lookup()
438 return self.RunGNGenAllIsolates(vals)
439
dprankefe4602312015-04-08 16:20:35440 def CmdHelp(self):
441 if self.args.subcommand:
442 self.ParseArgs([self.args.subcommand, '--help'])
443 else:
444 self.ParseArgs(['--help'])
445
dpranke751516a2015-10-03 01:11:34446 def CmdIsolate(self):
447 vals = self.GetConfig()
448 if not vals:
449 return 1
Dirk Prankef24e6b22018-03-27 20:12:30450 if self.args.build:
451 ret = self.Build(self.args.target)
452 if ret:
453 return ret
Dirk Pranked181a1a2017-12-14 01:47:11454 return self.RunGNIsolate(vals)
dpranke751516a2015-10-03 01:11:34455
456 def CmdLookup(self):
457 vals = self.Lookup()
Garrett Beatyb6cee042019-04-22 18:42:09458 gn_args = self.GNArgs(vals, expand_imports=self.args.recursive)
459 if self.args.quiet or self.args.recursive:
460 self.Print(gn_args, end='')
461 else:
462 cmd = self.GNCmd('gen', '_path_')
463 self.Print('\nWriting """\\\n%s""" to _path_/args.gn.\n' % gn_args)
464 env = None
dpranke751516a2015-10-03 01:11:34465
Garrett Beatyb6cee042019-04-22 18:42:09466 self.PrintCmd(cmd, env)
dpranke751516a2015-10-03 01:11:34467 return 0
468
Stephen Martiniscd377012019-10-18 17:40:46469 def CmdTry(self):
Stephen Martinis9388ffc2019-10-19 00:15:08470 ninja_target = self.args.target
471 if ninja_target.startswith('//'):
Stephen Martinis3016084b2019-11-20 20:26:22472 self.Print("Expected a ninja target like base_unittests, got %s" % (
473 ninja_target))
Stephen Martiniscd377012019-10-18 17:40:46474 return 1
475
Stephen Martinis3016084b2019-11-20 20:26:22476 _, out, _ = self.Run(['git', 'cl', 'diff', '--stat'], force_verbose=False)
477 if out:
478 self.Print("Your checkout appears to local changes which are not uploaded"
479 " to Gerrit. Changes must be committed and uploaded to Gerrit"
480 " to be tested using this tool.")
481 if not self.args.force:
482 return 1
483
Stephen Martiniscd377012019-10-18 17:40:46484 json_path = self.PathJoin(self.chromium_src_dir, 'out.json')
485 try:
486 ret, out, err = self.Run(
487 ['git', 'cl', 'issue', '--json=out.json'], force_verbose=False)
488 if ret != 0:
489 self.Print(
490 "Unable to fetch current issue. Output and error:\n%s\n%s" % (
491 out, err
492 ))
493 return ret
494 with open(json_path) as f:
495 issue_data = json.load(f)
496 finally:
497 if self.Exists(json_path):
498 os.unlink(json_path)
499
500 if not issue_data['issue']:
501 self.Print("Missing issue data. Upload your CL to Gerrit and try again.")
502 return 1
503
Stephen Martinis1f134492019-12-06 23:27:41504 class LedException(Exception):
505 pass
506
Stephen Martiniscd377012019-10-18 17:40:46507 def run_cmd(previous_res, cmd):
Stephen Martinis1f134492019-12-06 23:27:41508 if self.args.verbose:
509 self.Print(('| ' if previous_res else '') + ' '.join(cmd))
510
511 res, out, err = self.Call(cmd, stdin=previous_res)
Stephen Martiniscd377012019-10-18 17:40:46512 if res != 0:
Stephen Martinis1f134492019-12-06 23:27:41513 self.Print("Err while running '%s'. Output:\n%s\nstderr:\n%s" % (
514 ' '.join(cmd), out, err))
515 raise LedException()
Stephen Martiniscd377012019-10-18 17:40:46516 return out
517
Stephen Martinis1f134492019-12-06 23:27:41518 try:
519 result = LedResult(None, run_cmd).then(
520 # TODO(martiniss): maybe don't always assume the bucket?
521 'led', 'get-builder', 'luci.chromium.try:%s' % self.args.builder).then(
522 'led', 'edit', '-r', 'chromium_trybot_experimental',
523 '-p', 'tests=["%s"]' % ninja_target).then(
524 'led', 'edit-system', '--tag=purpose:user-debug-mb-try').then(
525 'led', 'edit-cr-cl', issue_data['issue_url']).then(
526 'led', 'launch').result
527 except LedException:
528 self.Print("If this is an unexpected error message, please file a bug"
529 " with https://goto.google.com/mb-try-bug")
530 raise
Stephen Martiniscd377012019-10-18 17:40:46531
532 swarming_data = json.loads(result)['swarming']
533 self.Print("Launched task at https://%s/task?id=%s" % (
534 swarming_data['host_name'], swarming_data['task_id']))
535
dpranke751516a2015-10-03 01:11:34536 def CmdRun(self):
537 vals = self.GetConfig()
538 if not vals:
539 return 1
Dirk Pranked181a1a2017-12-14 01:47:11540 if self.args.build:
Dirk Pranke5f22a822019-05-23 22:55:25541 self.Print('')
Dirk Prankef24e6b22018-03-27 20:12:30542 ret = self.Build(self.args.target)
dpranke751516a2015-10-03 01:11:34543 if ret:
544 return ret
Dirk Pranke5f22a822019-05-23 22:55:25545
546 self.Print('')
Dirk Pranked181a1a2017-12-14 01:47:11547 ret = self.RunGNIsolate(vals)
548 if ret:
549 return ret
dpranke751516a2015-10-03 01:11:34550
Dirk Pranke5f22a822019-05-23 22:55:25551 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33552 if self.args.swarmed:
Dirk Prankef24e6b22018-03-27 20:12:30553 return self._RunUnderSwarming(self.args.path, self.args.target)
Dirk Pranke8cb6aa782017-12-16 02:31:33554 else:
Dirk Prankef24e6b22018-03-27 20:12:30555 return self._RunLocallyIsolated(self.args.path, self.args.target)
556
557 def CmdZip(self):
Yun Liuc0f2f732019-09-18 17:06:31558 ret = self.CmdIsolate()
559 if ret:
560 return ret
Dirk Prankef24e6b22018-03-27 20:12:30561
Yun Liuc0f2f732019-09-18 17:06:31562 zip_dir = None
563 try:
564 zip_dir = self.TempDir()
565 remap_cmd = [
566 self.executable,
567 self.PathJoin(self.chromium_src_dir, 'tools', 'swarming_client',
568 'isolate.py'), 'remap', '--collapse_symlinks', '-s',
569 self.PathJoin(self.args.path, self.args.target + '.isolated'), '-o',
570 zip_dir
571 ]
572 self.Run(remap_cmd)
Dirk Prankef24e6b22018-03-27 20:12:30573
Yun Liuc0f2f732019-09-18 17:06:31574 zip_path = self.args.zip_path
575 with zipfile.ZipFile(
576 zip_path, 'w', zipfile.ZIP_DEFLATED, allowZip64=True) as fp:
577 for root, _, files in os.walk(zip_dir):
578 for filename in files:
579 path = self.PathJoin(root, filename)
580 fp.write(path, self.RelPath(path, zip_dir))
581 finally:
582 if zip_dir:
583 self.RemoveDirectory(zip_dir)
Dirk Pranke8cb6aa782017-12-16 02:31:33584
Robert Iannucci5a9d75f62018-03-02 05:28:20585 @staticmethod
586 def _AddBaseSoftware(cmd):
587 # HACK(iannucci): These packages SHOULD NOT BE HERE.
588 # Remove method once Swarming Pool Task Templates are implemented.
589 # crbug.com/812428
590
591 # Add in required base software. This should be kept in sync with the
John Budorick9d9175372019-04-01 19:04:24592 # `chromium_swarming` recipe module in build.git. All references to
593 # `swarming_module` below are purely due to this.
Robert Iannucci5a9d75f62018-03-02 05:28:20594 cipd_packages = [
595 ('infra/python/cpython/${platform}',
smut22dcd68e2019-06-25 23:33:27596 'version:2.7.15.chromium14'),
Robert Iannucci5a9d75f62018-03-02 05:28:20597 ('infra/tools/luci/logdog/butler/${platform}',
598 'git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c'),
599 ('infra/tools/luci/vpython-native/${platform}',
Andrii Shyshkalovb35c4cb2019-10-24 03:16:24600 'git_revision:e317c7d2c17d4c3460ee37524dfce4e1dee4306a'),
Robert Iannucci5a9d75f62018-03-02 05:28:20601 ('infra/tools/luci/vpython/${platform}',
Andrii Shyshkalovb35c4cb2019-10-24 03:16:24602 'git_revision:e317c7d2c17d4c3460ee37524dfce4e1dee4306a'),
Robert Iannucci5a9d75f62018-03-02 05:28:20603 ]
604 for pkg, vers in cipd_packages:
605 cmd.append('--cipd-package=.swarming_module:%s:%s' % (pkg, vers))
606
607 # Add packages to $PATH
608 cmd.extend([
609 '--env-prefix=PATH', '.swarming_module',
610 '--env-prefix=PATH', '.swarming_module/bin',
611 ])
612
613 # Add cache directives for vpython.
614 vpython_cache_path = '.swarming_module_cache/vpython'
615 cmd.extend([
616 '--named-cache=swarming_module_cache_vpython', vpython_cache_path,
617 '--env-prefix=VPYTHON_VIRTUALENV_ROOT', vpython_cache_path,
618 ])
619
Dirk Pranke8cb6aa782017-12-16 02:31:33620 def _RunUnderSwarming(self, build_dir, target):
Marc-Antoine Ruel559cc4732019-03-19 22:20:46621 isolate_server = 'isolateserver.appspot.com'
622 namespace = 'default-gzip'
623 swarming_server = 'chromium-swarm.appspot.com'
Dirk Pranke8cb6aa782017-12-16 02:31:33624 # TODO(dpranke): Look up the information for the target in
625 # the //testing/buildbot.json file, if possible, so that we
626 # can determine the isolate target, command line, and additional
627 # swarming parameters, if possible.
628 #
629 # TODO(dpranke): Also, add support for sharding and merging results.
630 dimensions = []
631 for k, v in self._DefaultDimensions() + self.args.dimensions:
632 dimensions += ['-d', k, v]
633
634 cmd = [
635 self.executable,
636 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
637 'archive',
Marc-Antoine Ruel559cc4732019-03-19 22:20:46638 '-s', self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
639 '-I', isolate_server,
640 '--namespace', namespace,
Dirk Pranke8cb6aa782017-12-16 02:31:33641 ]
Dirk Pranke5f22a822019-05-23 22:55:25642
643 # Talking to the isolateserver may fail because we're not logged in.
644 # We trap the command explicitly and rewrite the error output so that
645 # the error message is actually correct for a Chromium check out.
646 self.PrintCmd(cmd, env=None)
647 ret, out, err = self.Run(cmd, force_verbose=False)
Dirk Pranke8cb6aa782017-12-16 02:31:33648 if ret:
Dirk Pranke5f22a822019-05-23 22:55:25649 self.Print(' -> returned %d' % ret)
650 if out:
651 self.Print(out, end='')
652 if err:
653 # The swarming client will return an exit code of 2 (via
654 # argparse.ArgumentParser.error()) and print a message to indicate
655 # that auth failed, so we have to parse the message to check.
656 if (ret == 2 and 'Please login to' in err):
657 err = err.replace(' auth.py', ' tools/swarming_client/auth.py')
658 self.Print(err, end='', file=sys.stderr)
659
Dirk Pranke8cb6aa782017-12-16 02:31:33660 return ret
661
662 isolated_hash = out.splitlines()[0].split()[0]
663 cmd = [
664 self.executable,
665 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
666 'run',
667 '-s', isolated_hash,
Marc-Antoine Ruel559cc4732019-03-19 22:20:46668 '-I', isolate_server,
669 '--namespace', namespace,
670 '-S', swarming_server,
Stephen Martinis43ab3032019-09-11 20:07:41671 '--tags=purpose:user-debug-mb',
Dirk Pranke8cb6aa782017-12-16 02:31:33672 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20673 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33674 if self.args.extra_args:
675 cmd += ['--'] + self.args.extra_args
Dirk Pranke5f22a822019-05-23 22:55:25676 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33677 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
678 return ret
679
680 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50681 cmd = [
dpranke751516a2015-10-03 01:11:34682 self.executable,
683 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
684 'run',
685 '-s',
dpranke030d7a6d2016-03-26 17:23:50686 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33687 ]
dpranke030d7a6d2016-03-26 17:23:50688 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33689 cmd += ['--'] + self.args.extra_args
690 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34691 return ret
692
Dirk Pranke8cb6aa782017-12-16 02:31:33693 def _DefaultDimensions(self):
694 if not self.args.default_dimensions:
695 return []
696
697 # This code is naive and just picks reasonable defaults per platform.
698 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40699 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33700 elif self.platform.startswith('linux'):
Takuto Ikuta169663b2019-08-05 16:21:32701 os_dim = ('os', 'Ubuntu-16.04')
Dirk Pranke8cb6aa782017-12-16 02:31:33702 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40703 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33704 else:
705 raise MBErr('unrecognized platform string "%s"' % self.platform)
706
John Budorick9cf2d4c62019-11-11 23:56:12707 return [('pool', 'chromium.tests'),
Dirk Pranke8cb6aa782017-12-16 02:31:33708 ('cpu', 'x86-64'),
709 os_dim]
710
Greg Guterman1492aeb42020-01-14 22:59:14711 def CmdValidateBucket(self, print_ok=True):
712 errs = []
713
714 # Build a list of all of the configs referenced by builders.
715 all_configs = validation.GetAllConfigsBucket(self.buckets)
716
717 # Check that every referenced args file or config actually exists.
718 for config, loc in all_configs.items():
719 if config.startswith('//'):
720 if not self.Exists(self.ToAbsPath(config)):
721 errs.append(
722 'Unknown args file "%s" referenced from "%s".' % (config, loc))
723 elif not config in self.configs:
724 errs.append('Unknown config "%s" referenced from "%s".' % (config, loc))
725
726 # Check that every config and mixin is referenced.
727 validation.CheckAllConfigsAndMixinsReferenced(errs, all_configs,
728 self.configs, self.mixins)
729
730 validation.EnsureNoProprietaryMixinsBucket(
731 errs, self.default_config_bucket, self.args.config_file,
732 self.public_artifact_builders, self.buckets, self.configs, self.mixins)
733
734 validation.CheckDuplicateConfigs(errs, self.configs, self.mixins,
735 self.buckets, FlattenConfig)
736
737 if errs:
738 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
739 '\n ' + '\n '.join(errs))
740
741 if print_ok:
742 self.Print('mb config file %s looks ok.' % self.args.config_file)
743 return 0
744
dpranke0cafc162016-03-19 00:41:10745 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35746 errs = []
747
Greg Guterman1492aeb42020-01-14 22:59:14748 # Validate both bucket and master configs if
749 # a specific one isn't specified
750 if getattr(self.args, 'config_file', None) is None:
751 # Read the file to make sure it parses.
752 self.args.config_file = self.default_config_bucket
753 self.ReadConfigFile()
754 self.CmdValidateBucket()
755
756 self.args.config_file = self.default_config_master
757 self.ReadConfigFile()
758 else:
759 self.ReadConfigFile()
760 if self.group_by_bucket:
761 return self.CmdValidateBucket()
dprankefe4602312015-04-08 16:20:35762
dpranke3be00142016-03-17 22:46:04763 # Build a list of all of the configs referenced by builders.
Greg Guterman1492aeb42020-01-14 22:59:14764 all_configs = validation.GetAllConfigsMaster(self.masters)
dprankefe4602312015-04-08 16:20:35765
dpranke9dd5e252016-04-14 04:23:09766 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35767 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09768 if config.startswith('//'):
769 if not self.Exists(self.ToAbsPath(config)):
770 errs.append('Unknown args file "%s" referenced from "%s".' %
771 (config, loc))
772 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35773 errs.append('Unknown config "%s" referenced from "%s".' %
774 (config, loc))
775
Greg Guterman1492aeb42020-01-14 22:59:14776 # Check that every config and mixin is referenced.
777 validation.CheckAllConfigsAndMixinsReferenced(errs, all_configs,
778 self.configs, self.mixins)
dprankefe4602312015-04-08 16:20:35779
Greg Guterman1492aeb42020-01-14 22:59:14780 validation.EnsureNoProprietaryMixinsMaster(
781 errs, self.default_config_master, self.args.config_file, self.masters,
782 self.configs, self.mixins)
dprankefe4602312015-04-08 16:20:35783
Greg Guterman1492aeb42020-01-14 22:59:14784 validation.CheckDuplicateConfigs(errs, self.configs, self.mixins,
785 self.masters, FlattenConfig)
dprankefe4602312015-04-08 16:20:35786
dprankefe4602312015-04-08 16:20:35787 if errs:
dpranke4323c80632015-08-10 22:53:54788 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17789 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35790
dpranke0cafc162016-03-19 00:41:10791 if print_ok:
792 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35793 return 0
794
795 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30796 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34797
Greg Guterman1492aeb42020-01-14 22:59:14798 vals = DefaultVals()
dpranke751516a2015-10-03 01:11:34799 if self.args.builder or self.args.master or self.args.config:
800 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11801 # Re-run gn gen in order to ensure the config is consistent with the
802 # build dir.
803 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34804 return vals
805
Dirk Pranked181a1a2017-12-14 01:47:11806 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
807 'toolchain.ninja')
808 if not self.Exists(toolchain_path):
809 self.Print('Must either specify a path to an existing GN build dir '
810 'or pass in a -m/-b pair or a -c flag to specify the '
811 'configuration')
812 return {}
dpranke751516a2015-10-03 01:11:34813
Dirk Pranked181a1a2017-12-14 01:47:11814 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34815 return vals
816
dprankef37aebb92016-09-23 01:14:49817 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58818 args_contents = ""
819 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
820 if self.Exists(gn_args_path):
821 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34822 gn_args = []
823 for l in args_contents.splitlines():
Reid Klecknerfca22812020-02-04 02:28:07824 l = l.split('#', 2)[0].strip()
825 if not l:
826 continue
dpranke751516a2015-10-03 01:11:34827 fields = l.split(' ')
828 name = fields[0]
829 val = ' '.join(fields[2:])
830 gn_args.append('%s=%s' % (name, val))
831
dprankef37aebb92016-09-23 01:14:49832 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34833
834 def Lookup(self):
Erik Chen238f4ac2019-04-12 19:02:50835 vals = self.ReadIOSBotConfig()
836 if not vals:
837 self.ReadConfigFile()
Greg Guterman1492aeb42020-01-14 22:59:14838 if self.group_by_bucket:
839 config = self.ConfigFromArgsBucket()
840 else:
841 config = self.ConfigFromArgs()
Erik Chen238f4ac2019-04-12 19:02:50842 if config.startswith('//'):
843 if not self.Exists(self.ToAbsPath(config)):
844 raise MBErr('args file "%s" not found' % config)
Greg Guterman1492aeb42020-01-14 22:59:14845 vals = DefaultVals()
Erik Chen238f4ac2019-04-12 19:02:50846 vals['args_file'] = config
847 else:
848 if not config in self.configs:
849 raise MBErr('Config "%s" not found in %s' %
850 (config, self.args.config_file))
Greg Guterman1492aeb42020-01-14 22:59:14851 vals = FlattenConfig(self.configs, self.mixins, config)
Erik Chen238f4ac2019-04-12 19:02:50852 return vals
853
854 def ReadIOSBotConfig(self):
855 if not self.args.master or not self.args.builder:
856 return {}
857 path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
858 self.args.master, self.args.builder + '.json')
859 if not self.Exists(path):
860 return {}
861
862 contents = json.loads(self.ReadFile(path))
863 gn_args = ' '.join(contents.get('gn_args', []))
864
Greg Guterman1492aeb42020-01-14 22:59:14865 vals = DefaultVals()
Erik Chen238f4ac2019-04-12 19:02:50866 vals['gn_args'] = gn_args
dprankef37aebb92016-09-23 01:14:49867 return vals
dprankee0f486f2015-11-19 23:42:00868
dprankefe4602312015-04-08 16:20:35869 def ReadConfigFile(self):
870 if not self.Exists(self.args.config_file):
871 raise MBErr('config file not found at %s' % self.args.config_file)
872
873 try:
874 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
875 except SyntaxError as e:
876 raise MBErr('Failed to parse config file "%s": %s' %
877 (self.args.config_file, e))
878
dprankefe4602312015-04-08 16:20:35879 self.configs = contents['configs']
dprankefe4602312015-04-08 16:20:35880 self.mixins = contents['mixins']
Greg Guterman1492aeb42020-01-14 22:59:14881 self.masters = contents.get('masters')
882 self.buckets = contents.get('buckets')
883 self.public_artifact_builders = contents.get('public_artifact_builders')
884
885 self.group_by_bucket = bool(self.buckets)
dprankefe4602312015-04-08 16:20:35886
dprankecb4a2e242016-09-19 01:13:14887 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20888 if not self.args.isolate_map_files:
889 self.args.isolate_map_files = [self.default_isolate_map]
890
891 for f in self.args.isolate_map_files:
892 if not self.Exists(f):
893 raise MBErr('isolate map file not found at %s' % f)
894 isolate_maps = {}
895 for isolate_map in self.args.isolate_map_files:
896 try:
897 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
898 duplicates = set(isolate_map).intersection(isolate_maps)
899 if duplicates:
900 raise MBErr(
901 'Duplicate targets in isolate map files: %s.' %
902 ', '.join(duplicates))
903 isolate_maps.update(isolate_map)
904 except SyntaxError as e:
905 raise MBErr(
906 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
907 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14908
Greg Guterman1492aeb42020-01-14 22:59:14909 def ConfigFromArgsBucket(self):
910 if self.args.config:
911 if self.args.bucket or self.args.builder:
912 raise MBErr('Can not specify both -c/--config and -u/--bucket or '
913 '-b/--builder')
914
915 return self.args.config
916
917 if not self.args.bucket or not self.args.builder:
918 raise MBErr('Must specify either -c/--config or '
919 '(-u/--bucket and -b/--builder)')
920
921 if not self.args.bucket in self.buckets:
922 raise MBErr('Bucket name "%s" not found in "%s"' %
923 (self.args.bucket, self.args.config_file))
924
925 if not self.args.builder in self.buckets[self.args.bucket]:
926 raise MBErr('Builder name "%s" not found under buckets[%s] in "%s"' %
927 (self.args.builder, self.args.bucket, self.args.config_file))
928
929 config = self.buckets[self.args.bucket][self.args.builder]
930 if isinstance(config, dict):
931 if self.args.phase is None:
932 raise MBErr('Must specify a build --phase for %s on %s' %
933 (self.args.builder, self.args.bucket))
934 phase = str(self.args.phase)
935 if phase not in config:
936 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
937 (phase, self.args.builder, self.args.bucket))
938 return config[phase]
939
940 if self.args.phase is not None:
941 raise MBErr('Must not specify a build --phase for %s on %s' %
942 (self.args.builder, self.args.bucket))
943 return config
944
dprankefe4602312015-04-08 16:20:35945 def ConfigFromArgs(self):
Greg Guterman1492aeb42020-01-14 22:59:14946 ''' Deprecated in favor ConfigFromArgsBucket '''
dprankefe4602312015-04-08 16:20:35947 if self.args.config:
948 if self.args.master or self.args.builder:
949 raise MBErr('Can not specific both -c/--config and -m/--master or '
950 '-b/--builder')
951
952 return self.args.config
953
954 if not self.args.master or not self.args.builder:
955 raise MBErr('Must specify either -c/--config or '
956 '(-m/--master and -b/--builder)')
957
958 if not self.args.master in self.masters:
959 raise MBErr('Master name "%s" not found in "%s"' %
960 (self.args.master, self.args.config_file))
961
962 if not self.args.builder in self.masters[self.args.master]:
963 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
964 (self.args.builder, self.args.master, self.args.config_file))
965
dprankeb9380a12016-07-21 21:44:09966 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49967 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09968 if self.args.phase is None:
969 raise MBErr('Must specify a build --phase for %s on %s' %
970 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49971 phase = str(self.args.phase)
972 if phase not in config:
973 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09974 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49975 return config[phase]
dprankeb9380a12016-07-21 21:44:09976
977 if self.args.phase is not None:
978 raise MBErr('Must not specify a build --phase for %s on %s' %
979 (self.args.builder, self.args.master))
980 return config
dprankefe4602312015-04-08 16:20:35981
Takuto Ikuta9dffd7e2018-09-05 01:04:00982 def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
Dirk Prankef24e6b22018-03-27 20:12:30983 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50984
Takuto Ikuta9dffd7e2018-09-05 01:04:00985 if check:
986 cmd = self.GNCmd('gen', build_dir, '--check')
987 else:
988 cmd = self.GNCmd('gen', build_dir)
dprankeeca4a782016-04-14 01:42:38989 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:09990 if compute_inputs_for_analyze:
991 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:38992
993 # Since GN hasn't run yet, the build directory may not even exist.
994 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
995
996 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:54997 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:39998
dpranke751516a2015-10-03 01:11:34999 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:391000 # We need GN to generate the list of runtime dependencies for
1001 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:141002 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:391003 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:001004 path = self.args.swarming_targets_file
1005 if not self.Exists(path):
1006 self.WriteFailureAndRaise('"%s" does not exist' % path,
1007 output_path=None)
1008 contents = self.ReadFile(path)
Erik Chen42df41d2018-08-21 17:13:311009 isolate_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:001010
dprankecb4a2e242016-09-19 01:13:141011 isolate_map = self.ReadIsolateMap()
Dirk Pranke7a7e9b62019-02-17 01:46:251012 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
1013 isolate_map, build_dir)
1014
Erik Chen42df41d2018-08-21 17:13:311015 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:001016 if err:
Dirk Pranke7a7e9b62019-02-17 01:46:251017 raise MBErr(err)
dpranke74559b52015-06-10 21:20:391018
dpranke751516a2015-10-03 01:11:341019 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:141020 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:391021 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
1022
Debrian Figueroaae582232019-07-17 01:54:451023 ret, output, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:401024 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:111025 if self.args.json_output:
Debrian Figueroaae582232019-07-17 01:54:451026 # write errors to json.output
1027 self.WriteJSON({'output': output}, self.args.json_output)
Dirk Pranke7a7e9b62019-02-17 01:46:251028 # If `gn gen` failed, we should exit early rather than trying to
1029 # generate isolates. Run() will have already logged any error output.
1030 self.Print('GN gen failed: %d' % ret)
1031 return ret
dpranke74559b52015-06-10 21:20:391032
Erik Chen42df41d2018-08-21 17:13:311033 if getattr(self.args, 'swarming_targets_file', None):
Nico Weber0fd016762019-08-25 14:48:141034 ret = self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
Erik Chen42df41d2018-08-21 17:13:311035
Nico Weber0fd016762019-08-25 14:48:141036 return ret
Erik Chen42df41d2018-08-21 17:13:311037
1038 def RunGNGenAllIsolates(self, vals):
1039 """
1040 This command generates all .isolate files.
1041
1042 This command assumes that "mb.py gen" has already been run, as it relies on
1043 "gn ls" to fetch all gn targets. If uses that output, combined with the
1044 isolate_map, to determine all isolates that can be generated for the current
1045 gn configuration.
1046 """
1047 build_dir = self.args.path
1048 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
1049 force_verbose=False)
1050 if ret:
Yun Liuc0f2f732019-09-18 17:06:311051 # If `gn ls` failed, we should exit early rather than trying to
1052 # generate isolates.
1053 self.Print('GN ls failed: %d' % ret)
1054 return ret
Erik Chen42df41d2018-08-21 17:13:311055
1056 # Create a reverse map from isolate label to isolate dict.
1057 isolate_map = self.ReadIsolateMap()
1058 isolate_dict_map = {}
1059 for key, isolate_dict in isolate_map.iteritems():
1060 isolate_dict_map[isolate_dict['label']] = isolate_dict
1061 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
1062
1063 runtime_deps = []
1064
1065 isolate_targets = []
1066 # For every GN target, look up the isolate dict.
1067 for line in output.splitlines():
1068 target = line.strip()
1069 if target in isolate_dict_map:
1070 if isolate_dict_map[target]['type'] == 'additional_compile_target':
1071 # By definition, additional_compile_targets are not tests, so we
1072 # shouldn't generate isolates for them.
1073 continue
1074
1075 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
1076 runtime_deps.append(target)
1077
Dirk Pranke7a7e9b62019-02-17 01:46:251078 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
1079 isolate_map, build_dir)
1080
Erik Chen42df41d2018-08-21 17:13:311081 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
1082 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
1083 cmd = self.GNCmd('gen', build_dir)
1084 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
1085 self.Run(cmd)
1086
1087 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
1088
Dirk Pranke7a7e9b62019-02-17 01:46:251089 def RemovePossiblyStaleRuntimeDepsFiles(self, vals, targets, isolate_map,
1090 build_dir):
1091 # TODO(crbug.com/932700): Because `gn gen --runtime-deps-list-file`
1092 # puts the runtime_deps file in different locations based on the actual
1093 # type of a target, we may end up with multiple possible runtime_deps
1094 # files in a given build directory, where some of the entries might be
1095 # stale (since we might be reusing an existing build directory).
1096 #
1097 # We need to be able to get the right one reliably; you might think
1098 # we can just pick the newest file, but because GN won't update timestamps
1099 # if the contents of the files change, an older runtime_deps
1100 # file might actually be the one we should use over a newer one (see
1101 # crbug.com/932387 for a more complete explanation and example).
1102 #
1103 # In order to avoid this, we need to delete any possible runtime_deps
1104 # files *prior* to running GN. As long as the files aren't actually
1105 # needed during the build, this hopefully will not cause unnecessary
1106 # build work, and so it should be safe.
1107 #
1108 # Ultimately, we should just make sure we get the runtime_deps files
1109 # in predictable locations so we don't have this issue at all, and
1110 # that's what crbug.com/932700 is for.
1111 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, targets, isolate_map)
1112 for rpaths in possible_rpaths.values():
1113 for rpath in rpaths:
1114 path = self.ToAbsPath(build_dir, rpath)
1115 if self.Exists(path):
1116 self.RemoveFile(path)
1117
Erik Chen42df41d2018-08-21 17:13:311118 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
1119 """
1120 Generates isolates for a list of ninja targets.
1121
1122 Ninja targets are transformed to GN targets via isolate_map.
1123
1124 This function assumes that a previous invocation of "mb.py gen" has
1125 generated runtime deps for all targets.
1126 """
Dirk Pranke7a7e9b62019-02-17 01:46:251127 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, ninja_targets,
1128 isolate_map)
1129
1130 for target, rpaths in possible_rpaths.items():
1131 # TODO(crbug.com/932700): We don't know where each .runtime_deps
1132 # file might be, but assuming we called
1133 # RemovePossiblyStaleRuntimeDepsFiles prior to calling `gn gen`,
1134 # there should only be one file.
1135 found_one = False
1136 path_to_use = None
1137 for r in rpaths:
1138 path = self.ToAbsPath(build_dir, r)
1139 if self.Exists(path):
1140 if found_one:
1141 raise MBErr('Found more than one of %s' % ', '.join(rpaths))
1142 path_to_use = path
1143 found_one = True
1144
1145 if not found_one:
1146 raise MBErr('Did not find any of %s' % ', '.join(rpaths))
1147
1148 command, extra_files = self.GetIsolateCommand(target, vals)
1149 runtime_deps = self.ReadFile(path_to_use).splitlines()
1150
1151 canonical_target = target.replace(':','_').replace('/','_')
Nico Weber0fd016762019-08-25 14:48:141152 ret = self.WriteIsolateFiles(build_dir, command, canonical_target,
1153 runtime_deps, vals, extra_files)
1154 if ret:
1155 return ret
1156 return 0
Dirk Pranke7a7e9b62019-02-17 01:46:251157
1158 def PossibleRuntimeDepsPaths(self, vals, ninja_targets, isolate_map):
1159 """Returns a map of targets to possible .runtime_deps paths.
1160
1161 Each ninja target maps on to a GN label, but depending on the type
1162 of the GN target, `gn gen --runtime-deps-list-file` will write
1163 the .runtime_deps files into different locations. Unfortunately, in
1164 some cases we don't actually know which of multiple locations will
1165 actually be used, so we return all plausible candidates.
1166
1167 The paths that are returned are relative to the build directory.
1168 """
1169
jbudoricke3c4f95e2016-04-28 23:17:381170 android = 'target_os="android"' in vals['gn_args']
Dirk Pranke26de05aec2019-04-03 19:18:381171 ios = 'target_os="ios"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:421172 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:301173 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Dirk Pranke7a7e9b62019-02-17 01:46:251174 possible_runtime_deps_rpaths = {}
Erik Chen42df41d2018-08-21 17:13:311175 for target in ninja_targets:
John Budorick39f14962019-04-11 23:03:201176 target_type = isolate_map[target]['type']
1177 label = isolate_map[target]['label']
1178 stamp_runtime_deps = 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')
Erik Chen42df41d2018-08-21 17:13:311179 # TODO(https://crbug.com/876065): 'official_tests' use
1180 # type='additional_compile_target' to isolate tests. This is not the
1181 # intended use for 'additional_compile_target'.
John Budorick39f14962019-04-11 23:03:201182 if (target_type == 'additional_compile_target' and
Erik Chen42df41d2018-08-21 17:13:311183 target != 'official_tests'):
1184 # By definition, additional_compile_targets are not tests, so we
1185 # shouldn't generate isolates for them.
Dirk Pranke7a7e9b62019-02-17 01:46:251186 raise MBErr('Cannot generate isolate for %s since it is an '
1187 'additional_compile_target.' % target)
John Budorick39f14962019-04-11 23:03:201188 elif fuchsia or ios or target_type == 'generated_script':
1189 # iOS and Fuchsia targets end up as groups.
1190 # generated_script targets are always actions.
1191 rpaths = [stamp_runtime_deps]
Erik Chen42df41d2018-08-21 17:13:311192 elif android:
jbudoricke3c4f95e2016-04-28 23:17:381193 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:021194 # will result in runtime_deps associated with the stamp file, while the
1195 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:441196 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:251197 rpaths = [
dprankecb4a2e242016-09-19 01:13:141198 target + '.runtime_deps',
John Budorick39f14962019-04-11 23:03:201199 stamp_runtime_deps]
1200 elif (target_type == 'script' or
dprankecb4a2e242016-09-19 01:13:141201 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:111202 # For script targets, the build target is usually a group,
1203 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:491204 # for the label, which lives under the obj/ directory, but it may
1205 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:441206 label = isolate_map[target]['label']
John Budorick39f14962019-04-11 23:03:201207 rpaths = [stamp_runtime_deps]
Nico Weberd94b71a2018-02-22 22:00:301208 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:251209 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:491210 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251211 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:301212 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:251213 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:521214 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251215 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:021216
Dirk Pranke7a7e9b62019-02-17 01:46:251217 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:411218
Dirk Pranke7a7e9b62019-02-17 01:46:251219 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:341220
1221 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:301222 target = self.args.target
dprankecb4a2e242016-09-19 01:13:141223 isolate_map = self.ReadIsolateMap()
1224 err, labels = self.MapTargetsToLabels(isolate_map, [target])
1225 if err:
1226 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:251227
dprankecb4a2e242016-09-19 01:13:141228 label = labels[0]
dpranke751516a2015-10-03 01:11:341229
Dirk Prankef24e6b22018-03-27 20:12:301230 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:141231 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:341232
dprankeeca4a782016-04-14 01:42:381233 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:201234 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:341235 if ret:
dpranke030d7a6d2016-03-26 17:23:501236 if out:
1237 self.Print(out)
dpranke751516a2015-10-03 01:11:341238 return ret
1239
1240 runtime_deps = out.splitlines()
1241
Nico Weber0fd016762019-08-25 14:48:141242 ret = self.WriteIsolateFiles(build_dir, command, target, runtime_deps, vals,
1243 extra_files)
1244 if ret:
1245 return ret
dpranke751516a2015-10-03 01:11:341246
1247 ret, _, _ = self.Run([
1248 self.executable,
1249 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1250 'check',
1251 '-i',
1252 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1253 '-s',
1254 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1255 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301256
dprankefe4602312015-04-08 16:20:351257 return ret
1258
Nico Weber0fd016762019-08-25 14:48:141259 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps, vals,
dpranke751516a2015-10-03 01:11:341260 extra_files):
1261 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
Nico Weber0fd016762019-08-25 14:48:141262 files = sorted(set(runtime_deps + extra_files))
1263
1264 # Complain if any file is a directory that's inside the build directory,
1265 # since that makes incremental builds incorrect. See
1266 # https://crbug.com/912946
1267 is_android = 'target_os="android"' in vals['gn_args']
1268 is_cros = ('target_os="chromeos"' in vals['gn_args'] or
1269 vals.get('cros_passthrough', False))
1270 is_mac = self.platform == 'darwin'
Nico Weber0fd016762019-08-25 14:48:141271 is_msan = 'is_msan=true' in vals['gn_args']
1272
1273 err = ''
1274 for f in files:
1275 # Skip a few configs that need extra cleanup for now.
1276 # TODO(https://crbug.com/912946): Fix everything on all platforms and
1277 # enable check everywhere.
Nico Weberd9886b92019-09-10 17:52:171278 if is_android:
Nico Weber0fd016762019-08-25 14:48:141279 break
1280
1281 # Skip a few existing violations that need to be cleaned up. Each of
1282 # these will lead to incorrect incremental builds if their directory
1283 # contents change. Do not add to this list.
1284 # TODO(https://crbug.com/912946): Remove this if statement.
Nico Weber89895822019-08-27 18:59:031285 if ((is_msan and f == 'instrumented_libraries_prebuilt/') or
Clifford Chenge1244822019-08-27 17:26:551286 f == 'mr_extension/' or # https://crbug.com/997947
Nico Weber0fd016762019-08-25 14:48:141287 f.startswith('nacl_test_data/') or
Nico Weber5eee4522019-09-05 23:28:051288 f.startswith('ppapi_nacl_tests_libs/') or
Nico Weberd9886b92019-09-10 17:52:171289 (is_cros and f in ( # https://crbug.com/1002509
1290 'chromevox_test_data/',
1291 'gen/ui/file_manager/file_manager/',
1292 'resources/chromeos/',
Anastasia Helfinstein4bb719552019-11-21 19:02:511293 'resources/chromeos/accessibility/autoclick/',
1294 'resources/chromeos/accessibility/chromevox/',
1295 'resources/chromeos/accessibility/select_to_speak/',
1296 'test_data/chrome/browser/resources/chromeos/accessibility/'
1297 'autoclick/',
1298 'test_data/chrome/browser/resources/chromeos/accessibility/'
1299 'chromevox/',
1300 'test_data/chrome/browser/resources/chromeos/accessibility/'
1301 'select_to_speak/',
Nico Weberd9886b92019-09-10 17:52:171302 )) or
Nico Weber5eee4522019-09-05 23:28:051303 (is_mac and f in ( # https://crbug.com/1000667
Nico Weber5eee4522019-09-05 23:28:051304 'AlertNotificationService.xpc/',
Nico Weber5eee4522019-09-05 23:28:051305 'Chromium Framework.framework/',
1306 'Chromium Helper.app/',
1307 'Chromium.app/',
Nico Weber5eee4522019-09-05 23:28:051308 'Content Shell.app/',
Nico Weber5eee4522019-09-05 23:28:051309 'Google Chrome Framework.framework/',
1310 'Google Chrome Helper (GPU).app/',
Nico Weber5eee4522019-09-05 23:28:051311 'Google Chrome Helper (Plugin).app/',
Nico Weber5eee4522019-09-05 23:28:051312 'Google Chrome Helper (Renderer).app/',
Nico Weber5eee4522019-09-05 23:28:051313 'Google Chrome Helper.app/',
Nico Weber5eee4522019-09-05 23:28:051314 'Google Chrome.app/',
Mila Green0d2ce2a2019-12-06 19:31:221315 'GoogleUpdate.app/',
Nico Weber5eee4522019-09-05 23:28:051316 'blink_deprecated_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051317 'blink_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051318 'corb_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051319 'obj/tools/grit/brotli_mac_asan_workaround/',
1320 'power_saver_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051321 'ppapi_tests.plugin/',
Nico Weber5eee4522019-09-05 23:28:051322 'ui_unittests Framework.framework/',
1323 ))):
Nico Weber0fd016762019-08-25 14:48:141324 continue
1325
Nico Weber24e54f992019-08-26 14:33:321326 # This runs before the build, so we can't use isdir(f). But
Nico Weber0fd016762019-08-25 14:48:141327 # isolate.py luckily requires data directories to end with '/', so we
Nico Weber24e54f992019-08-26 14:33:321328 # can check for that.
Nico Weber57dbc9952019-09-04 13:33:581329 if not f.startswith('../../') and f.endswith('/'):
Nico Weber24e54f992019-08-26 14:33:321330 # Don't use self.PathJoin() -- all involved paths consistently use
1331 # forward slashes, so don't add one single backslash on Windows.
1332 err += '\n' + build_dir + '/' + f
Nico Weber0fd016762019-08-25 14:48:141333
1334 if err:
1335 self.Print('error: gn `data` items may not list generated directories; '
Nico Weber24e54f992019-08-26 14:33:321336 'list files in directory instead for:' + err)
Nico Weber0fd016762019-08-25 14:48:141337 return 1
1338
dpranke751516a2015-10-03 01:11:341339 self.WriteFile(isolate_path,
1340 pprint.pformat({
1341 'variables': {
1342 'command': command,
Nico Weber0fd016762019-08-25 14:48:141343 'files': files,
dpranke751516a2015-10-03 01:11:341344 }
1345 }) + '\n')
1346
1347 self.WriteJSON(
1348 {
1349 'args': [
1350 '--isolated',
1351 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1352 '--isolate',
1353 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1354 ],
1355 'dir': self.chromium_src_dir,
1356 'version': 1,
1357 },
1358 isolate_path + 'd.gen.json',
1359 )
1360
dprankecb4a2e242016-09-19 01:13:141361 def MapTargetsToLabels(self, isolate_map, targets):
1362 labels = []
1363 err = ''
1364
dprankecb4a2e242016-09-19 01:13:141365 for target in targets:
1366 if target == 'all':
1367 labels.append(target)
1368 elif target.startswith('//'):
1369 labels.append(target)
1370 else:
1371 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421372 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141373 err += ('test target "%s" type is unknown\n' % target)
1374 else:
thakis024d6f32017-05-16 23:21:421375 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141376 else:
1377 err += ('target "%s" not found in '
1378 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1379
1380 return err, labels
1381
dprankeeca4a782016-04-14 01:42:381382 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461383 if self.platform == 'linux2':
1384 subdir, exe = 'linux64', 'gn'
1385 elif self.platform == 'darwin':
1386 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251387 elif self.platform == 'aix6':
1388 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461389 else:
1390 subdir, exe = 'win', 'gn.exe'
1391
1392 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081393 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521394
dprankecb4a2e242016-09-19 01:13:141395
Garrett Beatyb6cee042019-04-22 18:42:091396 def GNArgs(self, vals, expand_imports=False):
dpranke73ed0d62016-04-25 19:18:341397 if vals['cros_passthrough']:
1398 if not 'GN_ARGS' in os.environ:
1399 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1400 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161401 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301402 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341403 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351404 if vals['gn_args']:
1405 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341406 else:
1407 gn_args = vals['gn_args']
1408
dpranked0c138b2016-04-13 18:28:471409 if self.args.goma_dir:
1410 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381411
agrieve41d21a72016-04-14 18:02:261412 android_version_code = self.args.android_version_code
1413 if android_version_code:
1414 gn_args += ' android_default_version_code="%s"' % android_version_code
1415
1416 android_version_name = self.args.android_version_name
1417 if android_version_name:
1418 gn_args += ' android_default_version_name="%s"' % android_version_name
1419
Garrett Beatyb6cee042019-04-22 18:42:091420 args_gn_lines = []
1421 parsed_gn_args = {}
dprankeeca4a782016-04-14 01:42:381422
Ben Pastene65ccf6132018-11-08 00:47:591423 # If we're using the Simple Chrome SDK, add a comment at the top that
1424 # points to the doc. This must happen after the gn_helpers.ToGNString()
1425 # call above since gn_helpers strips comments.
1426 if vals['cros_passthrough']:
Garrett Beatyb6cee042019-04-22 18:42:091427 args_gn_lines.extend([
Ben Pastene65ccf6132018-11-08 00:47:591428 '# These args are generated via the Simple Chrome SDK. See the link',
1429 '# below for more details:',
1430 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
Garrett Beatyb6cee042019-04-22 18:42:091431 ])
Ben Pastene65ccf6132018-11-08 00:47:591432
dpranke9dd5e252016-04-14 04:23:091433 args_file = vals.get('args_file', None)
1434 if args_file:
Garrett Beatyb6cee042019-04-22 18:42:091435 if expand_imports:
1436 content = self.ReadFile(self.ToAbsPath(args_file))
1437 parsed_gn_args = gn_helpers.FromGNArgs(content)
1438 else:
1439 args_gn_lines.append('import("%s")' % args_file)
1440
1441 # Canonicalize the arg string into a sorted, newline-separated list
1442 # of key-value pairs, and de-dup the keys if need be so that only
1443 # the last instance of each arg is listed.
1444 parsed_gn_args.update(gn_helpers.FromGNArgs(gn_args))
1445 args_gn_lines.append(gn_helpers.ToGNString(parsed_gn_args))
1446
1447 return '\n'.join(args_gn_lines)
dprankefe4602312015-04-08 16:20:351448
dprankecb4a2e242016-09-19 01:13:141449 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071450 isolate_map = self.ReadIsolateMap()
1451
Scott Graham3be4b4162017-09-12 00:41:411452 is_android = 'target_os="android"' in vals['gn_args']
1453 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Caleb Raittof983d102019-06-21 23:05:021454 is_cros = 'target_os="chromeos"' in vals['gn_args']
Nico Webera7bc1cb2019-06-15 17:42:391455 is_simplechrome = vals.get('cros_passthrough', False)
1456 is_mac = self.platform == 'darwin'
Nico Weberd94b71a2018-02-22 22:00:301457 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061458
kylechar39705682017-01-19 14:37:231459 # This should be true if tests with type='windowed_test_launcher' are
1460 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081461 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1462 # backends. Currently, tests are executed for the headless and X11 backends
1463 # and both can run under Xvfb.
1464 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1465 # backend.
Scott Graham3be4b4162017-09-12 00:41:411466 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251467
1468 asan = 'is_asan=true' in vals['gn_args']
1469 msan = 'is_msan=true' in vals['gn_args']
1470 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411471 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
Yun Liu5764e0dc2019-10-24 01:50:221472 clang_coverage = 'use_clang_coverage=true' in vals['gn_args']
Yun Liuc0f2f732019-09-18 17:06:311473 java_coverage = 'use_jacoco_coverage=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251474
dprankecb4a2e242016-09-19 01:13:141475 test_type = isolate_map[target]['type']
Brian Sheedy234580e52019-09-10 17:42:511476 use_python3 = isolate_map[target].get('use_python3', False)
dprankefe0d35e2016-02-05 02:43:591477
dprankecb4a2e242016-09-19 01:13:141478 executable = isolate_map[target].get('executable', target)
bsheedy9c16ed62019-04-10 20:32:111479 executable_suffix = isolate_map[target].get(
1480 'executable_suffix', '.exe' if is_win else '')
dprankefe0d35e2016-02-05 02:43:591481
Brian Sheedy234580e52019-09-10 17:42:511482 if use_python3:
1483 cmdline = [ 'vpython3' ]
1484 extra_files = [ '../../.vpython3' ]
1485 else:
1486 cmdline = [ 'vpython' ]
1487 extra_files = [ '../../.vpython' ]
1488 extra_files += [
Andrii Shyshkalovc158e0102018-01-10 05:52:001489 '../../testing/test_env.py',
1490 ]
dpranked8113582015-06-05 20:08:251491
dprankecb4a2e242016-09-19 01:13:141492 if test_type == 'nontest':
1493 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1494 output_path=None)
1495
John Budorick93e88ac82019-04-12 18:39:111496 if test_type == 'generated_script':
Ben Pastenecb0fb412019-06-11 02:31:541497 script = isolate_map[target]['script']
1498 if self.platform == 'win32':
1499 script += '.bat'
Brian Sheedy234580e52019-09-10 17:42:511500 cmdline += [
John Budorick93e88ac82019-04-12 18:39:111501 '../../testing/test_env.py',
Ben Pastenecb0fb412019-06-11 02:31:541502 script,
John Budorick93e88ac82019-04-12 18:39:111503 ]
Roberto Carrillo1460da852018-12-14 17:10:391504 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011505 if asan:
John Budorick31cdce62019-04-03 20:56:111506 cmdline += [os.path.join('bin', 'run_with_asan'), '--']
John Budorick8c4203042019-03-19 17:22:011507 cmdline += [
John Budorickfb97a852017-12-20 20:10:191508 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041509 '../../build/android/test_wrapper/logdog_wrapper.py',
1510 '--target', target,
hzl9ae14452017-04-04 23:38:021511 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481512 '--store-tombstones']
Yun Liu5764e0dc2019-10-24 01:50:221513 if clang_coverage or java_coverage:
Yun Liu7cef1072019-06-27 21:22:191514 cmdline += ['--coverage-dir', '${ISOLATED_OUTDIR}']
Scott Graham3be4b4162017-09-12 00:41:411515 elif is_fuchsia and test_type != 'script':
Brian Sheedy234580e52019-09-10 17:42:511516 cmdline += [
John Budorickfb97a852017-12-20 20:10:191517 '../../testing/test_env.py',
1518 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441519 '--test-launcher-bot-mode',
Sergey Ulanovd851243b2019-06-25 00:33:471520 '--system-log-file', '${ISOLATED_OUTDIR}/system_log'
John Budorickfb97a852017-12-20 20:10:191521 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321522 elif is_simplechrome and test_type != 'script':
Brian Sheedy234580e52019-09-10 17:42:511523 cmdline += [
Benjamin Pastene3bce864e2018-04-14 01:16:321524 '../../testing/test_env.py',
1525 os.path.join('bin', 'run_%s' % target),
1526 ]
kylechar39705682017-01-19 14:37:231527 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001528 extra_files.append('../../testing/xvfb.py')
Brian Sheedy234580e52019-09-10 17:42:511529 cmdline += [
Nico Webera7bc1cb2019-06-15 17:42:391530 '../../testing/xvfb.py',
1531 './' + str(executable) + executable_suffix,
1532 '--test-launcher-bot-mode',
1533 '--asan=%d' % asan,
1534 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1535 # supported.
1536 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021537 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1538 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
Nico Webera7bc1cb2019-06-15 17:42:391539 '--msan=%d' % msan,
1540 '--tsan=%d' % tsan,
1541 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471542 ]
1543 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
Brian Sheedy234580e52019-09-10 17:42:511544 cmdline += [
dprankea55584f12015-07-22 00:52:471545 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591546 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251547 '--test-launcher-bot-mode',
1548 '--asan=%d' % asan,
Caleb Raitto1fb2cc9e2019-06-14 01:04:231549 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1550 # supported.
Nico Webera7bc1cb2019-06-15 17:42:391551 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021552 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1553 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
dpranked8113582015-06-05 20:08:251554 '--msan=%d' % msan,
1555 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411556 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471557 ]
dpranke6abd8652015-08-28 03:21:111558 elif test_type == 'script':
Ben Pastene4534c39e2019-07-08 22:55:341559 # If we're testing a CrOS simplechrome build, assume we need to prepare a
1560 # DUT for testing. So prepend the command to run with the test wrapper.
Ben Pastene8ab6954d2018-05-04 04:08:241561 if is_simplechrome:
Ben Pastene908863c2019-07-25 16:20:031562 cmdline = [
1563 os.path.join('bin', 'cros_test_wrapper'),
1564 '--logs-dir=${ISOLATED_OUTDIR}',
1565 ]
Ben Pastene8ab6954d2018-05-04 04:08:241566 cmdline += [
dpranke6abd8652015-08-28 03:21:111567 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141568 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591569 ]
Dirk Prankef24e6b22018-03-27 20:12:301570 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471571 cmdline = [
1572 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591573 ]
dprankea55584f12015-07-22 00:52:471574 else:
1575 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1576 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251577
dprankecb4a2e242016-09-19 01:13:141578 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591579
dpranked8113582015-06-05 20:08:251580 return cmdline, extra_files
1581
dpranke74559b52015-06-10 21:20:391582 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331583 return self.PathJoin(self.chromium_src_dir,
1584 self.ToSrcRelPath(build_path),
1585 *comps)
dpranked8113582015-06-05 20:08:251586
dprankeee5b51f62015-04-09 00:03:221587 def ToSrcRelPath(self, path):
1588 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501589 if path.startswith('//'):
1590 return path[2:].replace('/', self.sep)
1591 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351592
Dirk Pranke0fd41bcd2015-06-19 00:05:501593 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141594 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501595 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001596 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501597 if ret:
1598 return ret
1599
Dirk Prankef24e6b22018-03-27 20:12:301600 build_path = self.args.path
1601 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141602 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301603 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141604 gn_output_path = output_path + '.gn'
1605
dpranke7837fc362015-11-19 03:54:161606 inp = self.ReadInputJSON(['files', 'test_targets',
1607 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321608 if self.args.verbose:
1609 self.Print()
1610 self.Print('analyze input:')
1611 self.PrintJSON(inp)
1612 self.Print()
1613
dpranke76734662015-04-16 02:17:501614
dpranke7c5f614d2015-07-22 23:43:391615 # This shouldn't normally happen, but could due to unusual race conditions,
1616 # like a try job that gets scheduled before a patch lands but runs after
1617 # the patch has landed.
1618 if not inp['files']:
1619 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161620 self.WriteJSON({
1621 'status': 'No dependency',
1622 'compile_targets': [],
1623 'test_targets': [],
1624 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391625 return 0
1626
dprankecb4a2e242016-09-19 01:13:141627 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161628 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561629
dprankecb4a2e242016-09-19 01:13:141630 isolate_map = self.ReadIsolateMap()
1631 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1632 isolate_map, inp['additional_compile_targets'])
1633 if err:
1634 raise MBErr(err)
1635
1636 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1637 isolate_map, inp['test_targets'])
1638 if err:
1639 raise MBErr(err)
1640 labels_to_targets = {}
1641 for i, label in enumerate(gn_inp['test_targets']):
1642 labels_to_targets[label] = inp['test_targets'][i]
1643
dprankef61de2f2015-05-14 04:09:561644 try:
dprankecb4a2e242016-09-19 01:13:141645 self.WriteJSON(gn_inp, gn_input_path)
1646 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
Debrian Figueroaae51d0d2019-07-22 18:04:111647 ret, output, _ = self.Run(cmd, force_verbose=True)
dprankecb4a2e242016-09-19 01:13:141648 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:111649 if self.args.json_output:
1650 # write errors to json.output
1651 self.WriteJSON({'output': output}, self.args.json_output)
dprankecb4a2e242016-09-19 01:13:141652 return ret
dpranke067d0142015-05-14 22:52:451653
dprankecb4a2e242016-09-19 01:13:141654 gn_outp_str = self.ReadFile(gn_output_path)
1655 try:
1656 gn_outp = json.loads(gn_outp_str)
1657 except Exception as e:
1658 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1659 % (repr(gn_outp_str), str(e)))
1660 raise
1661
1662 outp = {}
1663 if 'status' in gn_outp:
1664 outp['status'] = gn_outp['status']
1665 if 'error' in gn_outp:
1666 outp['error'] = gn_outp['error']
1667 if 'invalid_targets' in gn_outp:
1668 outp['invalid_targets'] = gn_outp['invalid_targets']
1669 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491670 all_input_compile_targets = sorted(
1671 set(inp['test_targets'] + inp['additional_compile_targets']))
1672
1673 # If we're building 'all', we can throw away the rest of the targets
1674 # since they're redundant.
dpranke385a3102016-09-20 22:04:081675 if 'all' in gn_outp['compile_targets']:
1676 outp['compile_targets'] = ['all']
1677 else:
Dirk Pranke45165072017-11-08 04:57:491678 outp['compile_targets'] = gn_outp['compile_targets']
1679
1680 # crbug.com/736215: When GN returns targets back, for targets in
1681 # the default toolchain, GN will have generated a phony ninja
1682 # target matching the label, and so we can safely (and easily)
1683 # transform any GN label into the matching ninja target. For
1684 # targets in other toolchains, though, GN doesn't generate the
1685 # phony targets, and we don't know how to turn the labels into
1686 # compile targets. In this case, we also conservatively give up
1687 # and build everything. Probably the right thing to do here is
1688 # to have GN return the compile targets directly.
1689 if any("(" in target for target in outp['compile_targets']):
1690 self.Print('WARNING: targets with non-default toolchains were '
1691 'found, building everything instead.')
1692 outp['compile_targets'] = all_input_compile_targets
1693 else:
dpranke385a3102016-09-20 22:04:081694 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491695 label.replace('//', '') for label in outp['compile_targets']]
1696
1697 # Windows has a maximum command line length of 8k; even Linux
1698 # maxes out at 128k; if analyze returns a *really long* list of
1699 # targets, we just give up and conservatively build everything instead.
1700 # Probably the right thing here is for ninja to support response
1701 # files as input on the command line
1702 # (see https://github.com/ninja-build/ninja/issues/1355).
1703 if len(' '.join(outp['compile_targets'])) > 7*1024:
1704 self.Print('WARNING: Too many compile targets were affected.')
1705 self.Print('WARNING: Building everything instead to avoid '
1706 'command-line length issues.')
1707 outp['compile_targets'] = all_input_compile_targets
1708
1709
dprankecb4a2e242016-09-19 01:13:141710 if 'test_targets' in gn_outp:
1711 outp['test_targets'] = [
1712 labels_to_targets[label] for label in gn_outp['test_targets']]
1713
1714 if self.args.verbose:
1715 self.Print()
1716 self.Print('analyze output:')
1717 self.PrintJSON(outp)
1718 self.Print()
1719
1720 self.WriteJSON(outp, output_path)
1721
dprankef61de2f2015-05-14 04:09:561722 finally:
dprankecb4a2e242016-09-19 01:13:141723 if self.Exists(gn_input_path):
1724 self.RemoveFile(gn_input_path)
1725 if self.Exists(gn_output_path):
1726 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351727
1728 return 0
1729
dpranked8113582015-06-05 20:08:251730 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301731 path = self.args.input_path
1732 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351733 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321734 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351735
1736 try:
1737 inp = json.loads(self.ReadFile(path))
1738 except Exception as e:
1739 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321740 (path, e), output_path)
dpranked8113582015-06-05 20:08:251741
1742 for k in required_keys:
1743 if not k in inp:
1744 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1745 output_path)
dprankefe4602312015-04-08 16:20:351746
1747 return inp
1748
dpranked5b2b9432015-06-23 16:55:301749 def WriteFailureAndRaise(self, msg, output_path):
1750 if output_path:
dprankee0547cd2015-09-15 01:27:401751 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351752 raise MBErr(msg)
1753
dprankee0547cd2015-09-15 01:27:401754 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321755 try:
dprankee0547cd2015-09-15 01:27:401756 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1757 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321758 except Exception as e:
1759 raise MBErr('Error %s writing to the output path "%s"' %
1760 (e, path))
dprankefe4602312015-04-08 16:20:351761
aneeshmde50f472016-04-01 01:13:101762
dpranke3cec199c2015-09-22 23:29:021763 def PrintCmd(self, cmd, env):
1764 if self.platform == 'win32':
1765 env_prefix = 'set '
1766 env_quoter = QuoteForSet
1767 shell_quoter = QuoteForCmd
1768 else:
1769 env_prefix = ''
1770 env_quoter = pipes.quote
1771 shell_quoter = pipes.quote
1772
1773 def print_env(var):
1774 if env and var in env:
1775 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1776
dprankeec079262016-06-07 02:21:201777 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021778
dpranke8c2cfd32015-09-17 20:12:331779 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351780 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021781 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351782
dprankecda00332015-04-11 04:18:321783 def PrintJSON(self, obj):
1784 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1785
dpranke751516a2015-10-03 01:11:341786 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301787 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381788 if self.platform == 'win32':
1789 # On Windows use the batch script since there is no exe
1790 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1791 else:
1792 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341793 if self.args.jobs:
1794 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1795 ninja_cmd.append(target)
Dirk Pranke5f22a822019-05-23 22:55:251796 ret, _, _ = self.Run(ninja_cmd, buffer_output=False)
dpranke751516a2015-10-03 01:11:341797 return ret
1798
Stephen Martinis1f134492019-12-06 23:27:411799 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351800 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401801 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021802 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351803 if self.args.dryrun:
1804 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401805
Stephen Martinis1f134492019-12-06 23:27:411806 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401807 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341808 if ret:
1809 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351810 if out:
Debrian Figueroaae582232019-07-17 01:54:451811 # This is the error seen on the logs
dprankeee5b51f62015-04-09 00:03:221812 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351813 if err:
dprankeee5b51f62015-04-09 00:03:221814 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351815 return ret, out, err
1816
Stephen Martiniscd377012019-10-18 17:40:461817 def Call(self, cmd, env=None, buffer_output=True, stdin=None):
dpranke751516a2015-10-03 01:11:341818 if buffer_output:
1819 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1820 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
Stephen Martiniscd377012019-10-18 17:40:461821 env=env, stdin=subprocess.PIPE)
1822 out, err = p.communicate(input=stdin)
dpranke751516a2015-10-03 01:11:341823 else:
1824 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1825 env=env)
1826 p.wait()
1827 out = err = ''
dprankefe4602312015-04-08 16:20:351828 return p.returncode, out, err
1829
1830 def ExpandUser(self, path):
1831 # This function largely exists so it can be overridden for testing.
1832 return os.path.expanduser(path)
1833
1834 def Exists(self, path):
1835 # This function largely exists so it can be overridden for testing.
1836 return os.path.exists(path)
1837
dpranke867bcf4a2016-03-14 22:28:321838 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501839 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321840 f = urllib2.urlopen(url)
1841 contents = f.read()
1842 f.close()
1843 return contents
1844
dprankec3441d12015-06-23 23:01:351845 def MaybeMakeDirectory(self, path):
1846 try:
1847 os.makedirs(path)
1848 except OSError, e:
1849 if e.errno != errno.EEXIST:
1850 raise
1851
dpranke8c2cfd32015-09-17 20:12:331852 def PathJoin(self, *comps):
1853 # This function largely exists so it can be overriden for testing.
1854 return os.path.join(*comps)
1855
dpranke030d7a6d2016-03-26 17:23:501856 def Print(self, *args, **kwargs):
1857 # This function largely exists so it can be overridden for testing.
1858 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101859 if kwargs.get('stream', sys.stdout) == sys.stdout:
1860 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501861
dprankefe4602312015-04-08 16:20:351862 def ReadFile(self, path):
1863 # This function largely exists so it can be overriden for testing.
1864 with open(path) as fp:
1865 return fp.read()
1866
dpranke030d7a6d2016-03-26 17:23:501867 def RelPath(self, path, start='.'):
1868 # This function largely exists so it can be overriden for testing.
1869 return os.path.relpath(path, start)
1870
dprankef61de2f2015-05-14 04:09:561871 def RemoveFile(self, path):
1872 # This function largely exists so it can be overriden for testing.
1873 os.remove(path)
1874
dprankec161aa92015-09-14 20:21:131875 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331876 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131877 # In other places in chromium, we often have to retry this command
1878 # because we're worried about other processes still holding on to
1879 # file handles, but when MB is invoked, it will be early enough in the
1880 # build that their should be no other processes to interfere. We
1881 # can change this if need be.
1882 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1883 else:
1884 shutil.rmtree(abs_path, ignore_errors=True)
1885
Dirk Prankef24e6b22018-03-27 20:12:301886 def TempDir(self):
1887 # This function largely exists so it can be overriden for testing.
1888 return tempfile.mkdtemp(prefix='mb_')
1889
dprankef61de2f2015-05-14 04:09:561890 def TempFile(self, mode='w'):
1891 # This function largely exists so it can be overriden for testing.
1892 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1893
dprankee0547cd2015-09-15 01:27:401894 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351895 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401896 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301897 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351898 with open(path, 'w') as fp:
1899 return fp.write(contents)
1900
dprankef61de2f2015-05-14 04:09:561901
Stephen Martiniscd377012019-10-18 17:40:461902class LedResult(object):
1903 """Holds the result of a led operation. Can be chained using |then|."""
1904
1905 def __init__(self, result, run_cmd):
1906 self._result = result
1907 self._run_cmd = run_cmd
1908
1909 @property
1910 def result(self):
1911 """The mutable result data of the previous led call as decoded JSON."""
1912 return self._result
1913
1914 def then(self, *cmd):
1915 """Invoke led, passing it the current `result` data as input.
1916
1917 Returns another LedResult object with the output of the command.
1918 """
1919 return self.__class__(
1920 self._run_cmd(self._result, cmd), self._run_cmd)
1921
1922
Greg Guterman1492aeb42020-01-14 22:59:141923def FlattenConfig(config_pool, mixin_pool, config):
1924 mixins = config_pool[config]
1925 vals = DefaultVals()
1926
1927 visited = []
1928 FlattenMixins(mixin_pool, mixins, vals, visited)
1929 return vals
1930
1931
1932def FlattenMixins(mixin_pool, mixins_to_flatten, vals, visited):
1933 for m in mixins_to_flatten:
1934 if m not in mixin_pool:
1935 raise MBErr('Unknown mixin "%s"' % m)
1936
1937 visited.append(m)
1938
1939 mixin_vals = mixin_pool[m]
1940
1941 if 'cros_passthrough' in mixin_vals:
1942 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
1943 if 'args_file' in mixin_vals:
1944 if vals['args_file']:
1945 raise MBErr('args_file specified multiple times in mixins '
1946 'for mixin %s' % m)
1947 vals['args_file'] = mixin_vals['args_file']
1948 if 'gn_args' in mixin_vals:
1949 if vals['gn_args']:
1950 vals['gn_args'] += ' ' + mixin_vals['gn_args']
1951 else:
1952 vals['gn_args'] = mixin_vals['gn_args']
1953
1954 if 'mixins' in mixin_vals:
1955 FlattenMixins(mixin_pool, mixin_vals['mixins'], vals, visited)
1956 return vals
1957
1958
Stephen Martiniscd377012019-10-18 17:40:461959
dprankefe4602312015-04-08 16:20:351960class MBErr(Exception):
1961 pass
1962
1963
dpranke3cec199c2015-09-22 23:29:021964# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
1965# details of this next section, which handles escaping command lines
1966# so that they can be copied and pasted into a cmd window.
1967UNSAFE_FOR_SET = set('^<>&|')
1968UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
1969ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
1970
1971
1972def QuoteForSet(arg):
1973 if any(a in UNSAFE_FOR_SET for a in arg):
1974 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
1975 return arg
1976
1977
1978def QuoteForCmd(arg):
1979 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:021980 if arg == '' or ' ' in arg or '"' in arg:
1981 quote_re = re.compile(r'(\\*)"')
1982 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
1983
1984 # Then check to see if the arg contains any metacharacters other than
1985 # double quotes; if it does, quote everything (including the double
1986 # quotes) for safety.
1987 if any(a in UNSAFE_FOR_CMD for a in arg):
1988 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
1989 return arg
1990
1991
dprankefe4602312015-04-08 16:20:351992if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:591993 sys.exit(main(sys.argv[1:]))