blob: 3083dadf4c332c1aaf9c963dc6c1dba4b31a6a90 [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']
1313
1314 err = ''
1315 for f in files:
1316 # Skip a few configs that need extra cleanup for now.
1317 # TODO(https://crbug.com/912946): Fix everything on all platforms and
1318 # enable check everywhere.
Nico Weberd9886b92019-09-10 17:52:171319 if is_android:
Nico Weber0fd016762019-08-25 14:48:141320 break
1321
1322 # Skip a few existing violations that need to be cleaned up. Each of
1323 # these will lead to incorrect incremental builds if their directory
1324 # contents change. Do not add to this list.
1325 # TODO(https://crbug.com/912946): Remove this if statement.
Michael Changd2e619e2020-02-14 20:27:151326 if ((is_msan and f == 'instrumented_libraries_prebuilt/')
1327 or f == 'mr_extension/' or # https://crbug.com/997947
Nico Weber0fd016762019-08-25 14:48:141328 f.startswith('nacl_test_data/') or
Nico Weber5eee4522019-09-05 23:28:051329 f.startswith('ppapi_nacl_tests_libs/') or
Nico Weberd9886b92019-09-10 17:52:171330 (is_cros and f in ( # https://crbug.com/1002509
1331 'chromevox_test_data/',
1332 'gen/ui/file_manager/file_manager/',
1333 'resources/chromeos/',
Anastasia Helfinstein4bb719552019-11-21 19:02:511334 'resources/chromeos/accessibility/autoclick/',
1335 'resources/chromeos/accessibility/chromevox/',
1336 'resources/chromeos/accessibility/select_to_speak/',
1337 'test_data/chrome/browser/resources/chromeos/accessibility/'
Michael Changd2e619e2020-02-14 20:27:151338 'autoclick/',
Anastasia Helfinstein4bb719552019-11-21 19:02:511339 'test_data/chrome/browser/resources/chromeos/accessibility/'
Michael Changd2e619e2020-02-14 20:27:151340 'chromevox/',
Anastasia Helfinstein4bb719552019-11-21 19:02:511341 'test_data/chrome/browser/resources/chromeos/accessibility/'
Michael Changd2e619e2020-02-14 20:27:151342 'select_to_speak/',
1343 )) or (is_mac and f in ( # https://crbug.com/1000667
Nico Weber5eee4522019-09-05 23:28:051344 'AlertNotificationService.xpc/',
Nico Weber5eee4522019-09-05 23:28:051345 'Chromium Framework.framework/',
1346 'Chromium Helper.app/',
1347 'Chromium.app/',
Michael Changd2e619e2020-02-14 20:27:151348 'ChromiumUpdater.app/',
Nico Weber5eee4522019-09-05 23:28:051349 'Content Shell.app/',
Nico Weber5eee4522019-09-05 23:28:051350 'Google Chrome Framework.framework/',
1351 'Google Chrome Helper (GPU).app/',
Nico Weber5eee4522019-09-05 23:28:051352 'Google Chrome Helper (Plugin).app/',
Nico Weber5eee4522019-09-05 23:28:051353 'Google Chrome Helper (Renderer).app/',
Nico Weber5eee4522019-09-05 23:28:051354 'Google Chrome Helper.app/',
Nico Weber5eee4522019-09-05 23:28:051355 'Google Chrome.app/',
Michael Changd2e619e2020-02-14 20:27:151356 'GoogleUpdater.app/',
Nico Weber5eee4522019-09-05 23:28:051357 'blink_deprecated_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051358 'blink_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051359 'corb_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051360 'obj/tools/grit/brotli_mac_asan_workaround/',
1361 'power_saver_test_plugin.plugin/',
Nico Weber5eee4522019-09-05 23:28:051362 'ppapi_tests.plugin/',
Nico Weber5eee4522019-09-05 23:28:051363 'ui_unittests Framework.framework/',
1364 ))):
Nico Weber0fd016762019-08-25 14:48:141365 continue
1366
Nico Weber24e54f992019-08-26 14:33:321367 # This runs before the build, so we can't use isdir(f). But
Nico Weber0fd016762019-08-25 14:48:141368 # isolate.py luckily requires data directories to end with '/', so we
Nico Weber24e54f992019-08-26 14:33:321369 # can check for that.
Nico Weber57dbc9952019-09-04 13:33:581370 if not f.startswith('../../') and f.endswith('/'):
Nico Weber24e54f992019-08-26 14:33:321371 # Don't use self.PathJoin() -- all involved paths consistently use
1372 # forward slashes, so don't add one single backslash on Windows.
1373 err += '\n' + build_dir + '/' + f
Nico Weber0fd016762019-08-25 14:48:141374
1375 if err:
1376 self.Print('error: gn `data` items may not list generated directories; '
Nico Weber24e54f992019-08-26 14:33:321377 'list files in directory instead for:' + err)
Nico Weber0fd016762019-08-25 14:48:141378 return 1
1379
dpranke751516a2015-10-03 01:11:341380 self.WriteFile(isolate_path,
1381 pprint.pformat({
1382 'variables': {
1383 'command': command,
Nico Weber0fd016762019-08-25 14:48:141384 'files': files,
dpranke751516a2015-10-03 01:11:341385 }
1386 }) + '\n')
1387
1388 self.WriteJSON(
1389 {
1390 'args': [
1391 '--isolated',
1392 self.ToSrcRelPath('%s/%s.isolated' % (build_dir, target)),
1393 '--isolate',
1394 self.ToSrcRelPath('%s/%s.isolate' % (build_dir, target)),
1395 ],
1396 'dir': self.chromium_src_dir,
1397 'version': 1,
1398 },
1399 isolate_path + 'd.gen.json',
1400 )
1401
dprankecb4a2e242016-09-19 01:13:141402 def MapTargetsToLabels(self, isolate_map, targets):
1403 labels = []
1404 err = ''
1405
dprankecb4a2e242016-09-19 01:13:141406 for target in targets:
1407 if target == 'all':
1408 labels.append(target)
1409 elif target.startswith('//'):
1410 labels.append(target)
1411 else:
1412 if target in isolate_map:
thakis024d6f32017-05-16 23:21:421413 if isolate_map[target]['type'] == 'unknown':
dprankecb4a2e242016-09-19 01:13:141414 err += ('test target "%s" type is unknown\n' % target)
1415 else:
thakis024d6f32017-05-16 23:21:421416 labels.append(isolate_map[target]['label'])
dprankecb4a2e242016-09-19 01:13:141417 else:
1418 err += ('target "%s" not found in '
1419 '//testing/buildbot/gn_isolate_map.pyl\n' % target)
1420
1421 return err, labels
1422
dprankeeca4a782016-04-14 01:42:381423 def GNCmd(self, subcommand, path, *args):
Xiaoqian Dai89626492018-06-28 17:07:461424 if self.platform == 'linux2':
1425 subdir, exe = 'linux64', 'gn'
1426 elif self.platform == 'darwin':
1427 subdir, exe = 'mac', 'gn'
John Barbozaa1a12ef2018-07-11 13:51:251428 elif self.platform == 'aix6':
1429 subdir, exe = 'aix', 'gn'
Xiaoqian Dai89626492018-06-28 17:07:461430 else:
1431 subdir, exe = 'win', 'gn.exe'
1432
1433 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, exe)
dpranke10118bf2016-09-16 23:16:081434 return [gn_path, subcommand, path] + list(args)
dpranke9aba8b212016-09-16 22:52:521435
dprankecb4a2e242016-09-19 01:13:141436
Garrett Beatyb6cee042019-04-22 18:42:091437 def GNArgs(self, vals, expand_imports=False):
dpranke73ed0d62016-04-25 19:18:341438 if vals['cros_passthrough']:
1439 if not 'GN_ARGS' in os.environ:
1440 raise MBErr('MB is expecting GN_ARGS to be in the environment')
1441 gn_args = os.environ['GN_ARGS']
dpranke40260182016-04-27 04:45:161442 if not re.search('target_os.*=.*"chromeos"', gn_args):
dpranke39f3be02016-04-27 04:07:301443 raise MBErr('GN_ARGS is missing target_os = "chromeos": (GN_ARGS=%s)' %
dpranke73ed0d62016-04-25 19:18:341444 gn_args)
Ben Pastene74ad53772018-07-19 17:21:351445 if vals['gn_args']:
1446 gn_args += ' ' + vals['gn_args']
dpranke73ed0d62016-04-25 19:18:341447 else:
1448 gn_args = vals['gn_args']
1449
dpranked0c138b2016-04-13 18:28:471450 if self.args.goma_dir:
1451 gn_args += ' goma_dir="%s"' % self.args.goma_dir
dprankeeca4a782016-04-14 01:42:381452
agrieve41d21a72016-04-14 18:02:261453 android_version_code = self.args.android_version_code
1454 if android_version_code:
1455 gn_args += ' android_default_version_code="%s"' % android_version_code
1456
1457 android_version_name = self.args.android_version_name
1458 if android_version_name:
1459 gn_args += ' android_default_version_name="%s"' % android_version_name
1460
Garrett Beatyb6cee042019-04-22 18:42:091461 args_gn_lines = []
1462 parsed_gn_args = {}
dprankeeca4a782016-04-14 01:42:381463
Ben Pastene65ccf6132018-11-08 00:47:591464 # If we're using the Simple Chrome SDK, add a comment at the top that
1465 # points to the doc. This must happen after the gn_helpers.ToGNString()
1466 # call above since gn_helpers strips comments.
1467 if vals['cros_passthrough']:
Garrett Beatyb6cee042019-04-22 18:42:091468 args_gn_lines.extend([
Ben Pastene65ccf6132018-11-08 00:47:591469 '# These args are generated via the Simple Chrome SDK. See the link',
1470 '# below for more details:',
1471 '# https://chromium.googlesource.com/chromiumos/docs/+/master/simple_chrome_workflow.md', # pylint: disable=line-too-long
Garrett Beatyb6cee042019-04-22 18:42:091472 ])
Ben Pastene65ccf6132018-11-08 00:47:591473
dpranke9dd5e252016-04-14 04:23:091474 args_file = vals.get('args_file', None)
1475 if args_file:
Garrett Beatyb6cee042019-04-22 18:42:091476 if expand_imports:
1477 content = self.ReadFile(self.ToAbsPath(args_file))
1478 parsed_gn_args = gn_helpers.FromGNArgs(content)
1479 else:
1480 args_gn_lines.append('import("%s")' % args_file)
1481
1482 # Canonicalize the arg string into a sorted, newline-separated list
1483 # of key-value pairs, and de-dup the keys if need be so that only
1484 # the last instance of each arg is listed.
1485 parsed_gn_args.update(gn_helpers.FromGNArgs(gn_args))
1486 args_gn_lines.append(gn_helpers.ToGNString(parsed_gn_args))
1487
1488 return '\n'.join(args_gn_lines)
dprankefe4602312015-04-08 16:20:351489
dprankecb4a2e242016-09-19 01:13:141490 def GetIsolateCommand(self, target, vals):
kylechar50abf5a2016-11-29 16:03:071491 isolate_map = self.ReadIsolateMap()
1492
Scott Graham3be4b4162017-09-12 00:41:411493 is_android = 'target_os="android"' in vals['gn_args']
1494 is_fuchsia = 'target_os="fuchsia"' in vals['gn_args']
Caleb Raittof983d102019-06-21 23:05:021495 is_cros = 'target_os="chromeos"' in vals['gn_args']
Nico Webera7bc1cb2019-06-15 17:42:391496 is_simplechrome = vals.get('cros_passthrough', False)
1497 is_mac = self.platform == 'darwin'
Nico Weberd94b71a2018-02-22 22:00:301498 is_win = self.platform == 'win32' or 'target_os="win"' in vals['gn_args']
jbudoricke8428732016-02-02 02:17:061499
kylechar39705682017-01-19 14:37:231500 # This should be true if tests with type='windowed_test_launcher' are
1501 # expected to run using xvfb. For example, Linux Desktop, X11 CrOS and
msisovaea52732017-03-21 08:08:081502 # Ozone CrOS builds. Note that one Ozone build can be used to run differen
1503 # backends. Currently, tests are executed for the headless and X11 backends
1504 # and both can run under Xvfb.
1505 # TODO(tonikitoo,msisov,fwang): Find a way to run tests for the Wayland
1506 # backend.
Scott Graham3be4b4162017-09-12 00:41:411507 use_xvfb = self.platform == 'linux2' and not is_android and not is_fuchsia
dpranked8113582015-06-05 20:08:251508
1509 asan = 'is_asan=true' in vals['gn_args']
1510 msan = 'is_msan=true' in vals['gn_args']
1511 tsan = 'is_tsan=true' in vals['gn_args']
pcc46233c22017-06-20 22:11:411512 cfi_diag = 'use_cfi_diag=true' in vals['gn_args']
Yun Liu5764e0dc2019-10-24 01:50:221513 clang_coverage = 'use_clang_coverage=true' in vals['gn_args']
Yun Liuc0f2f732019-09-18 17:06:311514 java_coverage = 'use_jacoco_coverage=true' in vals['gn_args']
dpranked8113582015-06-05 20:08:251515
dprankecb4a2e242016-09-19 01:13:141516 test_type = isolate_map[target]['type']
Brian Sheedy234580e52019-09-10 17:42:511517 use_python3 = isolate_map[target].get('use_python3', False)
dprankefe0d35e2016-02-05 02:43:591518
dprankecb4a2e242016-09-19 01:13:141519 executable = isolate_map[target].get('executable', target)
bsheedy9c16ed62019-04-10 20:32:111520 executable_suffix = isolate_map[target].get(
1521 'executable_suffix', '.exe' if is_win else '')
dprankefe0d35e2016-02-05 02:43:591522
Brian Sheedy234580e52019-09-10 17:42:511523 if use_python3:
1524 cmdline = [ 'vpython3' ]
1525 extra_files = [ '../../.vpython3' ]
1526 else:
1527 cmdline = [ 'vpython' ]
1528 extra_files = [ '../../.vpython' ]
1529 extra_files += [
Andrii Shyshkalovc158e0102018-01-10 05:52:001530 '../../testing/test_env.py',
1531 ]
dpranked8113582015-06-05 20:08:251532
dprankecb4a2e242016-09-19 01:13:141533 if test_type == 'nontest':
1534 self.WriteFailureAndRaise('We should not be isolating %s.' % target,
1535 output_path=None)
1536
John Budorick93e88ac82019-04-12 18:39:111537 if test_type == 'generated_script':
Ben Pastenecb0fb412019-06-11 02:31:541538 script = isolate_map[target]['script']
1539 if self.platform == 'win32':
1540 script += '.bat'
Brian Sheedy234580e52019-09-10 17:42:511541 cmdline += [
John Budorick93e88ac82019-04-12 18:39:111542 '../../testing/test_env.py',
Ben Pastenecb0fb412019-06-11 02:31:541543 script,
John Budorick93e88ac82019-04-12 18:39:111544 ]
Roberto Carrillo1460da852018-12-14 17:10:391545 elif is_android and test_type != "script":
John Budorick8c4203042019-03-19 17:22:011546 if asan:
John Budorick31cdce62019-04-03 20:56:111547 cmdline += [os.path.join('bin', 'run_with_asan'), '--']
John Budorick8c4203042019-03-19 17:22:011548 cmdline += [
John Budorickfb97a852017-12-20 20:10:191549 '../../testing/test_env.py',
hzl9b15df52017-03-23 23:43:041550 '../../build/android/test_wrapper/logdog_wrapper.py',
1551 '--target', target,
hzl9ae14452017-04-04 23:38:021552 '--logdog-bin-cmd', '../../bin/logdog_butler',
hzlfc66094f2017-05-18 00:50:481553 '--store-tombstones']
Yun Liu5764e0dc2019-10-24 01:50:221554 if clang_coverage or java_coverage:
Yun Liu7cef1072019-06-27 21:22:191555 cmdline += ['--coverage-dir', '${ISOLATED_OUTDIR}']
Scott Graham3be4b4162017-09-12 00:41:411556 elif is_fuchsia and test_type != 'script':
Brian Sheedy234580e52019-09-10 17:42:511557 cmdline += [
John Budorickfb97a852017-12-20 20:10:191558 '../../testing/test_env.py',
1559 os.path.join('bin', 'run_%s' % target),
Wez9d5c0b52018-12-04 00:53:441560 '--test-launcher-bot-mode',
Sergey Ulanovd851243b2019-06-25 00:33:471561 '--system-log-file', '${ISOLATED_OUTDIR}/system_log'
John Budorickfb97a852017-12-20 20:10:191562 ]
Benjamin Pastene3bce864e2018-04-14 01:16:321563 elif is_simplechrome and test_type != 'script':
Brian Sheedy234580e52019-09-10 17:42:511564 cmdline += [
Benjamin Pastene3bce864e2018-04-14 01:16:321565 '../../testing/test_env.py',
1566 os.path.join('bin', 'run_%s' % target),
1567 ]
kylechar39705682017-01-19 14:37:231568 elif use_xvfb and test_type == 'windowed_test_launcher':
Andrii Shyshkalovc158e0102018-01-10 05:52:001569 extra_files.append('../../testing/xvfb.py')
Brian Sheedy234580e52019-09-10 17:42:511570 cmdline += [
Nico Webera7bc1cb2019-06-15 17:42:391571 '../../testing/xvfb.py',
1572 './' + str(executable) + executable_suffix,
1573 '--test-launcher-bot-mode',
1574 '--asan=%d' % asan,
1575 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1576 # supported.
1577 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021578 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1579 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
Nico Webera7bc1cb2019-06-15 17:42:391580 '--msan=%d' % msan,
1581 '--tsan=%d' % tsan,
1582 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471583 ]
1584 elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
Brian Sheedy234580e52019-09-10 17:42:511585 cmdline += [
dprankea55584f12015-07-22 00:52:471586 '../../testing/test_env.py',
dprankefe0d35e2016-02-05 02:43:591587 './' + str(executable) + executable_suffix,
dpranked8113582015-06-05 20:08:251588 '--test-launcher-bot-mode',
1589 '--asan=%d' % asan,
Caleb Raitto1fb2cc9e2019-06-14 01:04:231590 # Enable lsan when asan is enabled except on Windows where LSAN isn't
1591 # supported.
Nico Webera7bc1cb2019-06-15 17:42:391592 # TODO(https://crbug.com/948939): Enable on Mac once things pass.
Caleb Raittof983d102019-06-21 23:05:021593 # TODO(https://crbug.com/974478): Enable on ChromeOS once things pass.
1594 '--lsan=%d' % (asan and not is_mac and not is_win and not is_cros),
dpranked8113582015-06-05 20:08:251595 '--msan=%d' % msan,
1596 '--tsan=%d' % tsan,
pcc46233c22017-06-20 22:11:411597 '--cfi-diag=%d' % cfi_diag,
dprankea55584f12015-07-22 00:52:471598 ]
dpranke6abd8652015-08-28 03:21:111599 elif test_type == 'script':
Ben Pastene4534c39e2019-07-08 22:55:341600 # If we're testing a CrOS simplechrome build, assume we need to prepare a
1601 # DUT for testing. So prepend the command to run with the test wrapper.
Ben Pastene8ab6954d2018-05-04 04:08:241602 if is_simplechrome:
Ben Pastene908863c2019-07-25 16:20:031603 cmdline = [
1604 os.path.join('bin', 'cros_test_wrapper'),
1605 '--logs-dir=${ISOLATED_OUTDIR}',
1606 ]
Ben Pastene8ab6954d2018-05-04 04:08:241607 cmdline += [
dpranke6abd8652015-08-28 03:21:111608 '../../testing/test_env.py',
dprankecb4a2e242016-09-19 01:13:141609 '../../' + self.ToSrcRelPath(isolate_map[target]['script'])
dprankefe0d35e2016-02-05 02:43:591610 ]
Dirk Prankef24e6b22018-03-27 20:12:301611 elif test_type in ('raw', 'additional_compile_target'):
dprankea55584f12015-07-22 00:52:471612 cmdline = [
1613 './' + str(target) + executable_suffix,
dprankefe0d35e2016-02-05 02:43:591614 ]
dprankea55584f12015-07-22 00:52:471615 else:
1616 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
1617 % (target, test_type), output_path=None)
dpranked8113582015-06-05 20:08:251618
dprankecb4a2e242016-09-19 01:13:141619 cmdline += isolate_map[target].get('args', [])
dprankefe0d35e2016-02-05 02:43:591620
dpranked8113582015-06-05 20:08:251621 return cmdline, extra_files
1622
dpranke74559b52015-06-10 21:20:391623 def ToAbsPath(self, build_path, *comps):
dpranke8c2cfd32015-09-17 20:12:331624 return self.PathJoin(self.chromium_src_dir,
1625 self.ToSrcRelPath(build_path),
1626 *comps)
dpranked8113582015-06-05 20:08:251627
dprankeee5b51f62015-04-09 00:03:221628 def ToSrcRelPath(self, path):
1629 """Returns a relative path from the top of the repo."""
dpranke030d7a6d2016-03-26 17:23:501630 if path.startswith('//'):
1631 return path[2:].replace('/', self.sep)
1632 return self.RelPath(path, self.chromium_src_dir)
dprankefe4602312015-04-08 16:20:351633
Dirk Pranke0fd41bcd2015-06-19 00:05:501634 def RunGNAnalyze(self, vals):
dprankecb4a2e242016-09-19 01:13:141635 # Analyze runs before 'gn gen' now, so we need to run gn gen
Dirk Pranke0fd41bcd2015-06-19 00:05:501636 # in order to ensure that we have a build directory.
Takuto Ikuta9dffd7e2018-09-05 01:04:001637 ret = self.RunGNGen(vals, compute_inputs_for_analyze=True, check=False)
Dirk Pranke0fd41bcd2015-06-19 00:05:501638 if ret:
1639 return ret
1640
Dirk Prankef24e6b22018-03-27 20:12:301641 build_path = self.args.path
1642 input_path = self.args.input_path
dprankecb4a2e242016-09-19 01:13:141643 gn_input_path = input_path + '.gn'
Dirk Prankef24e6b22018-03-27 20:12:301644 output_path = self.args.output_path
dprankecb4a2e242016-09-19 01:13:141645 gn_output_path = output_path + '.gn'
1646
dpranke7837fc362015-11-19 03:54:161647 inp = self.ReadInputJSON(['files', 'test_targets',
1648 'additional_compile_targets'])
dprankecda00332015-04-11 04:18:321649 if self.args.verbose:
1650 self.Print()
1651 self.Print('analyze input:')
1652 self.PrintJSON(inp)
1653 self.Print()
1654
dpranke76734662015-04-16 02:17:501655
dpranke7c5f614d2015-07-22 23:43:391656 # This shouldn't normally happen, but could due to unusual race conditions,
1657 # like a try job that gets scheduled before a patch lands but runs after
1658 # the patch has landed.
1659 if not inp['files']:
1660 self.Print('Warning: No files modified in patch, bailing out early.')
dpranke7837fc362015-11-19 03:54:161661 self.WriteJSON({
1662 'status': 'No dependency',
1663 'compile_targets': [],
1664 'test_targets': [],
1665 }, output_path)
dpranke7c5f614d2015-07-22 23:43:391666 return 0
1667
dprankecb4a2e242016-09-19 01:13:141668 gn_inp = {}
dprankeb7b183f2017-04-24 23:50:161669 gn_inp['files'] = ['//' + f for f in inp['files'] if not f.startswith('//')]
dprankef61de2f2015-05-14 04:09:561670
dprankecb4a2e242016-09-19 01:13:141671 isolate_map = self.ReadIsolateMap()
1672 err, gn_inp['additional_compile_targets'] = self.MapTargetsToLabels(
1673 isolate_map, inp['additional_compile_targets'])
1674 if err:
1675 raise MBErr(err)
1676
1677 err, gn_inp['test_targets'] = self.MapTargetsToLabels(
1678 isolate_map, inp['test_targets'])
1679 if err:
1680 raise MBErr(err)
1681 labels_to_targets = {}
1682 for i, label in enumerate(gn_inp['test_targets']):
1683 labels_to_targets[label] = inp['test_targets'][i]
1684
dprankef61de2f2015-05-14 04:09:561685 try:
dprankecb4a2e242016-09-19 01:13:141686 self.WriteJSON(gn_inp, gn_input_path)
1687 cmd = self.GNCmd('analyze', build_path, gn_input_path, gn_output_path)
Debrian Figueroaae51d0d2019-07-22 18:04:111688 ret, output, _ = self.Run(cmd, force_verbose=True)
dprankecb4a2e242016-09-19 01:13:141689 if ret:
Debrian Figueroaae51d0d2019-07-22 18:04:111690 if self.args.json_output:
1691 # write errors to json.output
1692 self.WriteJSON({'output': output}, self.args.json_output)
dprankecb4a2e242016-09-19 01:13:141693 return ret
dpranke067d0142015-05-14 22:52:451694
dprankecb4a2e242016-09-19 01:13:141695 gn_outp_str = self.ReadFile(gn_output_path)
1696 try:
1697 gn_outp = json.loads(gn_outp_str)
1698 except Exception as e:
1699 self.Print("Failed to parse the JSON string GN returned: %s\n%s"
1700 % (repr(gn_outp_str), str(e)))
1701 raise
1702
1703 outp = {}
1704 if 'status' in gn_outp:
1705 outp['status'] = gn_outp['status']
1706 if 'error' in gn_outp:
1707 outp['error'] = gn_outp['error']
1708 if 'invalid_targets' in gn_outp:
1709 outp['invalid_targets'] = gn_outp['invalid_targets']
1710 if 'compile_targets' in gn_outp:
Dirk Pranke45165072017-11-08 04:57:491711 all_input_compile_targets = sorted(
1712 set(inp['test_targets'] + inp['additional_compile_targets']))
1713
1714 # If we're building 'all', we can throw away the rest of the targets
1715 # since they're redundant.
dpranke385a3102016-09-20 22:04:081716 if 'all' in gn_outp['compile_targets']:
1717 outp['compile_targets'] = ['all']
1718 else:
Dirk Pranke45165072017-11-08 04:57:491719 outp['compile_targets'] = gn_outp['compile_targets']
1720
1721 # crbug.com/736215: When GN returns targets back, for targets in
1722 # the default toolchain, GN will have generated a phony ninja
1723 # target matching the label, and so we can safely (and easily)
1724 # transform any GN label into the matching ninja target. For
1725 # targets in other toolchains, though, GN doesn't generate the
1726 # phony targets, and we don't know how to turn the labels into
1727 # compile targets. In this case, we also conservatively give up
1728 # and build everything. Probably the right thing to do here is
1729 # to have GN return the compile targets directly.
1730 if any("(" in target for target in outp['compile_targets']):
1731 self.Print('WARNING: targets with non-default toolchains were '
1732 'found, building everything instead.')
1733 outp['compile_targets'] = all_input_compile_targets
1734 else:
dpranke385a3102016-09-20 22:04:081735 outp['compile_targets'] = [
Dirk Pranke45165072017-11-08 04:57:491736 label.replace('//', '') for label in outp['compile_targets']]
1737
1738 # Windows has a maximum command line length of 8k; even Linux
1739 # maxes out at 128k; if analyze returns a *really long* list of
1740 # targets, we just give up and conservatively build everything instead.
1741 # Probably the right thing here is for ninja to support response
1742 # files as input on the command line
1743 # (see https://github.com/ninja-build/ninja/issues/1355).
Andrew Grieve79903c72020-02-07 18:31:111744 # Android targets use a lot of templates and often exceed 7kb.
1745 # https://crbug.com/946266
1746 max_cmd_length_kb = 64 if platform.system() == 'Linux' else 7
1747
1748 if len(' '.join(outp['compile_targets'])) > max_cmd_length_kb * 1024:
Dirk Pranke45165072017-11-08 04:57:491749 self.Print('WARNING: Too many compile targets were affected.')
1750 self.Print('WARNING: Building everything instead to avoid '
1751 'command-line length issues.')
1752 outp['compile_targets'] = all_input_compile_targets
1753
1754
dprankecb4a2e242016-09-19 01:13:141755 if 'test_targets' in gn_outp:
1756 outp['test_targets'] = [
1757 labels_to_targets[label] for label in gn_outp['test_targets']]
1758
1759 if self.args.verbose:
1760 self.Print()
1761 self.Print('analyze output:')
1762 self.PrintJSON(outp)
1763 self.Print()
1764
1765 self.WriteJSON(outp, output_path)
1766
dprankef61de2f2015-05-14 04:09:561767 finally:
dprankecb4a2e242016-09-19 01:13:141768 if self.Exists(gn_input_path):
1769 self.RemoveFile(gn_input_path)
1770 if self.Exists(gn_output_path):
1771 self.RemoveFile(gn_output_path)
dprankefe4602312015-04-08 16:20:351772
1773 return 0
1774
dpranked8113582015-06-05 20:08:251775 def ReadInputJSON(self, required_keys):
Dirk Prankef24e6b22018-03-27 20:12:301776 path = self.args.input_path
1777 output_path = self.args.output_path
dprankefe4602312015-04-08 16:20:351778 if not self.Exists(path):
dprankecda00332015-04-11 04:18:321779 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
dprankefe4602312015-04-08 16:20:351780
1781 try:
1782 inp = json.loads(self.ReadFile(path))
1783 except Exception as e:
1784 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
dprankecda00332015-04-11 04:18:321785 (path, e), output_path)
dpranked8113582015-06-05 20:08:251786
1787 for k in required_keys:
1788 if not k in inp:
1789 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
1790 output_path)
dprankefe4602312015-04-08 16:20:351791
1792 return inp
1793
dpranked5b2b9432015-06-23 16:55:301794 def WriteFailureAndRaise(self, msg, output_path):
1795 if output_path:
dprankee0547cd2015-09-15 01:27:401796 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
dprankefe4602312015-04-08 16:20:351797 raise MBErr(msg)
1798
dprankee0547cd2015-09-15 01:27:401799 def WriteJSON(self, obj, path, force_verbose=False):
dprankecda00332015-04-11 04:18:321800 try:
dprankee0547cd2015-09-15 01:27:401801 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
1802 force_verbose=force_verbose)
dprankecda00332015-04-11 04:18:321803 except Exception as e:
1804 raise MBErr('Error %s writing to the output path "%s"' %
1805 (e, path))
dprankefe4602312015-04-08 16:20:351806
aneeshmde50f472016-04-01 01:13:101807
dpranke3cec199c2015-09-22 23:29:021808 def PrintCmd(self, cmd, env):
1809 if self.platform == 'win32':
1810 env_prefix = 'set '
1811 env_quoter = QuoteForSet
1812 shell_quoter = QuoteForCmd
1813 else:
1814 env_prefix = ''
1815 env_quoter = pipes.quote
1816 shell_quoter = pipes.quote
1817
1818 def print_env(var):
1819 if env and var in env:
1820 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
1821
dprankeec079262016-06-07 02:21:201822 print_env('LLVM_FORCE_HEAD_REVISION')
dpranke3cec199c2015-09-22 23:29:021823
dpranke8c2cfd32015-09-17 20:12:331824 if cmd[0] == self.executable:
dprankefe4602312015-04-08 16:20:351825 cmd = ['python'] + cmd[1:]
dpranke3cec199c2015-09-22 23:29:021826 self.Print(*[shell_quoter(arg) for arg in cmd])
dprankefe4602312015-04-08 16:20:351827
dprankecda00332015-04-11 04:18:321828 def PrintJSON(self, obj):
1829 self.Print(json.dumps(obj, indent=2, sort_keys=True))
1830
dpranke751516a2015-10-03 01:11:341831 def Build(self, target):
Dirk Prankef24e6b22018-03-27 20:12:301832 build_dir = self.ToSrcRelPath(self.args.path)
Mike Meade9c100ff2018-03-30 23:09:381833 if self.platform == 'win32':
1834 # On Windows use the batch script since there is no exe
1835 ninja_cmd = ['autoninja.bat', '-C', build_dir]
1836 else:
1837 ninja_cmd = ['autoninja', '-C', build_dir]
dpranke751516a2015-10-03 01:11:341838 if self.args.jobs:
1839 ninja_cmd.extend(['-j', '%d' % self.args.jobs])
1840 ninja_cmd.append(target)
Dirk Pranke5f22a822019-05-23 22:55:251841 ret, _, _ = self.Run(ninja_cmd, buffer_output=False)
dpranke751516a2015-10-03 01:11:341842 return ret
1843
Stephen Martinis1f134492019-12-06 23:27:411844 def Run(self, cmd, env=None, force_verbose=True, buffer_output=True):
dprankefe4602312015-04-08 16:20:351845 # This function largely exists so it can be overridden for testing.
dprankee0547cd2015-09-15 01:27:401846 if self.args.dryrun or self.args.verbose or force_verbose:
dpranke3cec199c2015-09-22 23:29:021847 self.PrintCmd(cmd, env)
dprankefe4602312015-04-08 16:20:351848 if self.args.dryrun:
1849 return 0, '', ''
dprankee0547cd2015-09-15 01:27:401850
Stephen Martinis1f134492019-12-06 23:27:411851 ret, out, err = self.Call(cmd, env=env, buffer_output=buffer_output)
dprankee0547cd2015-09-15 01:27:401852 if self.args.verbose or force_verbose:
dpranke751516a2015-10-03 01:11:341853 if ret:
1854 self.Print(' -> returned %d' % ret)
dprankefe4602312015-04-08 16:20:351855 if out:
Debrian Figueroaae582232019-07-17 01:54:451856 # This is the error seen on the logs
dprankeee5b51f62015-04-09 00:03:221857 self.Print(out, end='')
dprankefe4602312015-04-08 16:20:351858 if err:
dprankeee5b51f62015-04-09 00:03:221859 self.Print(err, end='', file=sys.stderr)
dprankefe4602312015-04-08 16:20:351860 return ret, out, err
1861
Stephen Martiniscd377012019-10-18 17:40:461862 def Call(self, cmd, env=None, buffer_output=True, stdin=None):
dpranke751516a2015-10-03 01:11:341863 if buffer_output:
1864 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1865 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
Stephen Martiniscd377012019-10-18 17:40:461866 env=env, stdin=subprocess.PIPE)
1867 out, err = p.communicate(input=stdin)
dpranke751516a2015-10-03 01:11:341868 else:
1869 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
1870 env=env)
1871 p.wait()
1872 out = err = ''
dprankefe4602312015-04-08 16:20:351873 return p.returncode, out, err
1874
1875 def ExpandUser(self, path):
1876 # This function largely exists so it can be overridden for testing.
1877 return os.path.expanduser(path)
1878
1879 def Exists(self, path):
1880 # This function largely exists so it can be overridden for testing.
1881 return os.path.exists(path)
1882
dpranke867bcf4a2016-03-14 22:28:321883 def Fetch(self, url):
dpranke030d7a6d2016-03-26 17:23:501884 # This function largely exists so it can be overridden for testing.
dpranke867bcf4a2016-03-14 22:28:321885 f = urllib2.urlopen(url)
1886 contents = f.read()
1887 f.close()
1888 return contents
1889
dprankec3441d12015-06-23 23:01:351890 def MaybeMakeDirectory(self, path):
1891 try:
1892 os.makedirs(path)
1893 except OSError, e:
1894 if e.errno != errno.EEXIST:
1895 raise
1896
dpranke8c2cfd32015-09-17 20:12:331897 def PathJoin(self, *comps):
1898 # This function largely exists so it can be overriden for testing.
1899 return os.path.join(*comps)
1900
dpranke030d7a6d2016-03-26 17:23:501901 def Print(self, *args, **kwargs):
1902 # This function largely exists so it can be overridden for testing.
1903 print(*args, **kwargs)
aneeshmde50f472016-04-01 01:13:101904 if kwargs.get('stream', sys.stdout) == sys.stdout:
1905 sys.stdout.flush()
dpranke030d7a6d2016-03-26 17:23:501906
dprankefe4602312015-04-08 16:20:351907 def ReadFile(self, path):
1908 # This function largely exists so it can be overriden for testing.
1909 with open(path) as fp:
1910 return fp.read()
1911
dpranke030d7a6d2016-03-26 17:23:501912 def RelPath(self, path, start='.'):
1913 # This function largely exists so it can be overriden for testing.
1914 return os.path.relpath(path, start)
1915
dprankef61de2f2015-05-14 04:09:561916 def RemoveFile(self, path):
1917 # This function largely exists so it can be overriden for testing.
1918 os.remove(path)
1919
dprankec161aa92015-09-14 20:21:131920 def RemoveDirectory(self, abs_path):
dpranke8c2cfd32015-09-17 20:12:331921 if self.platform == 'win32':
dprankec161aa92015-09-14 20:21:131922 # In other places in chromium, we often have to retry this command
1923 # because we're worried about other processes still holding on to
1924 # file handles, but when MB is invoked, it will be early enough in the
1925 # build that their should be no other processes to interfere. We
1926 # can change this if need be.
1927 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
1928 else:
1929 shutil.rmtree(abs_path, ignore_errors=True)
1930
Dirk Prankef24e6b22018-03-27 20:12:301931 def TempDir(self):
1932 # This function largely exists so it can be overriden for testing.
1933 return tempfile.mkdtemp(prefix='mb_')
1934
dprankef61de2f2015-05-14 04:09:561935 def TempFile(self, mode='w'):
1936 # This function largely exists so it can be overriden for testing.
1937 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
1938
dprankee0547cd2015-09-15 01:27:401939 def WriteFile(self, path, contents, force_verbose=False):
dprankefe4602312015-04-08 16:20:351940 # This function largely exists so it can be overriden for testing.
dprankee0547cd2015-09-15 01:27:401941 if self.args.dryrun or self.args.verbose or force_verbose:
dpranked5b2b9432015-06-23 16:55:301942 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
dprankefe4602312015-04-08 16:20:351943 with open(path, 'w') as fp:
1944 return fp.write(contents)
1945
dprankef61de2f2015-05-14 04:09:561946
Stephen Martiniscd377012019-10-18 17:40:461947class LedResult(object):
1948 """Holds the result of a led operation. Can be chained using |then|."""
1949
1950 def __init__(self, result, run_cmd):
1951 self._result = result
1952 self._run_cmd = run_cmd
1953
1954 @property
1955 def result(self):
1956 """The mutable result data of the previous led call as decoded JSON."""
1957 return self._result
1958
1959 def then(self, *cmd):
1960 """Invoke led, passing it the current `result` data as input.
1961
1962 Returns another LedResult object with the output of the command.
1963 """
1964 return self.__class__(
1965 self._run_cmd(self._result, cmd), self._run_cmd)
1966
1967
Greg Guterman1492aeb42020-01-14 22:59:141968def FlattenConfig(config_pool, mixin_pool, config):
1969 mixins = config_pool[config]
1970 vals = DefaultVals()
1971
1972 visited = []
1973 FlattenMixins(mixin_pool, mixins, vals, visited)
1974 return vals
1975
1976
1977def FlattenMixins(mixin_pool, mixins_to_flatten, vals, visited):
1978 for m in mixins_to_flatten:
1979 if m not in mixin_pool:
1980 raise MBErr('Unknown mixin "%s"' % m)
1981
1982 visited.append(m)
1983
1984 mixin_vals = mixin_pool[m]
1985
1986 if 'cros_passthrough' in mixin_vals:
1987 vals['cros_passthrough'] = mixin_vals['cros_passthrough']
1988 if 'args_file' in mixin_vals:
1989 if vals['args_file']:
1990 raise MBErr('args_file specified multiple times in mixins '
1991 'for mixin %s' % m)
1992 vals['args_file'] = mixin_vals['args_file']
1993 if 'gn_args' in mixin_vals:
1994 if vals['gn_args']:
1995 vals['gn_args'] += ' ' + mixin_vals['gn_args']
1996 else:
1997 vals['gn_args'] = mixin_vals['gn_args']
1998
1999 if 'mixins' in mixin_vals:
2000 FlattenMixins(mixin_pool, mixin_vals['mixins'], vals, visited)
2001 return vals
2002
2003
Stephen Martiniscd377012019-10-18 17:40:462004
dprankefe4602312015-04-08 16:20:352005class MBErr(Exception):
2006 pass
2007
2008
dpranke3cec199c2015-09-22 23:29:022009# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
2010# details of this next section, which handles escaping command lines
2011# so that they can be copied and pasted into a cmd window.
2012UNSAFE_FOR_SET = set('^<>&|')
2013UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
2014ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
2015
2016
2017def QuoteForSet(arg):
2018 if any(a in UNSAFE_FOR_SET for a in arg):
2019 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
2020 return arg
2021
2022
2023def QuoteForCmd(arg):
2024 # First, escape the arg so that CommandLineToArgvW will parse it properly.
dpranke3cec199c2015-09-22 23:29:022025 if arg == '' or ' ' in arg or '"' in arg:
2026 quote_re = re.compile(r'(\\*)"')
2027 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
2028
2029 # Then check to see if the arg contains any metacharacters other than
2030 # double quotes; if it does, quote everything (including the double
2031 # quotes) for safety.
2032 if any(a in UNSAFE_FOR_CMD for a in arg):
2033 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
2034 return arg
2035
2036
dprankefe4602312015-04-08 16:20:352037if __name__ == '__main__':
dpranke255085e2016-03-16 05:23:592038 sys.exit(main(sys.argv[1:]))