blob: 0c1c5029cba93ce974b50254ca9b84f914651271 [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 Iannuccid29a527e2020-02-26 02:05:19585 def _AddBaseSoftware(self, cmd):
Robert Iannucci5a9d75f62018-03-02 05:28:20586 # HACK(iannucci): These packages SHOULD NOT BE HERE.
587 # Remove method once Swarming Pool Task Templates are implemented.
588 # crbug.com/812428
589
Robert Iannuccid29a527e2020-02-26 02:05:19590 # Read vpython version from pinned depot_tools manifest. Better than
591 # duplicating the pin here. The pin file format is simple enough to parse
592 # it inline here.
593 manifest_path = self.PathJoin(self.chromium_src_dir, 'third_party',
594 'depot_tools', 'cipd_manifest.txt')
595 vpython_pkg = vpython_version = None
596 for line in self.ReadFile(manifest_path).splitlines():
597 # lines look like:
598 # name/of/package version
599 if 'vpython' in line and 'git_revision' in line:
600 vpython_pkg, vpython_version = line.split()
601 break
602 if vpython_pkg is None:
603 raise ValueError('unable to read vpython pin from %s' % (manifest_path, ))
604
Robert Iannucci5a9d75f62018-03-02 05:28:20605 # Add in required base software. This should be kept in sync with the
John Budorick9d9175372019-04-01 19:04:24606 # `chromium_swarming` recipe module in build.git. All references to
607 # `swarming_module` below are purely due to this.
Robert Iannucci5a9d75f62018-03-02 05:28:20608 cipd_packages = [
609 ('infra/python/cpython/${platform}',
smut22dcd68e2019-06-25 23:33:27610 'version:2.7.15.chromium14'),
Robert Iannucci5a9d75f62018-03-02 05:28:20611 ('infra/tools/luci/logdog/butler/${platform}',
612 'git_revision:e1abc57be62d198b5c2f487bfb2fa2d2eb0e867c'),
Robert Iannucci5a9d75f62018-03-02 05:28:20613 ]
Robert Iannuccid29a527e2020-02-26 02:05:19614 cipd_packages.append((vpython_pkg, vpython_version))
615 cipd_packages.append((vpython_pkg.replace('vpython', 'vpython-native'),
616 vpython_version))
617
Robert Iannucci5a9d75f62018-03-02 05:28:20618 for pkg, vers in cipd_packages:
619 cmd.append('--cipd-package=.swarming_module:%s:%s' % (pkg, vers))
620
621 # Add packages to $PATH
622 cmd.extend([
623 '--env-prefix=PATH', '.swarming_module',
624 '--env-prefix=PATH', '.swarming_module/bin',
625 ])
626
627 # Add cache directives for vpython.
628 vpython_cache_path = '.swarming_module_cache/vpython'
629 cmd.extend([
630 '--named-cache=swarming_module_cache_vpython', vpython_cache_path,
631 '--env-prefix=VPYTHON_VIRTUALENV_ROOT', vpython_cache_path,
632 ])
633
Dirk Pranke8cb6aa782017-12-16 02:31:33634 def _RunUnderSwarming(self, build_dir, target):
Marc-Antoine Ruel559cc4732019-03-19 22:20:46635 isolate_server = 'isolateserver.appspot.com'
636 namespace = 'default-gzip'
637 swarming_server = 'chromium-swarm.appspot.com'
Dirk Pranke8cb6aa782017-12-16 02:31:33638 # TODO(dpranke): Look up the information for the target in
639 # the //testing/buildbot.json file, if possible, so that we
640 # can determine the isolate target, command line, and additional
641 # swarming parameters, if possible.
642 #
643 # TODO(dpranke): Also, add support for sharding and merging results.
644 dimensions = []
645 for k, v in self._DefaultDimensions() + self.args.dimensions:
646 dimensions += ['-d', k, v]
647
648 cmd = [
649 self.executable,
650 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
651 'archive',
Marc-Antoine Ruel559cc4732019-03-19 22:20:46652 '-s', self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
653 '-I', isolate_server,
654 '--namespace', namespace,
Dirk Pranke8cb6aa782017-12-16 02:31:33655 ]
Dirk Pranke5f22a822019-05-23 22:55:25656
657 # Talking to the isolateserver may fail because we're not logged in.
658 # We trap the command explicitly and rewrite the error output so that
659 # the error message is actually correct for a Chromium check out.
660 self.PrintCmd(cmd, env=None)
661 ret, out, err = self.Run(cmd, force_verbose=False)
Dirk Pranke8cb6aa782017-12-16 02:31:33662 if ret:
Dirk Pranke5f22a822019-05-23 22:55:25663 self.Print(' -> returned %d' % ret)
664 if out:
665 self.Print(out, end='')
666 if err:
667 # The swarming client will return an exit code of 2 (via
668 # argparse.ArgumentParser.error()) and print a message to indicate
669 # that auth failed, so we have to parse the message to check.
670 if (ret == 2 and 'Please login to' in err):
671 err = err.replace(' auth.py', ' tools/swarming_client/auth.py')
672 self.Print(err, end='', file=sys.stderr)
673
Dirk Pranke8cb6aa782017-12-16 02:31:33674 return ret
675
676 isolated_hash = out.splitlines()[0].split()[0]
677 cmd = [
678 self.executable,
679 self.PathJoin('tools', 'swarming_client', 'swarming.py'),
680 'run',
681 '-s', isolated_hash,
Marc-Antoine Ruel559cc4732019-03-19 22:20:46682 '-I', isolate_server,
683 '--namespace', namespace,
684 '-S', swarming_server,
Stephen Martinis43ab3032019-09-11 20:07:41685 '--tags=purpose:user-debug-mb',
Dirk Pranke8cb6aa782017-12-16 02:31:33686 ] + dimensions
Robert Iannucci5a9d75f62018-03-02 05:28:20687 self._AddBaseSoftware(cmd)
Dirk Pranke8cb6aa782017-12-16 02:31:33688 if self.args.extra_args:
689 cmd += ['--'] + self.args.extra_args
Dirk Pranke5f22a822019-05-23 22:55:25690 self.Print('')
Dirk Pranke8cb6aa782017-12-16 02:31:33691 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
692 return ret
693
694 def _RunLocallyIsolated(self, build_dir, target):
dpranke030d7a6d2016-03-26 17:23:50695 cmd = [
dpranke751516a2015-10-03 01:11:34696 self.executable,
697 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
698 'run',
699 '-s',
dpranke030d7a6d2016-03-26 17:23:50700 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
Dirk Pranke8cb6aa782017-12-16 02:31:33701 ]
dpranke030d7a6d2016-03-26 17:23:50702 if self.args.extra_args:
Dirk Pranke8cb6aa782017-12-16 02:31:33703 cmd += ['--'] + self.args.extra_args
704 ret, _, _ = self.Run(cmd, force_verbose=True, buffer_output=False)
dpranke751516a2015-10-03 01:11:34705 return ret
706
Dirk Pranke8cb6aa782017-12-16 02:31:33707 def _DefaultDimensions(self):
708 if not self.args.default_dimensions:
709 return []
710
711 # This code is naive and just picks reasonable defaults per platform.
712 if self.platform == 'darwin':
Mike Meaded12fd0f2018-04-10 01:02:40713 os_dim = ('os', 'Mac-10.13')
Dirk Pranke8cb6aa782017-12-16 02:31:33714 elif self.platform.startswith('linux'):
Takuto Ikuta169663b2019-08-05 16:21:32715 os_dim = ('os', 'Ubuntu-16.04')
Dirk Pranke8cb6aa782017-12-16 02:31:33716 elif self.platform == 'win32':
Mike Meaded12fd0f2018-04-10 01:02:40717 os_dim = ('os', 'Windows-10')
Dirk Pranke8cb6aa782017-12-16 02:31:33718 else:
719 raise MBErr('unrecognized platform string "%s"' % self.platform)
720
John Budorick9cf2d4c62019-11-11 23:56:12721 return [('pool', 'chromium.tests'),
Dirk Pranke8cb6aa782017-12-16 02:31:33722 ('cpu', 'x86-64'),
723 os_dim]
724
Greg Guterman1492aeb42020-01-14 22:59:14725 def CmdValidateBucket(self, print_ok=True):
726 errs = []
727
728 # Build a list of all of the configs referenced by builders.
729 all_configs = validation.GetAllConfigsBucket(self.buckets)
730
731 # Check that every referenced args file or config actually exists.
732 for config, loc in all_configs.items():
733 if config.startswith('//'):
734 if not self.Exists(self.ToAbsPath(config)):
735 errs.append(
736 'Unknown args file "%s" referenced from "%s".' % (config, loc))
737 elif not config in self.configs:
738 errs.append('Unknown config "%s" referenced from "%s".' % (config, loc))
739
740 # Check that every config and mixin is referenced.
741 validation.CheckAllConfigsAndMixinsReferenced(errs, all_configs,
742 self.configs, self.mixins)
743
744 validation.EnsureNoProprietaryMixinsBucket(
745 errs, self.default_config_bucket, self.args.config_file,
746 self.public_artifact_builders, self.buckets, self.configs, self.mixins)
747
748 validation.CheckDuplicateConfigs(errs, self.configs, self.mixins,
749 self.buckets, FlattenConfig)
750
751 if errs:
752 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
753 '\n ' + '\n '.join(errs))
754
755 if print_ok:
756 self.Print('mb config file %s looks ok.' % self.args.config_file)
757 return 0
758
dpranke0cafc162016-03-19 00:41:10759 def CmdValidate(self, print_ok=True):
dprankefe4602312015-04-08 16:20:35760 errs = []
761
Greg Guterman1492aeb42020-01-14 22:59:14762 # Validate both bucket and master configs if
763 # a specific one isn't specified
764 if getattr(self.args, 'config_file', None) is None:
765 # Read the file to make sure it parses.
766 self.args.config_file = self.default_config_bucket
767 self.ReadConfigFile()
768 self.CmdValidateBucket()
769
770 self.args.config_file = self.default_config_master
771 self.ReadConfigFile()
772 else:
773 self.ReadConfigFile()
774 if self.group_by_bucket:
775 return self.CmdValidateBucket()
dprankefe4602312015-04-08 16:20:35776
dpranke3be00142016-03-17 22:46:04777 # Build a list of all of the configs referenced by builders.
Greg Guterman1492aeb42020-01-14 22:59:14778 all_configs = validation.GetAllConfigsMaster(self.masters)
dprankefe4602312015-04-08 16:20:35779
dpranke9dd5e252016-04-14 04:23:09780 # Check that every referenced args file or config actually exists.
dprankefe4602312015-04-08 16:20:35781 for config, loc in all_configs.items():
dpranke9dd5e252016-04-14 04:23:09782 if config.startswith('//'):
783 if not self.Exists(self.ToAbsPath(config)):
784 errs.append('Unknown args file "%s" referenced from "%s".' %
785 (config, loc))
786 elif not config in self.configs:
dprankefe4602312015-04-08 16:20:35787 errs.append('Unknown config "%s" referenced from "%s".' %
788 (config, loc))
789
Greg Guterman1492aeb42020-01-14 22:59:14790 # Check that every config and mixin is referenced.
791 validation.CheckAllConfigsAndMixinsReferenced(errs, all_configs,
792 self.configs, self.mixins)
dprankefe4602312015-04-08 16:20:35793
Greg Guterman1492aeb42020-01-14 22:59:14794 validation.EnsureNoProprietaryMixinsMaster(
795 errs, self.default_config_master, self.args.config_file, self.masters,
796 self.configs, self.mixins)
dprankefe4602312015-04-08 16:20:35797
Greg Guterman1492aeb42020-01-14 22:59:14798 validation.CheckDuplicateConfigs(errs, self.configs, self.mixins,
799 self.masters, FlattenConfig)
dprankefe4602312015-04-08 16:20:35800
dprankefe4602312015-04-08 16:20:35801 if errs:
dpranke4323c80632015-08-10 22:53:54802 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
dprankea33267872015-08-12 15:45:17803 '\n ' + '\n '.join(errs))
dprankefe4602312015-04-08 16:20:35804
dpranke0cafc162016-03-19 00:41:10805 if print_ok:
806 self.Print('mb config file %s looks ok.' % self.args.config_file)
dprankefe4602312015-04-08 16:20:35807 return 0
808
809 def GetConfig(self):
Dirk Prankef24e6b22018-03-27 20:12:30810 build_dir = self.args.path
dpranke751516a2015-10-03 01:11:34811
Greg Guterman1492aeb42020-01-14 22:59:14812 vals = DefaultVals()
dpranke751516a2015-10-03 01:11:34813 if self.args.builder or self.args.master or self.args.config:
814 vals = self.Lookup()
Dirk Pranked181a1a2017-12-14 01:47:11815 # Re-run gn gen in order to ensure the config is consistent with the
816 # build dir.
817 self.RunGNGen(vals)
dpranke751516a2015-10-03 01:11:34818 return vals
819
Dirk Pranked181a1a2017-12-14 01:47:11820 toolchain_path = self.PathJoin(self.ToAbsPath(build_dir),
821 'toolchain.ninja')
822 if not self.Exists(toolchain_path):
823 self.Print('Must either specify a path to an existing GN build dir '
824 'or pass in a -m/-b pair or a -c flag to specify the '
825 'configuration')
826 return {}
dpranke751516a2015-10-03 01:11:34827
Dirk Pranked181a1a2017-12-14 01:47:11828 vals['gn_args'] = self.GNArgsFromDir(build_dir)
dpranke751516a2015-10-03 01:11:34829 return vals
830
dprankef37aebb92016-09-23 01:14:49831 def GNArgsFromDir(self, build_dir):
brucedawsonecc0c1cd2016-06-02 18:24:58832 args_contents = ""
833 gn_args_path = self.PathJoin(self.ToAbsPath(build_dir), 'args.gn')
834 if self.Exists(gn_args_path):
835 args_contents = self.ReadFile(gn_args_path)
dpranke751516a2015-10-03 01:11:34836 gn_args = []
837 for l in args_contents.splitlines():
Reid Klecknerfca22812020-02-04 02:28:07838 l = l.split('#', 2)[0].strip()
839 if not l:
840 continue
dpranke751516a2015-10-03 01:11:34841 fields = l.split(' ')
842 name = fields[0]
843 val = ' '.join(fields[2:])
844 gn_args.append('%s=%s' % (name, val))
845
dprankef37aebb92016-09-23 01:14:49846 return ' '.join(gn_args)
dpranke751516a2015-10-03 01:11:34847
848 def Lookup(self):
Erik Chen238f4ac2019-04-12 19:02:50849 vals = self.ReadIOSBotConfig()
850 if not vals:
851 self.ReadConfigFile()
Greg Guterman1492aeb42020-01-14 22:59:14852 if self.group_by_bucket:
853 config = self.ConfigFromArgsBucket()
854 else:
855 config = self.ConfigFromArgs()
Erik Chen238f4ac2019-04-12 19:02:50856 if config.startswith('//'):
857 if not self.Exists(self.ToAbsPath(config)):
858 raise MBErr('args file "%s" not found' % config)
Greg Guterman1492aeb42020-01-14 22:59:14859 vals = DefaultVals()
Erik Chen238f4ac2019-04-12 19:02:50860 vals['args_file'] = config
861 else:
862 if not config in self.configs:
863 raise MBErr('Config "%s" not found in %s' %
864 (config, self.args.config_file))
Greg Guterman1492aeb42020-01-14 22:59:14865 vals = FlattenConfig(self.configs, self.mixins, config)
Erik Chen238f4ac2019-04-12 19:02:50866 return vals
867
868 def ReadIOSBotConfig(self):
869 if not self.args.master or not self.args.builder:
870 return {}
871 path = self.PathJoin(self.chromium_src_dir, 'ios', 'build', 'bots',
872 self.args.master, self.args.builder + '.json')
873 if not self.Exists(path):
874 return {}
875
876 contents = json.loads(self.ReadFile(path))
877 gn_args = ' '.join(contents.get('gn_args', []))
878
Greg Guterman1492aeb42020-01-14 22:59:14879 vals = DefaultVals()
Erik Chen238f4ac2019-04-12 19:02:50880 vals['gn_args'] = gn_args
dprankef37aebb92016-09-23 01:14:49881 return vals
dprankee0f486f2015-11-19 23:42:00882
dprankefe4602312015-04-08 16:20:35883 def ReadConfigFile(self):
884 if not self.Exists(self.args.config_file):
885 raise MBErr('config file not found at %s' % self.args.config_file)
886
887 try:
888 contents = ast.literal_eval(self.ReadFile(self.args.config_file))
889 except SyntaxError as e:
890 raise MBErr('Failed to parse config file "%s": %s' %
891 (self.args.config_file, e))
892
dprankefe4602312015-04-08 16:20:35893 self.configs = contents['configs']
dprankefe4602312015-04-08 16:20:35894 self.mixins = contents['mixins']
Greg Guterman1492aeb42020-01-14 22:59:14895 self.masters = contents.get('masters')
896 self.buckets = contents.get('buckets')
897 self.public_artifact_builders = contents.get('public_artifact_builders')
898
899 self.group_by_bucket = bool(self.buckets)
dprankefe4602312015-04-08 16:20:35900
dprankecb4a2e242016-09-19 01:13:14901 def ReadIsolateMap(self):
Zhiling Huang66958462018-02-03 00:28:20902 if not self.args.isolate_map_files:
903 self.args.isolate_map_files = [self.default_isolate_map]
904
905 for f in self.args.isolate_map_files:
906 if not self.Exists(f):
907 raise MBErr('isolate map file not found at %s' % f)
908 isolate_maps = {}
909 for isolate_map in self.args.isolate_map_files:
910 try:
911 isolate_map = ast.literal_eval(self.ReadFile(isolate_map))
912 duplicates = set(isolate_map).intersection(isolate_maps)
913 if duplicates:
914 raise MBErr(
915 'Duplicate targets in isolate map files: %s.' %
916 ', '.join(duplicates))
917 isolate_maps.update(isolate_map)
918 except SyntaxError as e:
919 raise MBErr(
920 'Failed to parse isolate map file "%s": %s' % (isolate_map, e))
921 return isolate_maps
dprankecb4a2e242016-09-19 01:13:14922
Greg Guterman1492aeb42020-01-14 22:59:14923 def ConfigFromArgsBucket(self):
924 if self.args.config:
925 if self.args.bucket or self.args.builder:
926 raise MBErr('Can not specify both -c/--config and -u/--bucket or '
927 '-b/--builder')
928
929 return self.args.config
930
931 if not self.args.bucket or not self.args.builder:
932 raise MBErr('Must specify either -c/--config or '
933 '(-u/--bucket and -b/--builder)')
934
935 if not self.args.bucket in self.buckets:
936 raise MBErr('Bucket name "%s" not found in "%s"' %
937 (self.args.bucket, self.args.config_file))
938
939 if not self.args.builder in self.buckets[self.args.bucket]:
940 raise MBErr('Builder name "%s" not found under buckets[%s] in "%s"' %
941 (self.args.builder, self.args.bucket, self.args.config_file))
942
943 config = self.buckets[self.args.bucket][self.args.builder]
944 if isinstance(config, dict):
945 if self.args.phase is None:
946 raise MBErr('Must specify a build --phase for %s on %s' %
947 (self.args.builder, self.args.bucket))
948 phase = str(self.args.phase)
949 if phase not in config:
950 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
951 (phase, self.args.builder, self.args.bucket))
952 return config[phase]
953
954 if self.args.phase is not None:
955 raise MBErr('Must not specify a build --phase for %s on %s' %
956 (self.args.builder, self.args.bucket))
957 return config
958
dprankefe4602312015-04-08 16:20:35959 def ConfigFromArgs(self):
Greg Guterman1492aeb42020-01-14 22:59:14960 ''' Deprecated in favor ConfigFromArgsBucket '''
dprankefe4602312015-04-08 16:20:35961 if self.args.config:
962 if self.args.master or self.args.builder:
963 raise MBErr('Can not specific both -c/--config and -m/--master or '
964 '-b/--builder')
965
966 return self.args.config
967
968 if not self.args.master or not self.args.builder:
969 raise MBErr('Must specify either -c/--config or '
970 '(-m/--master and -b/--builder)')
971
972 if not self.args.master in self.masters:
973 raise MBErr('Master name "%s" not found in "%s"' %
974 (self.args.master, self.args.config_file))
975
976 if not self.args.builder in self.masters[self.args.master]:
977 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
978 (self.args.builder, self.args.master, self.args.config_file))
979
dprankeb9380a12016-07-21 21:44:09980 config = self.masters[self.args.master][self.args.builder]
shenghuazhang804b21542016-10-11 02:06:49981 if isinstance(config, dict):
dprankeb9380a12016-07-21 21:44:09982 if self.args.phase is None:
983 raise MBErr('Must specify a build --phase for %s on %s' %
984 (self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49985 phase = str(self.args.phase)
986 if phase not in config:
987 raise MBErr('Phase %s doesn\'t exist for %s on %s' %
dprankeb9380a12016-07-21 21:44:09988 (phase, self.args.builder, self.args.master))
shenghuazhang804b21542016-10-11 02:06:49989 return config[phase]
dprankeb9380a12016-07-21 21:44:09990
991 if self.args.phase is not None:
992 raise MBErr('Must not specify a build --phase for %s on %s' %
993 (self.args.builder, self.args.master))
994 return config
dprankefe4602312015-04-08 16:20:35995
Takuto Ikuta9dffd7e2018-09-05 01:04:00996 def RunGNGen(self, vals, compute_inputs_for_analyze=False, check=True):
Dirk Prankef24e6b22018-03-27 20:12:30997 build_dir = self.args.path
Dirk Pranke0fd41bcd2015-06-19 00:05:50998
Takuto Ikuta9dffd7e2018-09-05 01:04:00999 if check:
1000 cmd = self.GNCmd('gen', build_dir, '--check')
1001 else:
1002 cmd = self.GNCmd('gen', build_dir)
dprankeeca4a782016-04-14 01:42:381003 gn_args = self.GNArgs(vals)
Andrew Grieve0bb79bb2018-06-27 03:14:091004 if compute_inputs_for_analyze:
1005 gn_args += ' compute_inputs_for_analyze=true'
dprankeeca4a782016-04-14 01:42:381006
1007 # Since GN hasn't run yet, the build directory may not even exist.
1008 self.MaybeMakeDirectory(self.ToAbsPath(build_dir))
1009
1010 gn_args_path = self.ToAbsPath(build_dir, 'args.gn')
dpranke4ff8b9f2016-04-15 03:07:541011 self.WriteFile(gn_args_path, gn_args, force_verbose=True)
dpranke74559b52015-06-10 21:20:391012
dpranke751516a2015-10-03 01:11:341013 if getattr(self.args, 'swarming_targets_file', None):
dpranke74559b52015-06-10 21:20:391014 # We need GN to generate the list of runtime dependencies for
1015 # the compile targets listed (one per line) in the file so
dprankecb4a2e242016-09-19 01:13:141016 # we can run them via swarming. We use gn_isolate_map.pyl to convert
dpranke74559b52015-06-10 21:20:391017 # the compile targets to the matching GN labels.
dprankeb2be10a2016-02-22 17:11:001018 path = self.args.swarming_targets_file
1019 if not self.Exists(path):
1020 self.WriteFailureAndRaise('"%s" does not exist' % path,
1021 output_path=None)
1022 contents = self.ReadFile(path)
Erik Chen42df41d2018-08-21 17:13:311023 isolate_targets = set(contents.splitlines())
dprankeb2be10a2016-02-22 17:11:001024
dprankecb4a2e242016-09-19 01:13:141025 isolate_map = self.ReadIsolateMap()
Dirk Pranke7a7e9b62019-02-17 01:46:251026 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
1027 isolate_map, build_dir)
1028
Erik Chen42df41d2018-08-21 17:13:311029 err, labels = self.MapTargetsToLabels(isolate_map, isolate_targets)
dprankeb2be10a2016-02-22 17:11:001030 if err:
Dirk Pranke7a7e9b62019-02-17 01:46:251031 raise MBErr(err)
dpranke74559b52015-06-10 21:20:391032
dpranke751516a2015-10-03 01:11:341033 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
dprankecb4a2e242016-09-19 01:13:141034 self.WriteFile(gn_runtime_deps_path, '\n'.join(labels) + '\n')
dpranke74559b52015-06-10 21:20:391035 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
1036
Sajjad Mirza261ddf92020-02-12 21:34:481037 # Detect if we are running in a vpython interpreter, and if so force GN to
1038 # use the real python interpreter. crbug.com/1049421
1039 # This ensures that ninja will only use the real python interpreter and not
1040 # vpython, so that any python scripts in the build will only use python
1041 # modules vendored into //third_party.
1042 # This can be deleted when python 3 becomes the only supported interpreter,
1043 # because in python 3 vpython will no longer have its current 'viral'
1044 # qualities and will require explicit usage to opt in to.
1045 prefix = getattr(sys, "real_prefix", sys.prefix)
Sajjad Mirza48cd8182020-02-21 02:08:141046 python_exe = 'python.exe' if self.platform.startswith('win') else 'python'
1047 # The value of prefix varies. Sometimes it extends to include the bin/
1048 # directory of the python install such that prefix/python is the
1049 # interpreter, and other times prefix/bin/python is the interpreter.
1050 # Therefore we need to check both. Also, it is safer to check prefix/bin
1051 # first because there have been previous installs where prefix/bin/python
1052 # was the real binary and prefix/python was actually vpython-native.
1053 possible_python_locations = [
1054 os.path.join(prefix, 'bin', python_exe),
1055 os.path.join(prefix, python_exe),
1056 ]
1057 for p in possible_python_locations:
1058 if os.path.isfile(p):
1059 cmd.append('--script-executable=%s' % p)
1060 break
Sajjad Mirza261ddf92020-02-12 21:34:481061 else:
Sajjad Mirza48cd8182020-02-21 02:08:141062 self.Print('python interpreter not under %s' % prefix)
Sajjad Mirza261ddf92020-02-12 21:34:481063
Debrian Figueroaae582232019-07-17 01:54:451064 ret, output, _ = self.Run(cmd)
dprankee0547cd2015-09-15 01:27:401065 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:111066 if self.args.json_output:
Debrian Figueroaae582232019-07-17 01:54:451067 # write errors to json.output
1068 self.WriteJSON({'output': output}, self.args.json_output)
Dirk Pranke7a7e9b62019-02-17 01:46:251069 # If `gn gen` failed, we should exit early rather than trying to
1070 # generate isolates. Run() will have already logged any error output.
1071 self.Print('GN gen failed: %d' % ret)
1072 return ret
dpranke74559b52015-06-10 21:20:391073
Erik Chen42df41d2018-08-21 17:13:311074 if getattr(self.args, 'swarming_targets_file', None):
Nico Weber0fd016762019-08-25 14:48:141075 ret = self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
Erik Chen42df41d2018-08-21 17:13:311076
Nico Weber0fd016762019-08-25 14:48:141077 return ret
Erik Chen42df41d2018-08-21 17:13:311078
1079 def RunGNGenAllIsolates(self, vals):
1080 """
1081 This command generates all .isolate files.
1082
1083 This command assumes that "mb.py gen" has already been run, as it relies on
1084 "gn ls" to fetch all gn targets. If uses that output, combined with the
1085 isolate_map, to determine all isolates that can be generated for the current
1086 gn configuration.
1087 """
1088 build_dir = self.args.path
1089 ret, output, _ = self.Run(self.GNCmd('ls', build_dir),
1090 force_verbose=False)
1091 if ret:
Yun Liuc0f2f732019-09-18 17:06:311092 # If `gn ls` failed, we should exit early rather than trying to
1093 # generate isolates.
1094 self.Print('GN ls failed: %d' % ret)
1095 return ret
Erik Chen42df41d2018-08-21 17:13:311096
1097 # Create a reverse map from isolate label to isolate dict.
1098 isolate_map = self.ReadIsolateMap()
1099 isolate_dict_map = {}
1100 for key, isolate_dict in isolate_map.iteritems():
1101 isolate_dict_map[isolate_dict['label']] = isolate_dict
1102 isolate_dict_map[isolate_dict['label']]['isolate_key'] = key
1103
1104 runtime_deps = []
1105
1106 isolate_targets = []
1107 # For every GN target, look up the isolate dict.
1108 for line in output.splitlines():
1109 target = line.strip()
1110 if target in isolate_dict_map:
1111 if isolate_dict_map[target]['type'] == 'additional_compile_target':
1112 # By definition, additional_compile_targets are not tests, so we
1113 # shouldn't generate isolates for them.
1114 continue
1115
1116 isolate_targets.append(isolate_dict_map[target]['isolate_key'])
1117 runtime_deps.append(target)
1118
Dirk Pranke7a7e9b62019-02-17 01:46:251119 self.RemovePossiblyStaleRuntimeDepsFiles(vals, isolate_targets,
1120 isolate_map, build_dir)
1121
Erik Chen42df41d2018-08-21 17:13:311122 gn_runtime_deps_path = self.ToAbsPath(build_dir, 'runtime_deps')
1123 self.WriteFile(gn_runtime_deps_path, '\n'.join(runtime_deps) + '\n')
1124 cmd = self.GNCmd('gen', build_dir)
1125 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
1126 self.Run(cmd)
1127
1128 return self.GenerateIsolates(vals, isolate_targets, isolate_map, build_dir)
1129
Dirk Pranke7a7e9b62019-02-17 01:46:251130 def RemovePossiblyStaleRuntimeDepsFiles(self, vals, targets, isolate_map,
1131 build_dir):
1132 # TODO(crbug.com/932700): Because `gn gen --runtime-deps-list-file`
1133 # puts the runtime_deps file in different locations based on the actual
1134 # type of a target, we may end up with multiple possible runtime_deps
1135 # files in a given build directory, where some of the entries might be
1136 # stale (since we might be reusing an existing build directory).
1137 #
1138 # We need to be able to get the right one reliably; you might think
1139 # we can just pick the newest file, but because GN won't update timestamps
1140 # if the contents of the files change, an older runtime_deps
1141 # file might actually be the one we should use over a newer one (see
1142 # crbug.com/932387 for a more complete explanation and example).
1143 #
1144 # In order to avoid this, we need to delete any possible runtime_deps
1145 # files *prior* to running GN. As long as the files aren't actually
1146 # needed during the build, this hopefully will not cause unnecessary
1147 # build work, and so it should be safe.
1148 #
1149 # Ultimately, we should just make sure we get the runtime_deps files
1150 # in predictable locations so we don't have this issue at all, and
1151 # that's what crbug.com/932700 is for.
1152 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, targets, isolate_map)
1153 for rpaths in possible_rpaths.values():
1154 for rpath in rpaths:
1155 path = self.ToAbsPath(build_dir, rpath)
1156 if self.Exists(path):
1157 self.RemoveFile(path)
1158
Erik Chen42df41d2018-08-21 17:13:311159 def GenerateIsolates(self, vals, ninja_targets, isolate_map, build_dir):
1160 """
1161 Generates isolates for a list of ninja targets.
1162
1163 Ninja targets are transformed to GN targets via isolate_map.
1164
1165 This function assumes that a previous invocation of "mb.py gen" has
1166 generated runtime deps for all targets.
1167 """
Dirk Pranke7a7e9b62019-02-17 01:46:251168 possible_rpaths = self.PossibleRuntimeDepsPaths(vals, ninja_targets,
1169 isolate_map)
1170
1171 for target, rpaths in possible_rpaths.items():
1172 # TODO(crbug.com/932700): We don't know where each .runtime_deps
1173 # file might be, but assuming we called
1174 # RemovePossiblyStaleRuntimeDepsFiles prior to calling `gn gen`,
1175 # there should only be one file.
1176 found_one = False
1177 path_to_use = None
1178 for r in rpaths:
1179 path = self.ToAbsPath(build_dir, r)
1180 if self.Exists(path):
1181 if found_one:
1182 raise MBErr('Found more than one of %s' % ', '.join(rpaths))
1183 path_to_use = path
1184 found_one = True
1185
1186 if not found_one:
1187 raise MBErr('Did not find any of %s' % ', '.join(rpaths))
1188
1189 command, extra_files = self.GetIsolateCommand(target, vals)
1190 runtime_deps = self.ReadFile(path_to_use).splitlines()
1191
1192 canonical_target = target.replace(':','_').replace('/','_')
Nico Weber0fd016762019-08-25 14:48:141193 ret = self.WriteIsolateFiles(build_dir, command, canonical_target,
1194 runtime_deps, vals, extra_files)
1195 if ret:
1196 return ret
1197 return 0
Dirk Pranke7a7e9b62019-02-17 01:46:251198
1199 def PossibleRuntimeDepsPaths(self, vals, ninja_targets, isolate_map):
1200 """Returns a map of targets to possible .runtime_deps paths.
1201
1202 Each ninja target maps on to a GN label, but depending on the type
1203 of the GN target, `gn gen --runtime-deps-list-file` will write
1204 the .runtime_deps files into different locations. Unfortunately, in
1205 some cases we don't actually know which of multiple locations will
1206 actually be used, so we return all plausible candidates.
1207
1208 The paths that are returned are relative to the build directory.
1209 """
1210
jbudoricke3c4f95e2016-04-28 23:17:381211 android = 'target_os="android"' in vals['gn_args']
Dirk Pranke26de05aec2019-04-03 19:18:381212 ios = 'target_os="ios"' in vals['gn_args']
Kevin Marshallf35fa5f2018-01-29 19:24:421213 fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Nico Weberd94b71a2018-02-22 22:00:301214 win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
Dirk Pranke7a7e9b62019-02-17 01:46:251215 possible_runtime_deps_rpaths = {}
Erik Chen42df41d2018-08-21 17:13:311216 for target in ninja_targets:
John Budorick39f14962019-04-11 23:03:201217 target_type = isolate_map[target]['type']
1218 label = isolate_map[target]['label']
1219 stamp_runtime_deps = 'obj/%s.stamp.runtime_deps' % label.replace(':', '/')
Erik Chen42df41d2018-08-21 17:13:311220 # TODO(https://crbug.com/876065): 'official_tests' use
1221 # type='additional_compile_target' to isolate tests. This is not the
1222 # intended use for 'additional_compile_target'.
John Budorick39f14962019-04-11 23:03:201223 if (target_type == 'additional_compile_target' and
Erik Chen42df41d2018-08-21 17:13:311224 target != 'official_tests'):
1225 # By definition, additional_compile_targets are not tests, so we
1226 # shouldn't generate isolates for them.
Dirk Pranke7a7e9b62019-02-17 01:46:251227 raise MBErr('Cannot generate isolate for %s since it is an '
1228 'additional_compile_target.' % target)
John Budorick39f14962019-04-11 23:03:201229 elif fuchsia or ios or target_type == 'generated_script':
1230 # iOS and Fuchsia targets end up as groups.
1231 # generated_script targets are always actions.
1232 rpaths = [stamp_runtime_deps]
Erik Chen42df41d2018-08-21 17:13:311233 elif android:
jbudoricke3c4f95e2016-04-28 23:17:381234 # Android targets may be either android_apk or executable. The former
jbudorick91c8a6012016-01-29 23:20:021235 # will result in runtime_deps associated with the stamp file, while the
1236 # latter will result in runtime_deps associated with the executable.
Abhishek Arya2f5f7342018-06-13 16:59:441237 label = isolate_map[target]['label']
Dirk Pranke7a7e9b62019-02-17 01:46:251238 rpaths = [
dprankecb4a2e242016-09-19 01:13:141239 target + '.runtime_deps',
John Budorick39f14962019-04-11 23:03:201240 stamp_runtime_deps]
1241 elif (target_type == 'script' or
dprankecb4a2e242016-09-19 01:13:141242 isolate_map[target].get('label_type') == 'group'):
dpranke6abd8652015-08-28 03:21:111243 # For script targets, the build target is usually a group,
1244 # for which gn generates the runtime_deps next to the stamp file
eyaich82d5ac942016-11-03 12:13:491245 # for the label, which lives under the obj/ directory, but it may
1246 # also be an executable.
Abhishek Arya2f5f7342018-06-13 16:59:441247 label = isolate_map[target]['label']
John Budorick39f14962019-04-11 23:03:201248 rpaths = [stamp_runtime_deps]
Nico Weberd94b71a2018-02-22 22:00:301249 if win:
Dirk Pranke7a7e9b62019-02-17 01:46:251250 rpaths += [ target + '.exe.runtime_deps' ]
eyaich82d5ac942016-11-03 12:13:491251 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251252 rpaths += [ target + '.runtime_deps' ]
Nico Weberd94b71a2018-02-22 22:00:301253 elif win:
Dirk Pranke7a7e9b62019-02-17 01:46:251254 rpaths = [target + '.exe.runtime_deps']
dpranke34bd39d2015-06-24 02:36:521255 else:
Dirk Pranke7a7e9b62019-02-17 01:46:251256 rpaths = [target + '.runtime_deps']
jbudorick91c8a6012016-01-29 23:20:021257
Dirk Pranke7a7e9b62019-02-17 01:46:251258 possible_runtime_deps_rpaths[target] = rpaths
Dirk Prankeb3b725c2019-02-16 02:18:411259
Dirk Pranke7a7e9b62019-02-17 01:46:251260 return possible_runtime_deps_rpaths
dpranke751516a2015-10-03 01:11:341261
1262 def RunGNIsolate(self, vals):
Dirk Prankef24e6b22018-03-27 20:12:301263 target = self.args.target
dprankecb4a2e242016-09-19 01:13:141264 isolate_map = self.ReadIsolateMap()
1265 err, labels = self.MapTargetsToLabels(isolate_map, [target])
1266 if err:
1267 raise MBErr(err)
Dirk Pranke7a7e9b62019-02-17 01:46:251268
dprankecb4a2e242016-09-19 01:13:141269 label = labels[0]
dpranke751516a2015-10-03 01:11:341270
Dirk Prankef24e6b22018-03-27 20:12:301271 build_dir = self.args.path
dprankecb4a2e242016-09-19 01:13:141272 command, extra_files = self.GetIsolateCommand(target, vals)
dpranke751516a2015-10-03 01:11:341273
dprankeeca4a782016-04-14 01:42:381274 cmd = self.GNCmd('desc', build_dir, label, 'runtime_deps')
dpranke40da0202016-02-13 05:05:201275 ret, out, _ = self.Call(cmd)
dpranke751516a2015-10-03 01:11:341276 if ret:
dpranke030d7a6d2016-03-26 17:23:501277 if out:
1278 self.Print(out)
dpranke751516a2015-10-03 01:11:341279 return ret
1280
1281 runtime_deps = out.splitlines()
1282
Nico Weber0fd016762019-08-25 14:48:141283 ret = self.WriteIsolateFiles(build_dir, command, target, runtime_deps, vals,
1284 extra_files)
1285 if ret:
1286 return ret
dpranke751516a2015-10-03 01:11:341287
1288 ret, _, _ = self.Run([
1289 self.executable,
1290 self.PathJoin('tools', 'swarming_client', 'isolate.py'),
1291 'check',
1292 '-i',
1293 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1294 '-s',
1295 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target))],
1296 buffer_output=False)
dpranked5b2b9432015-06-23 16:55:301297
dprankefe4602312015-04-08 16:20:351298 return ret
1299
Nico Weber0fd016762019-08-25 14:48:141300 def WriteIsolateFiles(self, build_dir, command, target, runtime_deps, vals,
dpranke751516a2015-10-03 01:11:341301 extra_files):
1302 isolate_path = self.ToAbsPath(build_dir, target + '.isolate')
Nico Weber0fd016762019-08-25 14:48:141303 files = sorted(set(runtime_deps + extra_files))
1304
1305 # Complain if any file is a directory that's inside the build directory,
1306 # since that makes incremental builds incorrect. See
1307 # https://crbug.com/912946
1308 is_android = 'target_os="android"' in vals['gn_args']
1309 is_cros = ('target_os="chromeos"' in vals['gn_args'] or
1310 vals.get('cros_passthrough', False))
1311 is_mac = self.platform == 'darwin'
Nico Weber0fd016762019-08-25 14:48:141312 is_msan = 'is_msan=true' in vals['gn_args']
Jeff Yoonf7f4eb42020-03-06 18:55:361313 is_ios = 'target_os="ios"' in vals['gn_args']
Nico Weber0fd016762019-08-25 14:48:141314
1315 err = ''
1316 for f in files:
1317 # Skip a few configs that need extra cleanup for now.
1318 # TODO(https://crbug.com/912946): Fix everything on all platforms and
1319 # enable check everywhere.
Nico Weberd9886b92019-09-10 17:52:171320 if is_android:
Nico Weber0fd016762019-08-25 14:48:141321 break
1322
Jeff Yoonf7f4eb42020-03-06 18:55:361323 # iOS has generated directories in gn data items.
1324 # Skipping for iOS instead of listing all apps.
1325 if is_ios:
1326 break
1327
Nico Weber0fd016762019-08-25 14:48:141328 # Skip a few existing violations that need to be cleaned up. Each of
1329 # these will lead to incorrect incremental builds if their directory
1330 # contents change. Do not add to this list.
1331 # TODO(https://crbug.com/912946): Remove this if statement.
Michael Changd2e619e2020-02-14 20:27:151332 if ((is_msan and f == 'instrumented_libraries_prebuilt/')
1333 or f == 'mr_extension/' or # https://crbug.com/997947
Nico Weber0fd016762019-08-25 14:48:141334 f.startswith('nacl_test_data/') or
Nico Weber5eee4522019-09-05 23:28:051335 f.startswith('ppapi_nacl_tests_libs/') or
Nico Weberd9886b92019-09-10 17:52:171336 (is_cros and f in ( # https://crbug.com/1002509
1337 'chromevox_test_data/',
1338 'gen/ui/file_manager/file_manager/',
1339 'resources/chromeos/',
Anastasia Helfinstein4bb719552019-11-21 19:02:511340 'resources/chromeos/accessibility/autoclick/',
1341 'resources/chromeos/accessibility/chromevox/',
1342 'resources/chromeos/accessibility/select_to_speak/',
1343 'test_data/chrome/browser/resources/chromeos/accessibility/'
Michael Changd2e619e2020-02-14 20:27:151344 'autoclick/',
Anastasia Helfinstein4bb719552019-11-21 19:02:511345 'test_data/chrome/browser/resources/chromeos/accessibility/'
Michael Changd2e619e2020-02-14 20:27:151346 'chromevox/',
Anastasia Helfinstein4bb719552019-11-21 19:02:511347 'test_data/chrome/browser/resources/chromeos/accessibility/'
Michael Changd2e619e2020-02-14 20:27:151348 'select_to_speak/',
1349 )) or (is_mac and f in ( # https://crbug.com/1000667
Nico Weber5eee4522019-09-05 23:28:051350 'AlertNotificationService.xpc/',
Nico Weber5eee4522019-09-05 23:28:051351 'Chromium Framework.framework/',
1352 'Chromium Helper.app/',
1353 'Chromium.app/',
Michael Changd2e619e2020-02-14 20:27:151354 'ChromiumUpdater.app/',
Nico Weber5eee4522019-09-05 23:28:051355 'Content Shell.app/',
Nico Weber5eee4522019-09-05 23:28:051356 'Google Chrome Framework.framework/',
1357 'Google Chrome Helper (GPU).app/',
Nico Weber5eee4522019-09-05 23:28:051358 'Google Chrome Helper (Plugin).app/',
Nico Weber5eee4522019-09-05 23:28:051359 'Google Chrome Helper (Renderer).app/',
Nico Weber5eee4522019-09-05 23:28:051360 'Google Chrome Helper.app/',
Nico Weber5eee4522019-09-05 23:28:051361 'Google Chrome.app/',
Michael Changd2e619e2020-02-14 20:27:151362 'GoogleUpdater.app/',
Nico Weber5eee4522019-09-05 23:28:051363 'blink_deprecated_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051364 'blink_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051365 'corb_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051366 'obj/tools/grit/brotli_mac_asan_workaround/',
1367 'power_saver_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051368 'ppapi_tests.plugin/',
Nico Weber5eee4522019-09-05 23:28:051369 'ui_unittests Framework.framework/',
1370 ))):
Nico Weber0fd016762019-08-25 14:48:141371 continue
1372
Nico Weber24e54f992019-08-26 14:33:321373 # This runs before the build, so we can't use isdir(f). But
Nico Weber0fd016762019-08-25 14:48:141374 # isolate.py luckily requires data directories to end with '/', so we
Nico Weber24e54f992019-08-26 14:33:321375 # can check for that.
Nico Weber57dbc9952019-09-04 13:33:581376 if not f.startswith('../../') and f.endswith('/'):
Nico Weber24e54f992019-08-26 14:33:321377 # Don't use self.PathJoin() -- all involved paths consistently use
1378 # forward slashes, so don't add one single backslash on Windows.
1379 err += '\n' + build_dir + '/' + f
Nico Weber0fd016762019-08-25 14:48:141380
1381 if err:
1382 self.Print('error: gn `data` items may not list generated directories; '
Nico Weber24e54f992019-08-26 14:33:321383 'list files in directory instead for:' + err)
Nico Weber0fd016762019-08-25 14:48:141384 return 1
1385
dpranke751516a2015-10-03 01:11:341386 self.WriteFile(isolate_path,
1387 pprint.pformat({
1388 'variables': {
1389 'command': command,
Nico Weber0fd016762019-08-25 14:48:141390 'files': files,
dpranke751516a2015-10-03 01:11:341391 }
1392 }) + '\n')
1393
1394 self.WriteJSON(
1395 {
1396 'args': [
1397 '--isolated',
1398 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1399 '--isolate',
1400 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1401 ],
1402 'dir': self.chromium_src_dir,
1403 'version': 1,
1404 },
1405 isolate_path + 'd.gen.json',
1406 )
1407
dprankecb4a2e242016-09-19 01:13:141408 def MapTargetsToLabels(self, isolate_map, targets):
1409 labels = []
1410 err = ''
1411
dprankecb4a2e242016-09-19 01:13:141412 for target in targets:
1413 if target == 'all':
1414 labels.append(target)
1415 elif target.startswith('//'):
1416 labels.append(target)
1417 else:
1418 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421419 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141420 err += ('test target "%s" type is unknown\n' % target)
1421 else:
thakis024d6f32017-05-16 23:21:421422 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141423 else:
1424 err += ('target "%s" not found in '
1425 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1426
1427 return err, labels
1428
dprankeeca4a782016-04-14 01:42:381429 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461430 if self.platform == 'linux2':
1431 subdir, exe = 'linux64', 'gn'
1432 elif self.platform == 'darwin':
1433 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251434 elif self.platform == 'aix6':
1435 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461436 else:
1437 subdir, exe = 'win', 'gn.exe'
1438
1439 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081440 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521441
dprankecb4a2e242016-09-19 01:13:141442
Garrett Beatyb6cee042019-04-22 18:42:091443 def GNArgs(self, vals, expand_imports=False):
dpranke73ed0d62016-04-25 19:18:341444 if vals['cros_passthrough']:
1445 if not 'GN_ARGS' in os.environ:
1446 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1447 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161448 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301449 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341450 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351451 if vals['gn_args']:
1452 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341453 else:
1454 gn_args = vals['gn_args']
1455
dpranked0c138b2016-04-13 18:28:471456 if self.args.goma_dir:
1457 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381458
agrieve41d21a72016-04-14 18:02:261459 android_version_code = self.args.android_version_code
1460 if android_version_code:
1461 gn_args += ' android_default_version_code="%s"' % android_version_code
1462
1463 android_version_name = self.args.android_version_name
1464 if android_version_name:
1465 gn_args += ' android_default_version_name="%s"' % android_version_name
1466
Garrett Beatyb6cee042019-04-22 18:42:091467 args_gn_lines = []
1468 parsed_gn_args = {}
dprankeeca4a782016-04-14 01:42:381469
Ben Pastene65ccf6132018-11-08 00:47:591470 # If we're using the Simple Chrome SDK, add a comment at the top that
1471 # points to the doc. This must happen after the gn_helpers.ToGNString()
1472 # call above since gn_helpers strips comments.
1473 if vals['cros_passthrough']:
Garrett Beatyb6cee042019-04-22 18:42:091474 args_gn_lines.extend([
Ben Pastene65ccf6132018-11-08 00:47:591475 '# These args are generated via the Simple Chrome SDK. See the link',
1476 '# below for more details:',
1477 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
Garrett Beatyb6cee042019-04-22 18:42:091478 ])
Ben Pastene65ccf6132018-11-08 00:47:591479
dpranke9dd5e252016-04-14 04:23:091480 args_file = vals.get('args_file', None)
1481 if args_file:
Garrett Beatyb6cee042019-04-22 18:42:091482 if expand_imports:
1483 content = self.ReadFile(self.ToAbsPath(args_file))
1484 parsed_gn_args = gn_helpers.FromGNArgs(content)
1485 else:
1486 args_gn_lines.append('import("%s")' % args_file)
1487
1488 # Canonicalize the arg string into a sorted, newline-separated list
1489 # of key-value pairs, and de-dup the keys if need be so that only
1490 # the last instance of each arg is listed.
1491 parsed_gn_args.update(gn_helpers.FromGNArgs(gn_args))
1492 args_gn_lines.append(gn_helpers.ToGNString(parsed_gn_args))
1493
1494 return '\n'.join(args_gn_lines)
dprankefe4602312015-04-08 16:20:351495
dprankecb4a2e242016-09-19 01:13:141496 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071497 isolate_map = self.ReadIsolateMap()
1498
Scott Graham3be4b4162017-09-12 00:41:411499 is_android = 'target_os="android"' in vals['gn_args']
1500 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Caleb Raittof983d102019-06-21 23:05:021501 is_cros = 'target_os="chromeos"' in vals['gn_args']
Nico Webera7bc1cb2019-06-15 17:42:391502 is_simplechrome = vals.get('cros_passthrough', False)
1503 is_mac = self.platform == 'darwin'
Nico Weberd94b71a2018-02-22 22:00:301504 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061505
kylechar39705682017-01-19 14:37:231506 # This should be true if tests with type='windowed_test_launcher' are
1507 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081508 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1509 # backends. Currently, tests are executed for the headless and X11 backends
1510 # and both can run under Xvfb.
1511 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1512 # backend.
Scott Graham3be4b4162017-09-12 00:41:411513 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251514
1515 asan = 'is_asan=true' in vals['gn_args']
1516 msan = 'is_msan=true' in vals['gn_args']
1517 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411518 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
Yun Liu5764e0dc2019-10-24 01:50:221519 clang_coverage = 'use_clang_coverage=true' in vals['gn_args']
Yun Liuc0f2f732019-09-18 17:06:311520 java_coverage = 'use_jacoco_coverage=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251521
dprankecb4a2e242016-09-19 01:13:141522 test_type = isolate_map[target]['type']
Brian Sheedy234580e52019-09-10 17:42:511523 use_python3 = isolate_map[target].get('use_python3', False)
dprankefe0d35e2016-02-05 02:43:591524
dprankecb4a2e242016-09-19 01:13:141525 executable = isolate_map[target].get('executable', target)
bsheedy9c16ed62019-04-10 20:32:111526 executable_suffix = isolate_map[target].get(
1527 'executable_suffix', '.exe' if is_win else '')
dprankefe0d35e2016-02-05 02:43:591528
Brian Sheedy234580e52019-09-10 17:42:511529 if use_python3:
1530 cmdline = [ 'vpython3' ]
1531 extra_files = [ '../../.vpython3' ]
1532 else:
1533 cmdline = [ 'vpython' ]
1534 extra_files = [ '../../.vpython' ]
1535 extra_files += [
Andrii Shyshkalovc158e0102018-01-10 05:52:001536 '../../testing/test_env.py',
1537 ]
dpranked8113582015-06-05 20:08:251538
dprankecb4a2e242016-09-19 01:13:141539 if test_type == 'nontest':
1540 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1541 output_path=None)
1542
John Budorick93e88ac82019-04-12 18:39:111543 if test_type == 'generated_script':
Ben Pastenecb0fb412019-06-11 02:31:541544 script = isolate_map[target]['script']
1545 if self.platform == 'win32':
1546 script += '.bat'
Brian Sheedy234580e52019-09-10 17:42:511547 cmdline += [
John Budorick93e88ac82019-04-12 18:39:111548 '../../testing/test_env.py',
Ben Pastenecb0fb412019-06-11 02:31:541549 script,
John Budorick93e88ac82019-04-12 18:39:111550 ]
Roberto Carrillo1460da852018-12-14 17:10:391551 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011552 if asan:
John Budorick31cdce62019-04-03 20:56:111553 cmdline += [os.path.join('bin', 'run_with_asan'), '--']
John Budorick8c4203042019-03-19 17:22:011554 cmdline += [
John Budorickfb97a852017-12-20 20:10:191555 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041556 '../../build/android/test_wrapper/logdog_wrapper.py',
1557 '--target', target,
hzl9ae14452017-04-04 23:38:021558 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481559 '--store-tombstones']
Yun Liu5764e0dc2019-10-24 01:50:221560 if clang_coverage or java_coverage:
Yun Liu7cef1072019-06-27 21:22:191561 cmdline += ['--coverage-dir', '${ISOLATED_OUTDIR}']
Scott Graham3be4b4162017-09-12 00:41:411562 elif is_fuchsia and test_type != 'script':
Brian Sheedy234580e52019-09-10 17:42:511563 cmdline += [
John Budorickfb97a852017-12-20 20:10:191564 '../../testing/test_env.py',
1565 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441566 '--test-launcher-bot-mode',
Sergey Ulanovd851243b2019-06-25 00:33:471567 '--system-log-file', '${ISOLATED_OUTDIR}/system_log'
John Budorickfb97a852017-12-20 20:10:191568 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321569 elif is_simplechrome and test_type != 'script':
Brian Sheedy234580e52019-09-10 17:42:511570 cmdline += [
Benjamin Pastene3bce864e2018-04-14 01:16:321571 '../../testing/test_env.py',
1572 os.path.join('bin', 'run_%s' % target),
1573 ]
kylechar39705682017-01-19 14:37:231574 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001575 extra_files.append('../../testing/xvfb.py')
Brian Sheedy234580e52019-09-10 17:42:511576 cmdline += [
Nico Webera7bc1cb2019-06-15 17:42:391577 '../../testing/xvfb.py',
1578 './' + str(executable) + executable_suffix,
1579 '--test-launcher-bot-mode',
1580 '--asan=%d' % asan,
1581 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1582 # supported.
1583 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021584 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1585 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
Nico Webera7bc1cb2019-06-15 17:42:391586 '--msan=%d' % msan,
1587 '--tsan=%d' % tsan,
1588 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471589 ]
1590 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
Brian Sheedy234580e52019-09-10 17:42:511591 cmdline += [
dprankea55584f12015-07-22 00:52:471592 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591593 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251594 '--test-launcher-bot-mode',
1595 '--asan=%d' % asan,
Caleb Raitto1fb2cc9e2019-06-14 01:04:231596 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1597 # supported.
Nico Webera7bc1cb2019-06-15 17:42:391598 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021599 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1600 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
dpranked8113582015-06-05 20:08:251601 '--msan=%d' % msan,
1602 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411603 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471604 ]
dpranke6abd8652015-08-28 03:21:111605 elif test_type == 'script':
Ben Pastene4534c39e2019-07-08 22:55:341606 # If we're testing a CrOS simplechrome build, assume we need to prepare a
1607 # DUT for testing. So prepend the command to run with the test wrapper.
Ben Pastene8ab6954d2018-05-04 04:08:241608 if is_simplechrome:
Ben Pastene908863c2019-07-25 16:20:031609 cmdline = [
1610 os.path.join('bin', 'cros_test_wrapper'),
1611 '--logs-dir=${ISOLATED_OUTDIR}',
1612 ]
Ben Pastene8ab6954d2018-05-04 04:08:241613 cmdline += [
dpranke6abd8652015-08-28 03:21:111614 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141615 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591616 ]
Dirk Prankef24e6b22018-03-27 20:12:301617 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471618 cmdline = [
1619 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591620 ]
dprankea55584f12015-07-22 00:52:471621 else:
1622 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1623 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251624
dprankecb4a2e242016-09-19 01:13:141625 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591626
dpranked8113582015-06-05 20:08:251627 return cmdline, extra_files
1628
dpranke74559b52015-06-10 21:20:391629 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331630 return self.PathJoin(self.chromium_src_dir,
1631 self.ToSrcRelPath(build_path),
1632 *comps)
dpranked8113582015-06-05 20:08:251633
dprankeee5b51f62015-04-09 00:03:221634 def ToSrcRelPath(self, path):
1635 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501636 if path.startswith('//'):
1637 return path[2:].replace('/', self.sep)
1638 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351639
Dirk Pranke0fd41bcd2015-06-19 00:05:501640 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141641 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501642 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001643 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501644 if ret:
1645 return ret
1646
Dirk Prankef24e6b22018-03-27 20:12:301647 build_path = self.args.path
1648 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141649 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301650 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141651 gn_output_path = output_path + '.gn'
1652
dpranke7837fc362015-11-19 03:54:161653 inp = self.ReadInputJSON(['files', 'test_targets',
1654 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321655 if self.args.verbose:
1656 self.Print()
1657 self.Print('analyze input:')
1658 self.PrintJSON(inp)
1659 self.Print()
1660
dpranke76734662015-04-16 02:17:501661
dpranke7c5f614d2015-07-22 23:43:391662 # This shouldn't normally happen, but could due to unusual race conditions,
1663 # like a try job that gets scheduled before a patch lands but runs after
1664 # the patch has landed.
1665 if not inp['files']:
1666 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161667 self.WriteJSON({
1668 'status': 'No dependency',
1669 'compile_targets': [],
1670 'test_targets': [],
1671 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391672 return 0
1673
dprankecb4a2e242016-09-19 01:13:141674 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161675 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561676
dprankecb4a2e242016-09-19 01:13:141677 isolate_map = self.ReadIsolateMap()
1678 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1679 isolate_map, inp['additional_compile_targets'])
1680 if err:
1681 raise MBErr(err)
1682
1683 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1684 isolate_map, inp['test_targets'])
1685 if err:
1686 raise MBErr(err)
1687 labels_to_targets = {}
1688 for i, label in enumerate(gn_inp['test_targets']):
1689 labels_to_targets[label] = inp['test_targets'][i]
1690
dprankef61de2f2015-05-14 04:09:561691 try:
dprankecb4a2e242016-09-19 01:13:141692 self.WriteJSON(gn_inp, gn_input_path)
1693 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
Debrian Figueroaae51d0d2019-07-22 18:04:111694 ret, output, _ = self.Run(cmd, force_verbose=True)
dprankecb4a2e242016-09-19 01:13:141695 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:111696 if self.args.json_output:
1697 # write errors to json.output
1698 self.WriteJSON({'output': output}, self.args.json_output)
dprankecb4a2e242016-09-19 01:13:141699 return ret
dpranke067d0142015-05-14 22:52:451700
dprankecb4a2e242016-09-19 01:13:141701 gn_outp_str = self.ReadFile(gn_output_path)
1702 try:
1703 gn_outp = json.loads(gn_outp_str)
1704 except Exception as e:
1705 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1706 % (repr(gn_outp_str), str(e)))
1707 raise
1708
1709 outp = {}
1710 if 'status' in gn_outp:
1711 outp['status'] = gn_outp['status']
1712 if 'error' in gn_outp:
1713 outp['error'] = gn_outp['error']
1714 if 'invalid_targets' in gn_outp:
1715 outp['invalid_targets'] = gn_outp['invalid_targets']
1716 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491717 all_input_compile_targets = sorted(
1718 set(inp['test_targets'] + inp['additional_compile_targets']))
1719
1720 # If we're building 'all', we can throw away the rest of the targets
1721 # since they're redundant.
dpranke385a3102016-09-20 22:04:081722 if 'all' in gn_outp['compile_targets']:
1723 outp['compile_targets'] = ['all']
1724 else:
Dirk Pranke45165072017-11-08 04:57:491725 outp['compile_targets'] = gn_outp['compile_targets']
1726
1727 # crbug.com/736215: When GN returns targets back, for targets in
1728 # the default toolchain, GN will have generated a phony ninja
1729 # target matching the label, and so we can safely (and easily)
1730 # transform any GN label into the matching ninja target. For
1731 # targets in other toolchains, though, GN doesn't generate the
1732 # phony targets, and we don't know how to turn the labels into
1733 # compile targets. In this case, we also conservatively give up
1734 # and build everything. Probably the right thing to do here is
1735 # to have GN return the compile targets directly.
1736 if any("(" in target for target in outp['compile_targets']):
1737 self.Print('WARNING: targets with non-default toolchains were '
1738 'found, building everything instead.')
1739 outp['compile_targets'] = all_input_compile_targets
1740 else:
dpranke385a3102016-09-20 22:04:081741 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491742 label.replace('//', '') for label in outp['compile_targets']]
1743
1744 # Windows has a maximum command line length of 8k; even Linux
1745 # maxes out at 128k; if analyze returns a *really long* list of
1746 # targets, we just give up and conservatively build everything instead.
1747 # Probably the right thing here is for ninja to support response
1748 # files as input on the command line
1749 # (see https://github.com/ninja-build/ninja/issues/1355).
Andrew Grieve79903c72020-02-07 18:31:111750 # Android targets use a lot of templates and often exceed 7kb.
1751 # https://crbug.com/946266
1752 max_cmd_length_kb = 64 if platform.system() == 'Linux' else 7
1753
1754 if len(' '.join(outp['compile_targets'])) > max_cmd_length_kb * 1024:
Dirk Pranke45165072017-11-08 04:57:491755 self.Print('WARNING: Too many compile targets were affected.')
1756 self.Print('WARNING: Building everything instead to avoid '
1757 'command-line length issues.')
1758 outp['compile_targets'] = all_input_compile_targets
1759
1760
dprankecb4a2e242016-09-19 01:13:141761 if 'test_targets' in gn_outp:
1762 outp['test_targets'] = [
1763 labels_to_targets[label] for label in gn_outp['test_targets']]
1764
1765 if self.args.verbose:
1766 self.Print()
1767 self.Print('analyze output:')
1768 self.PrintJSON(outp)
1769 self.Print()
1770
1771 self.WriteJSON(outp, output_path)
1772
dprankef61de2f2015-05-14 04:09:561773 finally:
dprankecb4a2e242016-09-19 01:13:141774 if self.Exists(gn_input_path):
1775 self.RemoveFile(gn_input_path)
1776 if self.Exists(gn_output_path):
1777 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351778
1779 return 0
1780
dpranked8113582015-06-05 20:08:251781 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301782 path = self.args.input_path
1783 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351784 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321785 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351786
1787 try:
1788 inp = json.loads(self.ReadFile(path))
1789 except Exception as e:
1790 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321791 (path, e), output_path)
dpranked8113582015-06-05 20:08:251792
1793 for k in required_keys:
1794 if not k in inp:
1795 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1796 output_path)
dprankefe4602312015-04-08 16:20:351797
1798 return inp
1799
dpranked5b2b9432015-06-23 16:55:301800 def WriteFailureAndRaise(self, msg, output_path):
1801 if output_path:
dprankee0547cd2015-09-15 01:27:401802 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351803 raise MBErr(msg)
1804
dprankee0547cd2015-09-15 01:27:401805 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321806 try:
dprankee0547cd2015-09-15 01:27:401807 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1808 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321809 except Exception as e:
1810 raise MBErr('Error %s writing to the output path "%s"' %
1811 (e, path))
dprankefe4602312015-04-08 16:20:351812
aneeshmde50f472016-04-01 01:13:101813
dpranke3cec199c2015-09-22 23:29:021814 def PrintCmd(self, cmd, env):
1815 if self.platform == 'win32':
1816 env_prefix = 'set '
1817 env_quoter = QuoteForSet
1818 shell_quoter = QuoteForCmd
1819 else:
1820 env_prefix = ''
1821 env_quoter = pipes.quote
1822 shell_quoter = pipes.quote
1823
1824 def print_env(var):
1825 if env and var in env:
1826 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1827
dprankeec079262016-06-07 02:21:201828 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021829
dpranke8c2cfd32015-09-17 20:12:331830 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351831 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021832 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351833
dprankecda00332015-04-11 04:18:321834 def PrintJSON(self, obj):
1835 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1836
dpranke751516a2015-10-03 01:11:341837 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301838 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381839 if self.platform == 'win32':
1840 # On Windows use the batch script since there is no exe
1841 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1842 else:
1843 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341844 if self.args.jobs:
1845 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1846 ninja_cmd.append(target)
Dirk Pranke5f22a822019-05-23 22:55:251847 ret, _, _ = self.Run(ninja_cmd, buffer_output=False)
dpranke751516a2015-10-03 01:11:341848 return ret
1849
Stephen Martinis1f134492019-12-06 23:27:411850 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351851 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401852 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021853 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351854 if self.args.dryrun:
1855 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401856
Stephen Martinis1f134492019-12-06 23:27:411857 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401858 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341859 if ret:
1860 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351861 if out:
Debrian Figueroaae582232019-07-17 01:54:451862 # This is the error seen on the logs
dprankeee5b51f62015-04-09 00:03:221863 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351864 if err:
dprankeee5b51f62015-04-09 00:03:221865 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351866 return ret, out, err
1867
Stephen Martiniscd377012019-10-18 17:40:461868 def Call(self, cmd, env=None, buffer_output=True, stdin=None):
dpranke751516a2015-10-03 01:11:341869 if buffer_output:
1870 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1871 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
Stephen Martiniscd377012019-10-18 17:40:461872 env=env, stdin=subprocess.PIPE)
1873 out, err = p.communicate(input=stdin)
dpranke751516a2015-10-03 01:11:341874 else:
1875 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1876 env=env)
1877 p.wait()
1878 out = err = ''
dprankefe4602312015-04-08 16:20:351879 return p.returncode, out, err
1880
1881 def ExpandUser(self, path):
1882 # This function largely exists so it can be overridden for testing.
1883 return os.path.expanduser(path)
1884
1885 def Exists(self, path):
1886 # This function largely exists so it can be overridden for testing.
1887 return os.path.exists(path)
1888
dpranke867bcf4a2016-03-14 22:28:321889 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501890 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321891 f = urllib2.urlopen(url)
1892 contents = f.read()
1893 f.close()
1894 return contents
1895
dprankec3441d12015-06-23 23:01:351896 def MaybeMakeDirectory(self, path):
1897 try:
1898 os.makedirs(path)
1899 except OSError, e:
1900 if e.errno != errno.EEXIST:
1901 raise
1902
dpranke8c2cfd32015-09-17 20:12:331903 def PathJoin(self, *comps):
1904 # This function largely exists so it can be overriden for testing.
1905 return os.path.join(*comps)
1906
dpranke030d7a6d2016-03-26 17:23:501907 def Print(self, *args, **kwargs):
1908 # This function largely exists so it can be overridden for testing.
1909 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101910 if kwargs.get('stream', sys.stdout) == sys.stdout:
1911 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501912
dprankefe4602312015-04-08 16:20:351913 def ReadFile(self, path):
1914 # This function largely exists so it can be overriden for testing.
1915 with open(path) as fp:
1916 return fp.read()
1917
dpranke030d7a6d2016-03-26 17:23:501918 def RelPath(self, path, start='.'):
1919 # This function largely exists so it can be overriden for testing.
1920 return os.path.relpath(path, start)
1921
dprankef61de2f2015-05-14 04:09:561922 def RemoveFile(self, path):
1923 # This function largely exists so it can be overriden for testing.
1924 os.remove(path)
1925
dprankec161aa92015-09-14 20:21:131926 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331927 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131928 # In other places in chromium, we often have to retry this command
1929 # because we're worried about other processes still holding on to
1930 # file handles, but when MB is invoked, it will be early enough in the
1931 # build that their should be no other processes to interfere. We
1932 # can change this if need be.
1933 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1934 else:
1935 shutil.rmtree(abs_path, ignore_errors=True)
1936
Dirk Prankef24e6b22018-03-27 20:12:301937 def TempDir(self):
1938 # This function largely exists so it can be overriden for testing.
1939 return tempfile.mkdtemp(prefix='mb_')
1940
dprankef61de2f2015-05-14 04:09:561941 def TempFile(self, mode='w'):
1942 # This function largely exists so it can be overriden for testing.
1943 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1944
dprankee0547cd2015-09-15 01:27:401945 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351946 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401947 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301948 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351949 with open(path, 'w') as fp:
1950 return fp.write(contents)
1951
dprankef61de2f2015-05-14 04:09:561952
Stephen Martiniscd377012019-10-18 17:40:461953class LedResult(object):
1954 """Holds the result of a led operation. Can be chained using |then|."""
1955
1956 def __init__(self, result, run_cmd):
1957 self._result = result
1958 self._run_cmd = run_cmd
1959
1960 @property
1961 def result(self):
1962 """The mutable result data of the previous led call as decoded JSON."""
1963 return self._result
1964
1965 def then(self, *cmd):
1966 """Invoke led, passing it the current `result` data as input.
1967
1968 Returns another LedResult object with the output of the command.
1969 """
1970 return self.__class__(
1971 self._run_cmd(self._result, cmd), self._run_cmd)
1972
1973
Greg Guterman1492aeb42020-01-14 22:59:141974def FlattenConfig(config_pool, mixin_pool, config):
1975 mixins = config_pool[config]
1976 vals = DefaultVals()
1977
1978 visited = []
1979 FlattenMixins(mixin_pool, mixins, vals, visited)
1980 return vals
1981
1982
1983def FlattenMixins(mixin_pool, mixins_to_flatten, vals, visited):
1984 for m in mixins_to_flatten:
1985 if m not in mixin_pool:
1986 raise MBErr('Unknown mixin "%s"' % m)
1987
1988 visited.append(m)
1989
1990 mixin_vals = mixin_pool[m]
1991
1992 if 'cros_passthrough' in mixin_vals:
1993 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
1994 if 'args_file' in mixin_vals:
1995 if vals['args_file']:
1996 raise MBErr('args_file specified multiple times in mixins '
1997 'for mixin %s' % m)
1998 vals['args_file'] = mixin_vals['args_file']
1999 if 'gn_args' in mixin_vals:
2000 if vals['gn_args']:
2001 vals['gn_args'] += ' ' + mixin_vals['gn_args']
2002 else:
2003 vals['gn_args'] = mixin_vals['gn_args']
2004
2005 if 'mixins' in mixin_vals:
2006 FlattenMixins(mixin_pool, mixin_vals['mixins'], vals, visited)
2007 return vals
2008
2009
Stephen Martiniscd377012019-10-18 17:40:462010
dprankefe4602312015-04-08 16:20:352011class MBErr(Exception):
2012 pass
2013
2014
dpranke3cec199c2015-09-22 23:29:022015# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
2016# details of this next section, which handles escaping command lines
2017# so that they can be copied and pasted into a cmd window.
2018UNSAFE_FOR_SET = set('^<>&|')
2019UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
2020ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
2021
2022
2023def QuoteForSet(arg):
2024 if any(a in UNSAFE_FOR_SET for a in arg):
2025 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
2026 return arg
2027
2028
2029def QuoteForCmd(arg):
2030 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:022031 if arg == '' or ' ' in arg or '"' in arg:
2032 quote_re = re.compile(r'(\\*)"')
2033 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
2034
2035 # Then check to see if the arg contains any metacharacters other than
2036 # double quotes; if it does, quote everything (including the double
2037 # quotes) for safety.
2038 if any(a in UNSAFE_FOR_CMD for a in arg):
2039 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
2040 return arg
2041
2042
dprankefe4602312015-04-08 16:20:352043if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:592044 sys.exit(main(sys.argv[1:]))