Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(633)

Side by Side Diff: tools/mb/mb.py

Issue 1366093004: Merge latest MB from trunk back to M46. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@branch_2490
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tools/mb/docs/user_guide.md ('k') | tools/mb/mb_config.pyl » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2015 The Chromium Authors. All rights reserved. 2 # Copyright 2015 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """MB - the Meta-Build wrapper around GYP and GN 6 """MB - the Meta-Build wrapper around GYP and GN
7 7
8 MB is a wrapper script for GYP and GN that can be used to generate build files 8 MB is a wrapper script for GYP and GN that can be used to generate build files
9 for sets of canned configurations and analyze them. 9 for sets of canned configurations and analyze them.
10 """ 10 """
11 11
12 from __future__ import print_function 12 from __future__ import print_function
13 13
14 import argparse 14 import argparse
15 import ast 15 import ast
16 import errno 16 import errno
17 import json 17 import json
18 import os 18 import os
19 import pipes 19 import pipes
20 import pprint 20 import pprint
21 import shlex 21 import re
22 import shutil 22 import shutil
23 import sys 23 import sys
24 import subprocess 24 import subprocess
25 import tempfile 25 import tempfile
26 26
27 def main(args): 27 def main(args):
28 mbw = MetaBuildWrapper() 28 mbw = MetaBuildWrapper()
29 mbw.ParseArgs(args) 29 mbw.ParseArgs(args)
30 return mbw.args.func() 30 return mbw.args.func()
31 31
32 32
33 class MetaBuildWrapper(object): 33 class MetaBuildWrapper(object):
34 def __init__(self): 34 def __init__(self):
35 p = os.path 35 p = os.path
36 d = os.path.dirname 36 d = os.path.dirname
37 self.chromium_src_dir = p.normpath(d(d(d(p.abspath(__file__))))) 37 self.chromium_src_dir = p.normpath(d(d(d(p.abspath(__file__)))))
38 self.default_config = p.join(self.chromium_src_dir, 'tools', 'mb', 38 self.default_config = p.join(self.chromium_src_dir, 'tools', 'mb',
39 'mb_config.pyl') 39 'mb_config.pyl')
40 self.executable = sys.executable
40 self.platform = sys.platform 41 self.platform = sys.platform
42 self.sep = os.sep
41 self.args = argparse.Namespace() 43 self.args = argparse.Namespace()
42 self.configs = {} 44 self.configs = {}
43 self.masters = {} 45 self.masters = {}
44 self.mixins = {} 46 self.mixins = {}
45 self.private_configs = [] 47 self.private_configs = []
46 self.common_dev_configs = [] 48 self.common_dev_configs = []
47 self.unsupported_configs = [] 49 self.unsupported_configs = []
48 50
49 def ParseArgs(self, argv): 51 def ParseArgs(self, argv):
50 def AddCommonOptions(subp): 52 def AddCommonOptions(subp):
51 subp.add_argument('-b', '--builder', 53 subp.add_argument('-b', '--builder',
52 help='builder name to look up config from') 54 help='builder name to look up config from')
53 subp.add_argument('-m', '--master', 55 subp.add_argument('-m', '--master',
54 help='master name to look up config from') 56 help='master name to look up config from')
55 subp.add_argument('-c', '--config', 57 subp.add_argument('-c', '--config',
56 help='configuration to analyze') 58 help='configuration to analyze')
57 subp.add_argument('-f', '--config-file', metavar='PATH', 59 subp.add_argument('-f', '--config-file', metavar='PATH',
58 default=self.default_config, 60 default=self.default_config,
59 help='path to config file ' 61 help='path to config file '
60 '(default is //tools/mb/mb_config.pyl)') 62 '(default is //tools/mb/mb_config.pyl)')
61 subp.add_argument('-g', '--goma-dir', default=self.ExpandUser('~/goma'), 63 subp.add_argument('-g', '--goma-dir', default=self.ExpandUser('~/goma'),
62 help='path to goma directory (default is %(default)s).') 64 help='path to goma directory (default is %(default)s).')
63 subp.add_argument('-n', '--dryrun', action='store_true', 65 subp.add_argument('-n', '--dryrun', action='store_true',
64 help='Do a dry run (i.e., do nothing, just print ' 66 help='Do a dry run (i.e., do nothing, just print '
65 'the commands that will run)') 67 'the commands that will run)')
66 subp.add_argument('-q', '--quiet', action='store_true', 68 subp.add_argument('-v', '--verbose', action='store_true',
67 help='Do not print anything on success, ' 69 help='verbose logging')
68 'just return an exit code.')
69 subp.add_argument('-v', '--verbose', action='count',
70 help='verbose logging (may specify multiple times).')
71 70
72 parser = argparse.ArgumentParser(prog='mb') 71 parser = argparse.ArgumentParser(prog='mb')
73 subps = parser.add_subparsers() 72 subps = parser.add_subparsers()
74 73
75 subp = subps.add_parser('analyze', 74 subp = subps.add_parser('analyze',
76 help='analyze whether changes to a set of files ' 75 help='analyze whether changes to a set of files '
77 'will cause a set of binaries to be rebuilt.') 76 'will cause a set of binaries to be rebuilt.')
78 AddCommonOptions(subp) 77 AddCommonOptions(subp)
79 subp.add_argument('--swarming-targets-file', 78 subp.add_argument('--swarming-targets-file',
80 help='save runtime dependencies for targets listed ' 79 help='save runtime dependencies for targets listed '
(...skipping 23 matching lines...) Expand all
104 'builder') 103 'builder')
105 AddCommonOptions(subp) 104 AddCommonOptions(subp)
106 subp.set_defaults(func=self.CmdLookup) 105 subp.set_defaults(func=self.CmdLookup)
107 106
108 subp = subps.add_parser('validate', 107 subp = subps.add_parser('validate',
109 help='validate the config file') 108 help='validate the config file')
110 subp.add_argument('-f', '--config-file', metavar='PATH', 109 subp.add_argument('-f', '--config-file', metavar='PATH',
111 default=self.default_config, 110 default=self.default_config,
112 help='path to config file ' 111 help='path to config file '
113 '(default is //tools/mb/mb_config.pyl)') 112 '(default is //tools/mb/mb_config.pyl)')
114 subp.add_argument('-q', '--quiet', action='store_true',
115 help='Do not print anything on success, '
116 'just return an exit code.')
117 subp.set_defaults(func=self.CmdValidate) 113 subp.set_defaults(func=self.CmdValidate)
118 114
119 subp = subps.add_parser('help', 115 subp = subps.add_parser('help',
120 help='Get help on a subcommand.') 116 help='Get help on a subcommand.')
121 subp.add_argument(nargs='?', action='store', dest='subcommand', 117 subp.add_argument(nargs='?', action='store', dest='subcommand',
122 help='The command to get help for.') 118 help='The command to get help for.')
123 subp.set_defaults(func=self.CmdHelp) 119 subp.set_defaults(func=self.CmdHelp)
124 120
125 self.args = parser.parse_args(argv) 121 self.args = parser.parse_args(argv)
126 122
127 def CmdAnalyze(self): 123 def CmdAnalyze(self):
128 vals = self.GetConfig() 124 vals = self.GetConfig()
129 if vals['type'] == 'gn': 125 if vals['type'] == 'gn':
130 return self.RunGNAnalyze(vals) 126 return self.RunGNAnalyze(vals)
131 elif vals['type'] == 'gyp': 127 elif vals['type'] == 'gyp':
132 return self.RunGYPAnalyze(vals) 128 return self.RunGYPAnalyze(vals)
133 else: 129 else:
134 raise MBErr('Unknown meta-build type "%s"' % vals['type']) 130 raise MBErr('Unknown meta-build type "%s"' % vals['type'])
135 131
136 def CmdGen(self): 132 def CmdGen(self):
137 vals = self.GetConfig() 133 vals = self.GetConfig()
134
135 self.ClobberIfNeeded(vals)
136
138 if vals['type'] == 'gn': 137 if vals['type'] == 'gn':
139 return self.RunGNGen(vals) 138 return self.RunGNGen(vals)
140 if vals['type'] == 'gyp': 139 if vals['type'] == 'gyp':
141 return self.RunGYPGen(vals) 140 return self.RunGYPGen(vals)
142 141
143 raise MBErr('Unknown meta-build type "%s"' % vals['type']) 142 raise MBErr('Unknown meta-build type "%s"' % vals['type'])
144 143
145 def CmdLookup(self): 144 def CmdLookup(self):
146 vals = self.GetConfig() 145 vals = self.GetConfig()
147 if vals['type'] == 'gn': 146 if vals['type'] == 'gn':
148 cmd = self.GNCmd('gen', '<path>', vals['gn_args']) 147 cmd = self.GNCmd('gen', '_path_', vals['gn_args'])
148 env = None
149 elif vals['type'] == 'gyp': 149 elif vals['type'] == 'gyp':
150 if vals['gyp_crosscompile']: 150 if vals['gyp_crosscompile']:
151 self.Print('GYP_CROSSCOMPILE=1') 151 self.Print('GYP_CROSSCOMPILE=1')
152 cmd = self.GYPCmd('<path>', vals['gyp_defines'], vals['gyp_config']) 152 cmd, env = self.GYPCmd('_path_', vals['gyp_defines'])
153 else: 153 else:
154 raise MBErr('Unknown meta-build type "%s"' % vals['type']) 154 raise MBErr('Unknown meta-build type "%s"' % vals['type'])
155 155
156 self.PrintCmd(cmd) 156 self.PrintCmd(cmd, env)
157 return 0 157 return 0
158 158
159 def CmdHelp(self): 159 def CmdHelp(self):
160 if self.args.subcommand: 160 if self.args.subcommand:
161 self.ParseArgs([self.args.subcommand, '--help']) 161 self.ParseArgs([self.args.subcommand, '--help'])
162 else: 162 else:
163 self.ParseArgs(['--help']) 163 self.ParseArgs(['--help'])
164 164
165 def CmdValidate(self): 165 def CmdValidate(self):
166 errs = [] 166 errs = []
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
225 225
226 # Check that every mixin defined is actually referenced somewhere. 226 # Check that every mixin defined is actually referenced somewhere.
227 for mixin in self.mixins: 227 for mixin in self.mixins:
228 if not mixin in referenced_mixins: 228 if not mixin in referenced_mixins:
229 errs.append('Unreferenced mixin "%s".' % mixin) 229 errs.append('Unreferenced mixin "%s".' % mixin)
230 230
231 if errs: 231 if errs:
232 raise MBErr(('mb config file %s has problems:' % self.args.config_file) + 232 raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
233 '\n ' + '\n '.join(errs)) 233 '\n ' + '\n '.join(errs))
234 234
235 if not self.args.quiet: 235 self.Print('mb config file %s looks ok.' % self.args.config_file)
236 self.Print('mb config file %s looks ok.' % self.args.config_file)
237 return 0 236 return 0
238 237
239 def GetConfig(self): 238 def GetConfig(self):
240 self.ReadConfigFile() 239 self.ReadConfigFile()
241 config = self.ConfigFromArgs() 240 config = self.ConfigFromArgs()
242 if not config in self.configs: 241 if not config in self.configs:
243 raise MBErr('Config "%s" not found in %s' % 242 raise MBErr('Config "%s" not found in %s' %
244 (config, self.args.config_file)) 243 (config, self.args.config_file))
245 244
246 return self.FlattenConfig(config) 245 return self.FlattenConfig(config)
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
282 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' % 281 raise MBErr('Builder name "%s" not found under masters[%s] in "%s"' %
283 (self.args.builder, self.args.master, self.args.config_file)) 282 (self.args.builder, self.args.master, self.args.config_file))
284 283
285 return self.masters[self.args.master][self.args.builder] 284 return self.masters[self.args.master][self.args.builder]
286 285
287 def FlattenConfig(self, config): 286 def FlattenConfig(self, config):
288 mixins = self.configs[config] 287 mixins = self.configs[config]
289 vals = { 288 vals = {
290 'type': None, 289 'type': None,
291 'gn_args': [], 290 'gn_args': [],
292 'gyp_config': [], 291 'gyp_defines': '',
293 'gyp_defines': [],
294 'gyp_crosscompile': False, 292 'gyp_crosscompile': False,
295 } 293 }
296 294
297 visited = [] 295 visited = []
298 self.FlattenMixins(mixins, vals, visited) 296 self.FlattenMixins(mixins, vals, visited)
299 return vals 297 return vals
300 298
301 def FlattenMixins(self, mixins, vals, visited): 299 def FlattenMixins(self, mixins, vals, visited):
302 for m in mixins: 300 for m in mixins:
303 if m not in self.mixins: 301 if m not in self.mixins:
304 raise MBErr('Unknown mixin "%s"' % m) 302 raise MBErr('Unknown mixin "%s"' % m)
305 303
306 # TODO: check for cycles in mixins. 304 # TODO: check for cycles in mixins.
307 305
308 visited.append(m) 306 visited.append(m)
309 307
310 mixin_vals = self.mixins[m] 308 mixin_vals = self.mixins[m]
311 if 'type' in mixin_vals: 309 if 'type' in mixin_vals:
312 vals['type'] = mixin_vals['type'] 310 vals['type'] = mixin_vals['type']
313 if 'gn_args' in mixin_vals: 311 if 'gn_args' in mixin_vals:
314 if vals['gn_args']: 312 if vals['gn_args']:
315 vals['gn_args'] += ' ' + mixin_vals['gn_args'] 313 vals['gn_args'] += ' ' + mixin_vals['gn_args']
316 else: 314 else:
317 vals['gn_args'] = mixin_vals['gn_args'] 315 vals['gn_args'] = mixin_vals['gn_args']
318 if 'gyp_config' in mixin_vals:
319 vals['gyp_config'] = mixin_vals['gyp_config']
320 if 'gyp_crosscompile' in mixin_vals: 316 if 'gyp_crosscompile' in mixin_vals:
321 vals['gyp_crosscompile'] = mixin_vals['gyp_crosscompile'] 317 vals['gyp_crosscompile'] = mixin_vals['gyp_crosscompile']
322 if 'gyp_defines' in mixin_vals: 318 if 'gyp_defines' in mixin_vals:
323 if vals['gyp_defines']: 319 if vals['gyp_defines']:
324 vals['gyp_defines'] += ' ' + mixin_vals['gyp_defines'] 320 vals['gyp_defines'] += ' ' + mixin_vals['gyp_defines']
325 else: 321 else:
326 vals['gyp_defines'] = mixin_vals['gyp_defines'] 322 vals['gyp_defines'] = mixin_vals['gyp_defines']
327 if 'mixins' in mixin_vals: 323 if 'mixins' in mixin_vals:
328 self.FlattenMixins(mixin_vals['mixins'], vals, visited) 324 self.FlattenMixins(mixin_vals['mixins'], vals, visited)
329 return vals 325 return vals
330 326
327 def ClobberIfNeeded(self, vals):
328 path = self.args.path[0]
329 build_dir = self.ToAbsPath(path)
330 mb_type_path = self.PathJoin(build_dir, 'mb_type')
331 needs_clobber = False
332 new_mb_type = vals['type']
333 if self.Exists(build_dir):
334 if self.Exists(mb_type_path):
335 old_mb_type = self.ReadFile(mb_type_path)
336 if old_mb_type != new_mb_type:
337 self.Print("Build type mismatch: was %s, will be %s, clobbering %s" %
338 (old_mb_type, new_mb_type, path))
339 needs_clobber = True
340 else:
341 # There is no 'mb_type' file in the build directory, so this probably
342 # means that the prior build(s) were not done through mb, and we
343 # have no idea if this was a GYP build or a GN build. Clobber it
344 # to be safe.
345 self.Print("%s/mb_type missing, clobbering to be safe" % path)
346 needs_clobber = True
347
348 if self.args.dryrun:
349 return
350
351 if needs_clobber:
352 self.RemoveDirectory(build_dir)
353
354 self.MaybeMakeDirectory(build_dir)
355 self.WriteFile(mb_type_path, new_mb_type)
356
331 def RunGNGen(self, vals): 357 def RunGNGen(self, vals):
332 path = self.args.path[0] 358 path = self.args.path[0]
333 359
334 cmd = self.GNCmd('gen', path, vals['gn_args']) 360 cmd = self.GNCmd('gen', path, vals['gn_args'], extra_args=['--check'])
335 361
336 swarming_targets = [] 362 swarming_targets = []
337 if self.args.swarming_targets_file: 363 if self.args.swarming_targets_file:
338 # We need GN to generate the list of runtime dependencies for 364 # We need GN to generate the list of runtime dependencies for
339 # the compile targets listed (one per line) in the file so 365 # the compile targets listed (one per line) in the file so
340 # we can run them via swarming. We use ninja_to_gn.pyl to convert 366 # we can run them via swarming. We use ninja_to_gn.pyl to convert
341 # the compile targets to the matching GN labels. 367 # the compile targets to the matching GN labels.
342 contents = self.ReadFile(self.args.swarming_targets_file) 368 contents = self.ReadFile(self.args.swarming_targets_file)
343 swarming_targets = contents.splitlines() 369 swarming_targets = contents.splitlines()
344 gn_isolate_map = ast.literal_eval(self.ReadFile(os.path.join( 370 gn_isolate_map = ast.literal_eval(self.ReadFile(self.PathJoin(
345 self.chromium_src_dir, 'testing', 'buildbot', 'gn_isolate_map.pyl'))) 371 self.chromium_src_dir, 'testing', 'buildbot', 'gn_isolate_map.pyl')))
346 gn_labels = [] 372 gn_labels = []
347 for target in swarming_targets: 373 for target in swarming_targets:
348 if not target in gn_isolate_map: 374 if not target in gn_isolate_map:
349 raise MBErr('test target "%s" not found in %s' % 375 raise MBErr('test target "%s" not found in %s' %
350 (target, '//testing/buildbot/gn_isolate_map.pyl')) 376 (target, '//testing/buildbot/gn_isolate_map.pyl'))
351 gn_labels.append(gn_isolate_map[target]['label']) 377 gn_labels.append(gn_isolate_map[target]['label'])
352 378
353 gn_runtime_deps_path = self.ToAbsPath(path, 'runtime_deps') 379 gn_runtime_deps_path = self.ToAbsPath(path, 'runtime_deps')
354 380
355 # Since GN hasn't run yet, the build directory may not even exist. 381 # Since GN hasn't run yet, the build directory may not even exist.
356 self.MaybeMakeDirectory(self.ToAbsPath(path)) 382 self.MaybeMakeDirectory(self.ToAbsPath(path))
357 383
358 self.WriteFile(gn_runtime_deps_path, '\n'.join(gn_labels) + '\n') 384 self.WriteFile(gn_runtime_deps_path, '\n'.join(gn_labels) + '\n')
359 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path) 385 cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
360 386
361 ret, _, _ = self.Run(cmd) 387 ret, _, _ = self.Run(cmd)
388 if ret:
389 # If `gn gen` failed, we should exit early rather than trying to
390 # generate isolates. Run() will have already logged any error output.
391 self.Print('GN gen failed: %d' % ret)
392 return ret
362 393
363 for target in swarming_targets: 394 for target in swarming_targets:
364 if gn_isolate_map[target]['type'] == 'gpu_browser_test': 395 if gn_isolate_map[target]['type'] == 'gpu_browser_test':
365 runtime_deps_target = 'browser_tests' 396 runtime_deps_target = 'browser_tests'
397 elif gn_isolate_map[target]['type'] == 'script':
398 # For script targets, the build target is usually a group,
399 # for which gn generates the runtime_deps next to the stamp file
400 # for the label, which lives under the obj/ directory.
401 label = gn_isolate_map[target]['label']
402 runtime_deps_target = 'obj/%s.stamp' % label.replace(':', '/')
366 else: 403 else:
367 runtime_deps_target = target 404 runtime_deps_target = target
368 if sys.platform == 'win32': 405 if self.platform == 'win32':
369 deps_path = self.ToAbsPath(path, 406 deps_path = self.ToAbsPath(path,
370 runtime_deps_target + '.exe.runtime_deps') 407 runtime_deps_target + '.exe.runtime_deps')
371 else: 408 else:
372 deps_path = self.ToAbsPath(path, 409 deps_path = self.ToAbsPath(path,
373 runtime_deps_target + '.runtime_deps') 410 runtime_deps_target + '.runtime_deps')
374 if not self.Exists(deps_path): 411 if not self.Exists(deps_path):
375 raise MBErr('did not generate %s' % deps_path) 412 raise MBErr('did not generate %s' % deps_path)
376 413
377 command, extra_files = self.GetIsolateCommand(target, vals, 414 command, extra_files = self.GetIsolateCommand(target, vals,
378 gn_isolate_map) 415 gn_isolate_map)
379 416
380 runtime_deps = self.ReadFile(deps_path).splitlines() 417 runtime_deps = self.ReadFile(deps_path).splitlines()
381 418
382 isolate_path = self.ToAbsPath(path, target + '.isolate') 419 isolate_path = self.ToAbsPath(path, target + '.isolate')
383 self.WriteFile(isolate_path, 420 self.WriteFile(isolate_path,
384 pprint.pformat({ 421 pprint.pformat({
385 'variables': { 422 'variables': {
386 'command': command, 423 'command': command,
387 'files': sorted(runtime_deps + extra_files), 424 'files': sorted(runtime_deps + extra_files),
388 } 425 }
389 }) + '\n') 426 }) + '\n')
390 427
391 self.WriteJSON( 428 self.WriteJSON(
392 { 429 {
393 'args': [ 430 'args': [
394 '--isolated', 431 '--isolated',
395 self.ToSrcRelPath('%s%s%s.isolated' % (path, os.sep, target)), 432 self.ToSrcRelPath('%s%s%s.isolated' % (path, self.sep, target)),
396 '--isolate', 433 '--isolate',
397 self.ToSrcRelPath('%s%s%s.isolate' % (path, os.sep, target)), 434 self.ToSrcRelPath('%s%s%s.isolate' % (path, self.sep, target)),
398 ], 435 ],
399 'dir': self.chromium_src_dir, 436 'dir': self.chromium_src_dir,
400 'version': 1, 437 'version': 1,
401 }, 438 },
402 isolate_path + 'd.gen.json', 439 isolate_path + 'd.gen.json',
403 ) 440 )
404 441
405
406 return ret 442 return ret
407 443
408 def GNCmd(self, subcommand, path, gn_args=''): 444 def GNCmd(self, subcommand, path, gn_args='', extra_args=None):
409 if self.platform == 'linux2': 445 if self.platform == 'linux2':
410 gn_path = os.path.join(self.chromium_src_dir, 'buildtools', 'linux64', 446 subdir = 'linux64'
411 'gn')
412 elif self.platform == 'darwin': 447 elif self.platform == 'darwin':
413 gn_path = os.path.join(self.chromium_src_dir, 'buildtools', 'mac', 448 subdir = 'mac'
414 'gn')
415 else: 449 else:
416 gn_path = os.path.join(self.chromium_src_dir, 'buildtools', 'win', 450 subdir = 'win'
417 'gn.exe') 451 gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, 'gn')
418 452
419 cmd = [gn_path, subcommand, path] 453 cmd = [gn_path, subcommand, path]
420 gn_args = gn_args.replace("$(goma_dir)", self.args.goma_dir) 454 gn_args = gn_args.replace("$(goma_dir)", self.args.goma_dir)
421 if gn_args: 455 if gn_args:
422 cmd.append('--args=%s' % gn_args) 456 cmd.append('--args=%s' % gn_args)
457 if extra_args:
458 cmd.extend(extra_args)
423 return cmd 459 return cmd
424 460
425 def RunGYPGen(self, vals): 461 def RunGYPGen(self, vals):
426 path = self.args.path[0] 462 path = self.args.path[0]
427 463
428 output_dir, gyp_config = self.ParseGYPConfigPath(path) 464 output_dir = self.ParseGYPConfigPath(path)
429 if gyp_config != vals['gyp_config']: 465 cmd, env = self.GYPCmd(output_dir, vals['gyp_defines'])
430 raise MBErr('The last component of the path (%s) must match the '
431 'GYP configuration specified in the config (%s), and '
432 'it does not.' % (gyp_config, vals['gyp_config']))
433 cmd = self.GYPCmd(output_dir, vals['gyp_defines'], config=gyp_config)
434 env = None
435 if vals['gyp_crosscompile']: 466 if vals['gyp_crosscompile']:
436 if self.args.verbose:
437 self.Print('Setting GYP_CROSSCOMPILE=1 in the environment')
438 env = os.environ.copy()
439 env['GYP_CROSSCOMPILE'] = '1' 467 env['GYP_CROSSCOMPILE'] = '1'
440 ret, _, _ = self.Run(cmd, env=env) 468 ret, _, _ = self.Run(cmd, env=env)
441 return ret 469 return ret
442 470
443 def RunGYPAnalyze(self, vals): 471 def RunGYPAnalyze(self, vals):
444 output_dir, gyp_config = self.ParseGYPConfigPath(self.args.path[0]) 472 output_dir = self.ParseGYPConfigPath(self.args.path[0])
445 if gyp_config != vals['gyp_config']:
446 raise MBErr('The last component of the path (%s) must match the '
447 'GYP configuration specified in the config (%s), and '
448 'it does not.' % (gyp_config, vals['gyp_config']))
449 if self.args.verbose: 473 if self.args.verbose:
450 inp = self.ReadInputJSON(['files', 'targets']) 474 inp = self.ReadInputJSON(['files', 'targets'])
451 self.Print() 475 self.Print()
452 self.Print('analyze input:') 476 self.Print('analyze input:')
453 self.PrintJSON(inp) 477 self.PrintJSON(inp)
454 self.Print() 478 self.Print()
455 479
456 cmd = self.GYPCmd(output_dir, vals['gyp_defines'], config=gyp_config) 480 cmd, env = self.GYPCmd(output_dir, vals['gyp_defines'])
457 cmd.extend(['-f', 'analyzer', 481 cmd.extend(['-f', 'analyzer',
458 '-G', 'config_path=%s' % self.args.input_path[0], 482 '-G', 'config_path=%s' % self.args.input_path[0],
459 '-G', 'analyzer_output_path=%s' % self.args.output_path[0]]) 483 '-G', 'analyzer_output_path=%s' % self.args.output_path[0]])
460 ret, _, _ = self.Run(cmd) 484 ret, _, _ = self.Run(cmd, env=env)
461 if not ret and self.args.verbose: 485 if not ret and self.args.verbose:
462 outp = json.loads(self.ReadFile(self.args.output_path[0])) 486 outp = json.loads(self.ReadFile(self.args.output_path[0]))
463 self.Print() 487 self.Print()
464 self.Print('analyze output:') 488 self.Print('analyze output:')
465 self.PrintJSON(outp) 489 self.PrintJSON(outp)
466 self.Print() 490 self.Print()
467 491
468 return ret 492 return ret
469 493
470 def RunGNIsolate(self, vals):
471 build_path = self.args.path[0]
472 inp = self.ReadInputJSON(['targets'])
473 if self.args.verbose:
474 self.Print()
475 self.Print('isolate input:')
476 self.PrintJSON(inp)
477 self.Print()
478 output_path = self.args.output_path[0]
479
480 for target in inp['targets']:
481 runtime_deps_path = self.ToAbsPath(build_path, target + '.runtime_deps')
482
483 if not self.Exists(runtime_deps_path):
484 self.WriteFailureAndRaise('"%s" does not exist' % runtime_deps_path,
485 output_path)
486
487 command, extra_files = self.GetIsolateCommand(target, vals, None)
488
489 runtime_deps = self.ReadFile(runtime_deps_path).splitlines()
490
491
492 isolate_path = self.ToAbsPath(build_path, target + '.isolate')
493 self.WriteFile(isolate_path,
494 pprint.pformat({
495 'variables': {
496 'command': command,
497 'files': sorted(runtime_deps + extra_files),
498 }
499 }) + '\n')
500
501 self.WriteJSON(
502 {
503 'args': [
504 '--isolated',
505 self.ToSrcRelPath('%s/%s.isolated' % (build_path, target)),
506 '--isolate',
507 self.ToSrcRelPath('%s/%s.isolate' % (build_path, target)),
508 ],
509 'dir': self.chromium_src_dir,
510 'version': 1,
511 },
512 isolate_path + 'd.gen.json',
513 )
514
515 return 0
516
517 def GetIsolateCommand(self, target, vals, gn_isolate_map): 494 def GetIsolateCommand(self, target, vals, gn_isolate_map):
518 # This needs to mirror the settings in //build/config/ui.gni: 495 # This needs to mirror the settings in //build/config/ui.gni:
519 # use_x11 = is_linux && !use_ozone. 496 # use_x11 = is_linux && !use_ozone.
520 # TODO(dpranke): Figure out how to keep this in sync better. 497 # TODO(dpranke): Figure out how to keep this in sync better.
521 use_x11 = (sys.platform == 'linux2' and 498 use_x11 = (self.platform == 'linux2' and
522 not 'target_os="android"' in vals['gn_args'] and 499 not 'target_os="android"' in vals['gn_args'] and
523 not 'use_ozone=true' in vals['gn_args']) 500 not 'use_ozone=true' in vals['gn_args'])
524 501
525 asan = 'is_asan=true' in vals['gn_args'] 502 asan = 'is_asan=true' in vals['gn_args']
526 msan = 'is_msan=true' in vals['gn_args'] 503 msan = 'is_msan=true' in vals['gn_args']
527 tsan = 'is_tsan=true' in vals['gn_args'] 504 tsan = 'is_tsan=true' in vals['gn_args']
528 505
529 executable_suffix = '.exe' if sys.platform == 'win32' else '' 506 executable_suffix = '.exe' if self.platform == 'win32' else ''
530 507
531 test_type = gn_isolate_map[target]['type'] 508 test_type = gn_isolate_map[target]['type']
532 cmdline = [] 509 cmdline = []
533 extra_files = [] 510 extra_files = []
534 511
535 if use_x11 and test_type == 'windowed_test_launcher': 512 if use_x11 and test_type == 'windowed_test_launcher':
536 extra_files = [ 513 extra_files = [
537 'xdisplaycheck', 514 'xdisplaycheck',
538 '../../testing/test_env.py', 515 '../../testing/test_env.py',
539 '../../testing/xvfb.py', 516 '../../testing/xvfb.py',
(...skipping 21 matching lines...) Expand all
561 '--msan=%d' % msan, 538 '--msan=%d' % msan,
562 '--tsan=%d' % tsan, 539 '--tsan=%d' % tsan,
563 ] 540 ]
564 elif test_type == 'gpu_browser_test': 541 elif test_type == 'gpu_browser_test':
565 extra_files = [ 542 extra_files = [
566 '../../testing/test_env.py' 543 '../../testing/test_env.py'
567 ] 544 ]
568 gtest_filter = gn_isolate_map[target]['gtest_filter'] 545 gtest_filter = gn_isolate_map[target]['gtest_filter']
569 cmdline = [ 546 cmdline = [
570 '../../testing/test_env.py', 547 '../../testing/test_env.py',
571 'browser_tests<(EXECUTABLE_SUFFIX)', 548 './browser_tests' + executable_suffix,
572 '--test-launcher-bot-mode', 549 '--test-launcher-bot-mode',
573 '--enable-gpu', 550 '--enable-gpu',
574 '--test-launcher-jobs=1', 551 '--test-launcher-jobs=1',
575 '--gtest_filter=%s' % gtest_filter, 552 '--gtest_filter=%s' % gtest_filter,
576 ] 553 ]
554 elif test_type == 'script':
555 extra_files = [
556 '../../testing/test_env.py'
557 ]
558 cmdline = [
559 '../../testing/test_env.py',
560 ] + ['../../' + self.ToSrcRelPath(gn_isolate_map[target]['script'])]
577 elif test_type in ('raw'): 561 elif test_type in ('raw'):
578 extra_files = [] 562 extra_files = []
579 cmdline = [ 563 cmdline = [
580 './' + str(target) + executable_suffix, 564 './' + str(target) + executable_suffix,
581 ] + gn_isolate_map[target].get('args') 565 ] + gn_isolate_map[target].get('args')
582 566
583 else: 567 else:
584 self.WriteFailureAndRaise('No command line for %s found (test type %s).' 568 self.WriteFailureAndRaise('No command line for %s found (test type %s).'
585 % (target, test_type), output_path=None) 569 % (target, test_type), output_path=None)
586 570
587 return cmdline, extra_files 571 return cmdline, extra_files
588 572
589 def ToAbsPath(self, build_path, *comps): 573 def ToAbsPath(self, build_path, *comps):
590 return os.path.join(self.chromium_src_dir, 574 return self.PathJoin(self.chromium_src_dir,
591 self.ToSrcRelPath(build_path), 575 self.ToSrcRelPath(build_path),
592 *comps) 576 *comps)
593 577
594 def ToSrcRelPath(self, path): 578 def ToSrcRelPath(self, path):
595 """Returns a relative path from the top of the repo.""" 579 """Returns a relative path from the top of the repo."""
596 # TODO: Support normal paths in addition to source-absolute paths. 580 # TODO: Support normal paths in addition to source-absolute paths.
597 assert(path.startswith('//')) 581 assert(path.startswith('//'))
598 return path[2:].replace('/', os.sep) 582 return path[2:].replace('/', self.sep)
599 583
600 def ParseGYPConfigPath(self, path): 584 def ParseGYPConfigPath(self, path):
601 rpath = self.ToSrcRelPath(path) 585 rpath = self.ToSrcRelPath(path)
602 output_dir, _, config = rpath.rpartition('/') 586 output_dir, _, _ = rpath.rpartition(self.sep)
603 self.CheckGYPConfigIsSupported(config, path) 587 return output_dir
604 return output_dir, config
605 588
606 def CheckGYPConfigIsSupported(self, config, path): 589 def GYPCmd(self, output_dir, gyp_defines):
607 if config not in ('Debug', 'Release'): 590 goma_dir = self.args.goma_dir
608 if (sys.platform in ('win32', 'cygwin') and
609 config not in ('Debug_x64', 'Release_x64')):
610 raise MBErr('Unknown or unsupported config type "%s" in "%s"' %
611 config, path)
612 591
613 def GYPCmd(self, output_dir, gyp_defines, config): 592 # GYP uses shlex.split() to split the gyp defines into separate arguments,
614 gyp_defines = gyp_defines.replace("$(goma_dir)", self.args.goma_dir) 593 # so we can support backslashes and and spaces in arguments by quoting
594 # them, even on Windows, where this normally wouldn't work.
595 if '\\' in goma_dir or ' ' in goma_dir:
596 goma_dir = "'%s'" % goma_dir
597 gyp_defines = gyp_defines.replace("$(goma_dir)", goma_dir)
598
615 cmd = [ 599 cmd = [
616 sys.executable, 600 self.executable,
617 os.path.join('build', 'gyp_chromium'), 601 self.PathJoin('build', 'gyp_chromium'),
618 '-G', 602 '-G',
619 'output_dir=' + output_dir, 603 'output_dir=' + output_dir,
620 '-G',
621 'config=' + config,
622 ] 604 ]
623 for d in shlex.split(gyp_defines): 605 env = os.environ.copy()
624 cmd += ['-D', d] 606 env['GYP_DEFINES'] = gyp_defines
625 return cmd 607 return cmd, env
626 608
627 def RunGNAnalyze(self, vals): 609 def RunGNAnalyze(self, vals):
628 # analyze runs before 'gn gen' now, so we need to run gn gen 610 # analyze runs before 'gn gen' now, so we need to run gn gen
629 # in order to ensure that we have a build directory. 611 # in order to ensure that we have a build directory.
630 ret = self.RunGNGen(vals) 612 ret = self.RunGNGen(vals)
631 if ret: 613 if ret:
632 return ret 614 return ret
633 615
634 inp = self.ReadInputJSON(['files', 'targets']) 616 inp = self.ReadInputJSON(['files', 'targets'])
635 if self.args.verbose: 617 if self.args.verbose:
(...skipping 27 matching lines...) Expand all
663 645
664 ret = 0 646 ret = 0
665 response_file = self.TempFile() 647 response_file = self.TempFile()
666 response_file.write('\n'.join(inp['files']) + '\n') 648 response_file.write('\n'.join(inp['files']) + '\n')
667 response_file.close() 649 response_file.close()
668 650
669 matching_targets = [] 651 matching_targets = []
670 try: 652 try:
671 cmd = self.GNCmd('refs', self.args.path[0]) + [ 653 cmd = self.GNCmd('refs', self.args.path[0]) + [
672 '@%s' % response_file.name, '--all', '--as=output'] 654 '@%s' % response_file.name, '--all', '--as=output']
673 ret, out, _ = self.Run(cmd) 655 ret, out, _ = self.Run(cmd, force_verbose=False)
674 if ret and not 'The input matches no targets' in out: 656 if ret and not 'The input matches no targets' in out:
675 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out), 657 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out),
676 output_path) 658 output_path)
677 build_dir = self.ToSrcRelPath(self.args.path[0]) + os.sep 659 build_dir = self.ToSrcRelPath(self.args.path[0]) + self.sep
678 for output in out.splitlines(): 660 for output in out.splitlines():
679 build_output = output.replace(build_dir, '') 661 build_output = output.replace(build_dir, '')
680 if build_output in inp['targets']: 662 if build_output in inp['targets']:
681 matching_targets.append(build_output) 663 matching_targets.append(build_output)
682 664
683 cmd = self.GNCmd('refs', self.args.path[0]) + [ 665 cmd = self.GNCmd('refs', self.args.path[0]) + [
684 '@%s' % response_file.name, '--all'] 666 '@%s' % response_file.name, '--all']
685 ret, out, _ = self.Run(cmd) 667 ret, out, _ = self.Run(cmd, force_verbose=False)
686 if ret and not 'The input matches no targets' in out: 668 if ret and not 'The input matches no targets' in out:
687 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out), 669 self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out),
688 output_path) 670 output_path)
689 for label in out.splitlines(): 671 for label in out.splitlines():
690 build_target = label[2:] 672 build_target = label[2:]
691 # We want to accept 'chrome/android:chrome_shell_apk' and 673 # We want to accept 'chrome/android:chrome_public_apk' and
692 # just 'chrome_shell_apk'. This may result in too many targets 674 # just 'chrome_public_apk'. This may result in too many targets
693 # getting built, but we can adjust that later if need be. 675 # getting built, but we can adjust that later if need be.
694 for input_target in inp['targets']: 676 for input_target in inp['targets']:
695 if (input_target == build_target or 677 if (input_target == build_target or
696 build_target.endswith(':' + input_target)): 678 build_target.endswith(':' + input_target)):
697 matching_targets.append(input_target) 679 matching_targets.append(input_target)
698 finally: 680 finally:
699 self.RemoveFile(response_file.name) 681 self.RemoveFile(response_file.name)
700 682
701 if matching_targets: 683 if matching_targets:
702 # TODO: it could be that a target X might depend on a target Y 684 # TODO: it could be that a target X might depend on a target Y
703 # and both would be listed in the input, but we would only need 685 # and both would be listed in the input, but we would only need
704 # to specify target X as a build_target (whereas both X and Y are 686 # to specify target X as a build_target (whereas both X and Y are
705 # targets). I'm not sure if that optimization is generally worth it. 687 # targets). I'm not sure if that optimization is generally worth it.
706 self.WriteJSON({'targets': sorted(matching_targets), 688 self.WriteJSON({'targets': sorted(set(matching_targets)),
707 'build_targets': sorted(matching_targets), 689 'build_targets': sorted(set(matching_targets)),
708 'status': 'Found dependency'}, output_path) 690 'status': 'Found dependency'}, output_path)
709 else: 691 else:
710 self.WriteJSON({'targets': [], 692 self.WriteJSON({'targets': [],
711 'build_targets': [], 693 'build_targets': [],
712 'status': 'No dependency'}, output_path) 694 'status': 'No dependency'}, output_path)
713 695
714 if not ret and self.args.verbose: 696 if self.args.verbose:
715 outp = json.loads(self.ReadFile(output_path)) 697 outp = json.loads(self.ReadFile(output_path))
716 self.Print() 698 self.Print()
717 self.Print('analyze output:') 699 self.Print('analyze output:')
718 self.PrintJSON(outp) 700 self.PrintJSON(outp)
719 self.Print() 701 self.Print()
720 702
721 return 0 703 return 0
722 704
723 def ReadInputJSON(self, required_keys): 705 def ReadInputJSON(self, required_keys):
724 path = self.args.input_path[0] 706 path = self.args.input_path[0]
725 output_path = self.args.output_path[0] 707 output_path = self.args.output_path[0]
726 if not self.Exists(path): 708 if not self.Exists(path):
727 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path) 709 self.WriteFailureAndRaise('"%s" does not exist' % path, output_path)
728 710
729 try: 711 try:
730 inp = json.loads(self.ReadFile(path)) 712 inp = json.loads(self.ReadFile(path))
731 except Exception as e: 713 except Exception as e:
732 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' % 714 self.WriteFailureAndRaise('Failed to read JSON input from "%s": %s' %
733 (path, e), output_path) 715 (path, e), output_path)
734 716
735 for k in required_keys: 717 for k in required_keys:
736 if not k in inp: 718 if not k in inp:
737 self.WriteFailureAndRaise('input file is missing a "%s" key' % k, 719 self.WriteFailureAndRaise('input file is missing a "%s" key' % k,
738 output_path) 720 output_path)
739 721
740 return inp 722 return inp
741 723
742 def WriteFailureAndRaise(self, msg, output_path): 724 def WriteFailureAndRaise(self, msg, output_path):
743 if output_path: 725 if output_path:
744 self.WriteJSON({'error': msg}, output_path) 726 self.WriteJSON({'error': msg}, output_path, force_verbose=True)
745 raise MBErr(msg) 727 raise MBErr(msg)
746 728
747 def WriteJSON(self, obj, path): 729 def WriteJSON(self, obj, path, force_verbose=False):
748 try: 730 try:
749 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n') 731 self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
732 force_verbose=force_verbose)
750 except Exception as e: 733 except Exception as e:
751 raise MBErr('Error %s writing to the output path "%s"' % 734 raise MBErr('Error %s writing to the output path "%s"' %
752 (e, path)) 735 (e, path))
753 736
754 def PrintCmd(self, cmd): 737 def PrintCmd(self, cmd, env):
755 if cmd[0] == sys.executable: 738 if self.platform == 'win32':
739 env_prefix = 'set '
740 env_quoter = QuoteForSet
741 shell_quoter = QuoteForCmd
742 else:
743 env_prefix = ''
744 env_quoter = pipes.quote
745 shell_quoter = pipes.quote
746
747 def print_env(var):
748 if env and var in env:
749 self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
750
751 print_env('GYP_CROSSCOMPILE')
752 print_env('GYP_DEFINES')
753
754 if cmd[0] == self.executable:
756 cmd = ['python'] + cmd[1:] 755 cmd = ['python'] + cmd[1:]
757 self.Print(*[pipes.quote(c) for c in cmd]) 756 self.Print(*[shell_quoter(arg) for arg in cmd])
758 757
759 def PrintJSON(self, obj): 758 def PrintJSON(self, obj):
760 self.Print(json.dumps(obj, indent=2, sort_keys=True)) 759 self.Print(json.dumps(obj, indent=2, sort_keys=True))
761 760
762 def Print(self, *args, **kwargs): 761 def Print(self, *args, **kwargs):
763 # This function largely exists so it can be overridden for testing. 762 # This function largely exists so it can be overridden for testing.
764 print(*args, **kwargs) 763 print(*args, **kwargs)
765 764
766 def Run(self, cmd, env=None): 765 def Run(self, cmd, env=None, force_verbose=True):
767 # This function largely exists so it can be overridden for testing. 766 # This function largely exists so it can be overridden for testing.
768 if self.args.dryrun or self.args.verbose: 767 if self.args.dryrun or self.args.verbose or force_verbose:
769 self.PrintCmd(cmd) 768 self.PrintCmd(cmd, env)
770 if self.args.dryrun: 769 if self.args.dryrun:
771 return 0, '', '' 770 return 0, '', ''
771
772 ret, out, err = self.Call(cmd, env=env) 772 ret, out, err = self.Call(cmd, env=env)
773 if self.args.verbose: 773 if self.args.verbose or force_verbose:
774 if out: 774 if out:
775 self.Print(out, end='') 775 self.Print(out, end='')
776 if err: 776 if err:
777 self.Print(err, end='', file=sys.stderr) 777 self.Print(err, end='', file=sys.stderr)
778 return ret, out, err 778 return ret, out, err
779 779
780 def Call(self, cmd, env=None): 780 def Call(self, cmd, env=None):
781 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir, 781 p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
782 stdout=subprocess.PIPE, stderr=subprocess.PIPE, 782 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
783 env=env) 783 env=env)
784 out, err = p.communicate() 784 out, err = p.communicate()
785 return p.returncode, out, err 785 return p.returncode, out, err
786 786
787 def ExpandUser(self, path): 787 def ExpandUser(self, path):
788 # This function largely exists so it can be overridden for testing. 788 # This function largely exists so it can be overridden for testing.
789 return os.path.expanduser(path) 789 return os.path.expanduser(path)
790 790
791 def Exists(self, path): 791 def Exists(self, path):
792 # This function largely exists so it can be overridden for testing. 792 # This function largely exists so it can be overridden for testing.
793 return os.path.exists(path) 793 return os.path.exists(path)
794 794
795 def MaybeMakeDirectory(self, path): 795 def MaybeMakeDirectory(self, path):
796 try: 796 try:
797 os.makedirs(path) 797 os.makedirs(path)
798 except OSError, e: 798 except OSError, e:
799 if e.errno != errno.EEXIST: 799 if e.errno != errno.EEXIST:
800 raise 800 raise
801 801
802 def PathJoin(self, *comps):
803 # This function largely exists so it can be overriden for testing.
804 return os.path.join(*comps)
805
802 def ReadFile(self, path): 806 def ReadFile(self, path):
803 # This function largely exists so it can be overriden for testing. 807 # This function largely exists so it can be overriden for testing.
804 with open(path) as fp: 808 with open(path) as fp:
805 return fp.read() 809 return fp.read()
806 810
807 def RemoveFile(self, path): 811 def RemoveFile(self, path):
808 # This function largely exists so it can be overriden for testing. 812 # This function largely exists so it can be overriden for testing.
809 os.remove(path) 813 os.remove(path)
810 814
815 def RemoveDirectory(self, abs_path):
816 if self.platform == 'win32':
817 # In other places in chromium, we often have to retry this command
818 # because we're worried about other processes still holding on to
819 # file handles, but when MB is invoked, it will be early enough in the
820 # build that their should be no other processes to interfere. We
821 # can change this if need be.
822 self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
823 else:
824 shutil.rmtree(abs_path, ignore_errors=True)
825
811 def TempFile(self, mode='w'): 826 def TempFile(self, mode='w'):
812 # This function largely exists so it can be overriden for testing. 827 # This function largely exists so it can be overriden for testing.
813 return tempfile.NamedTemporaryFile(mode=mode, delete=False) 828 return tempfile.NamedTemporaryFile(mode=mode, delete=False)
814 829
815 def WriteFile(self, path, contents): 830 def WriteFile(self, path, contents, force_verbose=False):
816 # This function largely exists so it can be overriden for testing. 831 # This function largely exists so it can be overriden for testing.
817 if self.args.dryrun or self.args.verbose: 832 if self.args.dryrun or self.args.verbose or force_verbose:
818 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path)) 833 self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
819 with open(path, 'w') as fp: 834 with open(path, 'w') as fp:
820 return fp.write(contents) 835 return fp.write(contents)
821 836
822 837
823 class MBErr(Exception): 838 class MBErr(Exception):
824 pass 839 pass
825 840
826 841
842 # See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
843 # details of this next section, which handles escaping command lines
844 # so that they can be copied and pasted into a cmd window.
845 UNSAFE_FOR_SET = set('^<>&|')
846 UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
847 ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
848
849
850 def QuoteForSet(arg):
851 if any(a in UNSAFE_FOR_SET for a in arg):
852 arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
853 return arg
854
855
856 def QuoteForCmd(arg):
857 # First, escape the arg so that CommandLineToArgvW will parse it properly.
858 # From //tools/gyp/pylib/gyp/msvs_emulation.py:23.
859 if arg == '' or ' ' in arg or '"' in arg:
860 quote_re = re.compile(r'(\\*)"')
861 arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
862
863 # Then check to see if the arg contains any metacharacters other than
864 # double quotes; if it does, quote everything (including the double
865 # quotes) for safety.
866 if any(a in UNSAFE_FOR_CMD for a in arg):
867 arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
868 return arg
869
870
827 if __name__ == '__main__': 871 if __name__ == '__main__':
828 try: 872 try:
829 sys.exit(main(sys.argv[1:])) 873 sys.exit(main(sys.argv[1:]))
830 except MBErr as e: 874 except MBErr as e:
831 print(e) 875 print(e)
832 sys.exit(1) 876 sys.exit(1)
833 except KeyboardInterrupt: 877 except KeyboardInterrupt:
834 print("interrupted, exiting", stream=sys.stderr) 878 print("interrupted, exiting", stream=sys.stderr)
835 sys.exit(130) 879 sys.exit(130)
OLDNEW
« no previous file with comments | « tools/mb/docs/user_guide.md ('k') | tools/mb/mb_config.pyl » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698