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

Unified Diff: tools/mb/mb.py

Issue 1370593003: Merge MB from trunk back to M45. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@branch_2454
Patch Set: Created 5 years, 3 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 side-by-side diff with in-line comments
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 »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/mb/mb.py
diff --git a/tools/mb/mb.py b/tools/mb/mb.py
index e7ef57b63efe391f2cb8df238839cd744240b8ee..12f942dc87cf75056370a3a99fdb76a03a5505ac 100755
--- a/tools/mb/mb.py
+++ b/tools/mb/mb.py
@@ -18,13 +18,12 @@ import json
import os
import pipes
import pprint
-import shlex
+import re
import shutil
import sys
import subprocess
import tempfile
-
def main(args):
mbw = MetaBuildWrapper()
mbw.ParseArgs(args)
@@ -38,7 +37,9 @@ class MetaBuildWrapper(object):
self.chromium_src_dir = p.normpath(d(d(d(p.abspath(__file__)))))
self.default_config = p.join(self.chromium_src_dir, 'tools', 'mb',
'mb_config.pyl')
+ self.executable = sys.executable
self.platform = sys.platform
+ self.sep = os.sep
self.args = argparse.Namespace()
self.configs = {}
self.masters = {}
@@ -64,11 +65,8 @@ class MetaBuildWrapper(object):
subp.add_argument('-n', '--dryrun', action='store_true',
help='Do a dry run (i.e., do nothing, just print '
'the commands that will run)')
- subp.add_argument('-q', '--quiet', action='store_true',
- help='Do not print anything, just return an exit '
- 'code.')
- subp.add_argument('-v', '--verbose', action='count',
- help='verbose logging (may specify multiple times).')
+ subp.add_argument('-v', '--verbose', action='store_true',
+ help='verbose logging')
parser = argparse.ArgumentParser(prog='mb')
subps = parser.add_subparsers()
@@ -108,7 +106,10 @@ class MetaBuildWrapper(object):
subp = subps.add_parser('validate',
help='validate the config file')
- AddCommonOptions(subp)
+ subp.add_argument('-f', '--config-file', metavar='PATH',
+ default=self.default_config,
+ help='path to config file '
+ '(default is //tools/mb/mb_config.pyl)')
subp.set_defaults(func=self.CmdValidate)
subp = subps.add_parser('help',
@@ -130,6 +131,9 @@ class MetaBuildWrapper(object):
def CmdGen(self):
vals = self.GetConfig()
+
+ self.ClobberIfNeeded(vals)
+
if vals['type'] == 'gn':
return self.RunGNGen(vals)
if vals['type'] == 'gyp':
@@ -140,13 +144,16 @@ class MetaBuildWrapper(object):
def CmdLookup(self):
vals = self.GetConfig()
if vals['type'] == 'gn':
- cmd = self.GNCmd('gen', '<path>', vals['gn_args'])
+ cmd = self.GNCmd('gen', '_path_', vals['gn_args'])
+ env = None
elif vals['type'] == 'gyp':
- cmd = self.GYPCmd('<path>', vals['gyp_defines'], vals['gyp_config'])
+ if vals['gyp_crosscompile']:
+ self.Print('GYP_CROSSCOMPILE=1')
+ cmd, env = self.GYPCmd('_path_', vals['gyp_defines'])
else:
raise MBErr('Unknown meta-build type "%s"' % vals['type'])
- self.PrintCmd(cmd)
+ self.PrintCmd(cmd, env)
return 0
def CmdHelp(self):
@@ -222,10 +229,10 @@ class MetaBuildWrapper(object):
errs.append('Unreferenced mixin "%s".' % mixin)
if errs:
- raise MBErr('mb config file %s has problems:\n ' + '\n '.join(errs))
+ raise MBErr(('mb config file %s has problems:' % self.args.config_file) +
+ '\n ' + '\n '.join(errs))
- if not self.args.quiet:
- self.Print('mb config file %s looks ok.' % self.args.config_file)
+ self.Print('mb config file %s looks ok.' % self.args.config_file)
return 0
def GetConfig(self):
@@ -281,8 +288,8 @@ class MetaBuildWrapper(object):
vals = {
'type': None,
'gn_args': [],
- 'gyp_config': [],
- 'gyp_defines': [],
+ 'gyp_defines': '',
+ 'gyp_crosscompile': False,
}
visited = []
@@ -306,8 +313,8 @@ class MetaBuildWrapper(object):
vals['gn_args'] += ' ' + mixin_vals['gn_args']
else:
vals['gn_args'] = mixin_vals['gn_args']
- if 'gyp_config' in mixin_vals:
- vals['gyp_config'] = mixin_vals['gyp_config']
+ if 'gyp_crosscompile' in mixin_vals:
+ vals['gyp_crosscompile'] = mixin_vals['gyp_crosscompile']
if 'gyp_defines' in mixin_vals:
if vals['gyp_defines']:
vals['gyp_defines'] += ' ' + mixin_vals['gyp_defines']
@@ -317,10 +324,40 @@ class MetaBuildWrapper(object):
self.FlattenMixins(mixin_vals['mixins'], vals, visited)
return vals
+ def ClobberIfNeeded(self, vals):
+ path = self.args.path[0]
+ build_dir = self.ToAbsPath(path)
+ mb_type_path = self.PathJoin(build_dir, 'mb_type')
+ needs_clobber = False
+ new_mb_type = vals['type']
+ if self.Exists(build_dir):
+ if self.Exists(mb_type_path):
+ old_mb_type = self.ReadFile(mb_type_path)
+ if old_mb_type != new_mb_type:
+ self.Print("Build type mismatch: was %s, will be %s, clobbering %s" %
+ (old_mb_type, new_mb_type, path))
+ needs_clobber = True
+ else:
+ # There is no 'mb_type' file in the build directory, so this probably
+ # means that the prior build(s) were not done through mb, and we
+ # have no idea if this was a GYP build or a GN build. Clobber it
+ # to be safe.
+ self.Print("%s/mb_type missing, clobbering to be safe" % path)
+ needs_clobber = True
+
+ if self.args.dryrun:
+ return
+
+ if needs_clobber:
+ self.RemoveDirectory(build_dir)
+
+ self.MaybeMakeDirectory(build_dir)
+ self.WriteFile(mb_type_path, new_mb_type)
+
def RunGNGen(self, vals):
path = self.args.path[0]
- cmd = self.GNCmd('gen', path, vals['gn_args'])
+ cmd = self.GNCmd('gen', path, vals['gn_args'], extra_args=['--check'])
swarming_targets = []
if self.args.swarming_targets_file:
@@ -330,14 +367,14 @@ class MetaBuildWrapper(object):
# the compile targets to the matching GN labels.
contents = self.ReadFile(self.args.swarming_targets_file)
swarming_targets = contents.splitlines()
- ninja_targets_to_labels = ast.literal_eval(self.ReadFile(os.path.join(
- self.chromium_src_dir, 'testing', 'buildbot', 'ninja_to_gn.pyl')))
+ gn_isolate_map = ast.literal_eval(self.ReadFile(self.PathJoin(
+ self.chromium_src_dir, 'testing', 'buildbot', 'gn_isolate_map.pyl')))
gn_labels = []
for target in swarming_targets:
- if not target in ninja_targets_to_labels:
+ if not target in gn_isolate_map:
raise MBErr('test target "%s" not found in %s' %
- (target, '//testing/buildbot/ninja_to_gn.pyl'))
- gn_labels.append(ninja_targets_to_labels[target])
+ (target, '//testing/buildbot/gn_isolate_map.pyl'))
+ gn_labels.append(gn_isolate_map[target]['label'])
gn_runtime_deps_path = self.ToAbsPath(path, 'runtime_deps')
@@ -348,16 +385,34 @@ class MetaBuildWrapper(object):
cmd.append('--runtime-deps-list-file=%s' % gn_runtime_deps_path)
ret, _, _ = self.Run(cmd)
+ if ret:
+ # If `gn gen` failed, we should exit early rather than trying to
+ # generate isolates. Run() will have already logged any error output.
+ self.Print('GN gen failed: %d' % ret)
+ return ret
for target in swarming_targets:
- if sys.platform == 'win32':
- deps_path = self.ToAbsPath(path, target + '.exe.runtime_deps')
+ if gn_isolate_map[target]['type'] == 'gpu_browser_test':
+ runtime_deps_target = 'browser_tests'
+ elif gn_isolate_map[target]['type'] == 'script':
+ # For script targets, the build target is usually a group,
+ # for which gn generates the runtime_deps next to the stamp file
+ # for the label, which lives under the obj/ directory.
+ label = gn_isolate_map[target]['label']
+ runtime_deps_target = 'obj/%s.stamp' % label.replace(':', '/')
else:
- deps_path = self.ToAbsPath(path, target + '.runtime_deps')
+ runtime_deps_target = target
+ if self.platform == 'win32':
+ deps_path = self.ToAbsPath(path,
+ runtime_deps_target + '.exe.runtime_deps')
+ else:
+ deps_path = self.ToAbsPath(path,
+ runtime_deps_target + '.runtime_deps')
if not self.Exists(deps_path):
raise MBErr('did not generate %s' % deps_path)
- command, extra_files = self.GetIsolateCommand(target, vals)
+ command, extra_files = self.GetIsolateCommand(target, vals,
+ gn_isolate_map)
runtime_deps = self.ReadFile(deps_path).splitlines()
@@ -367,7 +422,6 @@ class MetaBuildWrapper(object):
'variables': {
'command': command,
'files': sorted(runtime_deps + extra_files),
- 'read_only': 1,
}
}) + '\n')
@@ -375,9 +429,9 @@ class MetaBuildWrapper(object):
{
'args': [
'--isolated',
- self.ToSrcRelPath('%s%s%s.isolated' % (path, os.sep, target)),
+ self.ToSrcRelPath('%s%s%s.isolated' % (path, self.sep, target)),
'--isolate',
- self.ToSrcRelPath('%s%s%s.isolate' % (path, os.sep, target)),
+ self.ToSrcRelPath('%s%s%s.isolate' % (path, self.sep, target)),
],
'dir': self.chromium_src_dir,
'version': 1,
@@ -385,55 +439,49 @@ class MetaBuildWrapper(object):
isolate_path + 'd.gen.json',
)
-
return ret
- def GNCmd(self, subcommand, path, gn_args=''):
+ def GNCmd(self, subcommand, path, gn_args='', extra_args=None):
if self.platform == 'linux2':
- gn_path = os.path.join(self.chromium_src_dir, 'buildtools', 'linux64',
- 'gn')
+ subdir = 'linux64'
elif self.platform == 'darwin':
- gn_path = os.path.join(self.chromium_src_dir, 'buildtools', 'mac',
- 'gn')
+ subdir = 'mac'
else:
- gn_path = os.path.join(self.chromium_src_dir, 'buildtools', 'win',
- 'gn.exe')
+ subdir = 'win'
+ gn_path = self.PathJoin(self.chromium_src_dir, 'buildtools', subdir, 'gn')
cmd = [gn_path, subcommand, path]
gn_args = gn_args.replace("$(goma_dir)", self.args.goma_dir)
if gn_args:
cmd.append('--args=%s' % gn_args)
+ if extra_args:
+ cmd.extend(extra_args)
return cmd
def RunGYPGen(self, vals):
path = self.args.path[0]
- output_dir, gyp_config = self.ParseGYPConfigPath(path)
- if gyp_config != vals['gyp_config']:
- raise MBErr('The last component of the path (%s) must match the '
- 'GYP configuration specified in the config (%s), and '
- 'it does not.' % (gyp_config, vals['gyp_config']))
- cmd = self.GYPCmd(output_dir, vals['gyp_defines'], config=gyp_config)
- ret, _, _ = self.Run(cmd)
+ output_dir = self.ParseGYPConfigPath(path)
+ cmd, env = self.GYPCmd(output_dir, vals['gyp_defines'])
+ if vals['gyp_crosscompile']:
+ env['GYP_CROSSCOMPILE'] = '1'
+ ret, _, _ = self.Run(cmd, env=env)
return ret
def RunGYPAnalyze(self, vals):
- output_dir, gyp_config = self.ParseGYPConfigPath(self.args.path[0])
- if gyp_config != vals['gyp_config']:
- raise MBErr('The last component of the path (%s) must match the '
- 'GYP configuration specified in the config (%s), and '
- 'it does not.' % (gyp_config, vals['gyp_config']))
+ output_dir = self.ParseGYPConfigPath(self.args.path[0])
if self.args.verbose:
- inp = self.GetAnalyzeInput()
+ inp = self.ReadInputJSON(['files', 'targets'])
self.Print()
self.Print('analyze input:')
self.PrintJSON(inp)
self.Print()
- cmd = self.GYPCmd(output_dir, vals['gyp_defines'], config=gyp_config)
- cmd.extend(['-G', 'config_path=%s' % self.args.input_path[0],
+ cmd, env = self.GYPCmd(output_dir, vals['gyp_defines'])
+ cmd.extend(['-f', 'analyzer',
+ '-G', 'config_path=%s' % self.args.input_path[0],
'-G', 'analyzer_output_path=%s' % self.args.output_path[0]])
- ret, _, _ = self.Run(cmd)
+ ret, _, _ = self.Run(cmd, env=env)
if not ret and self.args.verbose:
outp = json.loads(self.ReadFile(self.args.output_path[0]))
self.Print()
@@ -443,19 +491,11 @@ class MetaBuildWrapper(object):
return ret
- def GetIsolateCommand(self, target, vals):
- extra_files = []
-
- # TODO(dpranke): We should probably pull this from
- # the test list info in //testing/buildbot/*.json,
- # and assert that the test has can_use_on_swarming_builders: True,
- # but we hardcode it here for now.
- test_type = {}.get(target, 'gtest_test')
-
+ def GetIsolateCommand(self, target, vals, gn_isolate_map):
# This needs to mirror the settings in //build/config/ui.gni:
# use_x11 = is_linux && !use_ozone.
# TODO(dpranke): Figure out how to keep this in sync better.
- use_x11 = (sys.platform == 'linux2' and
+ use_x11 = (self.platform == 'linux2' and
not 'target_os="android"' in vals['gn_args'] and
not 'use_ozone=true' in vals['gn_args'])
@@ -463,83 +503,108 @@ class MetaBuildWrapper(object):
msan = 'is_msan=true' in vals['gn_args']
tsan = 'is_tsan=true' in vals['gn_args']
- executable_suffix = '.exe' if sys.platform == 'win32' else ''
-
- if test_type == 'gtest_test':
- extra_files.append('../../testing/test_env.py')
+ executable_suffix = '.exe' if self.platform == 'win32' else ''
- if use_x11:
- # TODO(dpranke): Figure out some way to figure out which
- # test steps really need xvfb.
- extra_files.append('xdisplaycheck')
- extra_files.append('../../testing/xvfb.py')
+ test_type = gn_isolate_map[target]['type']
+ cmdline = []
+ extra_files = []
- cmdline = [
+ if use_x11 and test_type == 'windowed_test_launcher':
+ extra_files = [
+ 'xdisplaycheck',
+ '../../testing/test_env.py',
'../../testing/xvfb.py',
- '.',
- './' + str(target),
- '--brave-new-test-launcher',
- '--test-launcher-bot-mode',
- '--asan=%d' % asan,
- '--msan=%d' % msan,
- '--tsan=%d' % tsan,
- ]
- else:
- cmdline = [
+ ]
+ cmdline = [
+ '../../testing/xvfb.py',
+ '.',
+ './' + str(target),
+ '--brave-new-test-launcher',
+ '--test-launcher-bot-mode',
+ '--asan=%d' % asan,
+ '--msan=%d' % msan,
+ '--tsan=%d' % tsan,
+ ]
+ elif test_type in ('windowed_test_launcher', 'console_test_launcher'):
+ extra_files = [
+ '../../testing/test_env.py'
+ ]
+ cmdline = [
'../../testing/test_env.py',
- '.',
'./' + str(target) + executable_suffix,
'--brave-new-test-launcher',
'--test-launcher-bot-mode',
'--asan=%d' % asan,
'--msan=%d' % msan,
'--tsan=%d' % tsan,
- ]
- else:
- # TODO(dpranke): Handle script_tests and other types of swarmed tests.
- self.WriteFailureAndRaise('unknown test type "%s" for %s' %
- (test_type, target), output_path=None)
+ ]
+ elif test_type == 'gpu_browser_test':
+ extra_files = [
+ '../../testing/test_env.py'
+ ]
+ gtest_filter = gn_isolate_map[target]['gtest_filter']
+ cmdline = [
+ '../../testing/test_env.py',
+ './browser_tests' + executable_suffix,
+ '--test-launcher-bot-mode',
+ '--enable-gpu',
+ '--test-launcher-jobs=1',
+ '--gtest_filter=%s' % gtest_filter,
+ ]
+ elif test_type == 'script':
+ extra_files = [
+ '../../testing/test_env.py'
+ ]
+ cmdline = [
+ '../../testing/test_env.py',
+ ] + ['../../' + self.ToSrcRelPath(gn_isolate_map[target]['script'])]
+ elif test_type in ('raw'):
+ extra_files = []
+ cmdline = [
+ './' + str(target) + executable_suffix,
+ ] + gn_isolate_map[target].get('args')
+ else:
+ self.WriteFailureAndRaise('No command line for %s found (test type %s).'
+ % (target, test_type), output_path=None)
return cmdline, extra_files
def ToAbsPath(self, build_path, *comps):
- return os.path.join(self.chromium_src_dir,
- self.ToSrcRelPath(build_path),
- *comps)
+ return self.PathJoin(self.chromium_src_dir,
+ self.ToSrcRelPath(build_path),
+ *comps)
def ToSrcRelPath(self, path):
"""Returns a relative path from the top of the repo."""
# TODO: Support normal paths in addition to source-absolute paths.
assert(path.startswith('//'))
- return path[2:].replace('/', os.sep)
+ return path[2:].replace('/', self.sep)
def ParseGYPConfigPath(self, path):
rpath = self.ToSrcRelPath(path)
- output_dir, _, config = rpath.rpartition('/')
- self.CheckGYPConfigIsSupported(config, path)
- return output_dir, config
-
- def CheckGYPConfigIsSupported(self, config, path):
- if config not in ('Debug', 'Release'):
- if (sys.platform in ('win32', 'cygwin') and
- config not in ('Debug_x64', 'Release_x64')):
- raise MBErr('Unknown or unsupported config type "%s" in "%s"' %
- config, path)
-
- def GYPCmd(self, output_dir, gyp_defines, config):
- gyp_defines = gyp_defines.replace("$(goma_dir)", self.args.goma_dir)
+ output_dir, _, _ = rpath.rpartition(self.sep)
+ return output_dir
+
+ def GYPCmd(self, output_dir, gyp_defines):
+ goma_dir = self.args.goma_dir
+
+ # GYP uses shlex.split() to split the gyp defines into separate arguments,
+ # so we can support backslashes and and spaces in arguments by quoting
+ # them, even on Windows, where this normally wouldn't work.
+ if '\\' in goma_dir or ' ' in goma_dir:
+ goma_dir = "'%s'" % goma_dir
+ gyp_defines = gyp_defines.replace("$(goma_dir)", goma_dir)
+
cmd = [
- sys.executable,
- os.path.join('build', 'gyp_chromium'),
+ self.executable,
+ self.PathJoin('build', 'gyp_chromium'),
'-G',
'output_dir=' + output_dir,
- '-G',
- 'config=' + config,
]
- for d in shlex.split(gyp_defines):
- cmd += ['-D', d]
- return cmd
+ env = os.environ.copy()
+ env['GYP_DEFINES'] = gyp_defines
+ return cmd, env
def RunGNAnalyze(self, vals):
# analyze runs before 'gn gen' now, so we need to run gn gen
@@ -568,6 +633,16 @@ class MetaBuildWrapper(object):
self.WriteJSON({'status': 'Found dependency (all)'}, output_path)
return 0
+ # This shouldn't normally happen, but could due to unusual race conditions,
+ # like a try job that gets scheduled before a patch lands but runs after
+ # the patch has landed.
+ if not inp['files']:
+ self.Print('Warning: No files modified in patch, bailing out early.')
+ self.WriteJSON({'targets': [],
+ 'build_targets': [],
+ 'status': 'No dependency'}, output_path)
+ return 0
+
ret = 0
response_file = self.TempFile()
response_file.write('\n'.join(inp['files']) + '\n')
@@ -577,11 +652,11 @@ class MetaBuildWrapper(object):
try:
cmd = self.GNCmd('refs', self.args.path[0]) + [
'@%s' % response_file.name, '--all', '--as=output']
- ret, out, _ = self.Run(cmd)
+ ret, out, _ = self.Run(cmd, force_verbose=False)
if ret and not 'The input matches no targets' in out:
self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out),
output_path)
- build_dir = self.ToSrcRelPath(self.args.path[0]) + os.sep
+ build_dir = self.ToSrcRelPath(self.args.path[0]) + self.sep
for output in out.splitlines():
build_output = output.replace(build_dir, '')
if build_output in inp['targets']:
@@ -589,14 +664,14 @@ class MetaBuildWrapper(object):
cmd = self.GNCmd('refs', self.args.path[0]) + [
'@%s' % response_file.name, '--all']
- ret, out, _ = self.Run(cmd)
+ ret, out, _ = self.Run(cmd, force_verbose=False)
if ret and not 'The input matches no targets' in out:
self.WriteFailureAndRaise('gn refs returned %d: %s' % (ret, out),
output_path)
for label in out.splitlines():
build_target = label[2:]
- # We want to accept 'chrome/android:chrome_shell_apk' and
- # just 'chrome_shell_apk'. This may result in too many targets
+ # We want to accept 'chrome/android:chrome_public_apk' and
+ # just 'chrome_public_apk'. This may result in too many targets
# getting built, but we can adjust that later if need be.
for input_target in inp['targets']:
if (input_target == build_target or
@@ -610,15 +685,15 @@ class MetaBuildWrapper(object):
# and both would be listed in the input, but we would only need
# to specify target X as a build_target (whereas both X and Y are
# targets). I'm not sure if that optimization is generally worth it.
- self.WriteJSON({'targets': sorted(matching_targets),
- 'build_targets': sorted(matching_targets),
+ self.WriteJSON({'targets': sorted(set(matching_targets)),
+ 'build_targets': sorted(set(matching_targets)),
'status': 'Found dependency'}, output_path)
else:
self.WriteJSON({'targets': [],
'build_targets': [],
'status': 'No dependency'}, output_path)
- if not ret and self.args.verbose:
+ if self.args.verbose:
outp = json.loads(self.ReadFile(output_path))
self.Print()
self.Print('analyze output:')
@@ -648,20 +723,37 @@ class MetaBuildWrapper(object):
def WriteFailureAndRaise(self, msg, output_path):
if output_path:
- self.WriteJSON({'error': msg}, output_path)
+ self.WriteJSON({'error': msg}, output_path, force_verbose=True)
raise MBErr(msg)
- def WriteJSON(self, obj, path):
+ def WriteJSON(self, obj, path, force_verbose=False):
try:
- self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n')
+ self.WriteFile(path, json.dumps(obj, indent=2, sort_keys=True) + '\n',
+ force_verbose=force_verbose)
except Exception as e:
raise MBErr('Error %s writing to the output path "%s"' %
(e, path))
- def PrintCmd(self, cmd):
- if cmd[0] == sys.executable:
+ def PrintCmd(self, cmd, env):
+ if self.platform == 'win32':
+ env_prefix = 'set '
+ env_quoter = QuoteForSet
+ shell_quoter = QuoteForCmd
+ else:
+ env_prefix = ''
+ env_quoter = pipes.quote
+ shell_quoter = pipes.quote
+
+ def print_env(var):
+ if env and var in env:
+ self.Print('%s%s=%s' % (env_prefix, var, env_quoter(env[var])))
+
+ print_env('GYP_CROSSCOMPILE')
+ print_env('GYP_DEFINES')
+
+ if cmd[0] == self.executable:
cmd = ['python'] + cmd[1:]
- self.Print(*[pipes.quote(c) for c in cmd])
+ self.Print(*[shell_quoter(arg) for arg in cmd])
def PrintJSON(self, obj):
self.Print(json.dumps(obj, indent=2, sort_keys=True))
@@ -670,23 +762,25 @@ class MetaBuildWrapper(object):
# This function largely exists so it can be overridden for testing.
print(*args, **kwargs)
- def Run(self, cmd):
+ def Run(self, cmd, env=None, force_verbose=True):
# This function largely exists so it can be overridden for testing.
- if self.args.dryrun or self.args.verbose:
- self.PrintCmd(cmd)
+ if self.args.dryrun or self.args.verbose or force_verbose:
+ self.PrintCmd(cmd, env)
if self.args.dryrun:
return 0, '', ''
- ret, out, err = self.Call(cmd)
- if self.args.verbose:
+
+ ret, out, err = self.Call(cmd, env=env)
+ if self.args.verbose or force_verbose:
if out:
self.Print(out, end='')
if err:
self.Print(err, end='', file=sys.stderr)
return ret, out, err
- def Call(self, cmd):
+ def Call(self, cmd, env=None):
p = subprocess.Popen(cmd, shell=False, cwd=self.chromium_src_dir,
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ env=env)
out, err = p.communicate()
return p.returncode, out, err
@@ -705,6 +799,10 @@ class MetaBuildWrapper(object):
if e.errno != errno.EEXIST:
raise
+ def PathJoin(self, *comps):
+ # This function largely exists so it can be overriden for testing.
+ return os.path.join(*comps)
+
def ReadFile(self, path):
# This function largely exists so it can be overriden for testing.
with open(path) as fp:
@@ -714,13 +812,24 @@ class MetaBuildWrapper(object):
# This function largely exists so it can be overriden for testing.
os.remove(path)
+ def RemoveDirectory(self, abs_path):
+ if self.platform == 'win32':
+ # In other places in chromium, we often have to retry this command
+ # because we're worried about other processes still holding on to
+ # file handles, but when MB is invoked, it will be early enough in the
+ # build that their should be no other processes to interfere. We
+ # can change this if need be.
+ self.Run(['cmd.exe', '/c', 'rmdir', '/q', '/s', abs_path])
+ else:
+ shutil.rmtree(abs_path, ignore_errors=True)
+
def TempFile(self, mode='w'):
# This function largely exists so it can be overriden for testing.
return tempfile.NamedTemporaryFile(mode=mode, delete=False)
- def WriteFile(self, path, contents):
+ def WriteFile(self, path, contents, force_verbose=False):
# This function largely exists so it can be overriden for testing.
- if self.args.dryrun or self.args.verbose:
+ if self.args.dryrun or self.args.verbose or force_verbose:
self.Print('\nWriting """\\\n%s""" to %s.\n' % (contents, path))
with open(path, 'w') as fp:
return fp.write(contents)
@@ -730,6 +839,35 @@ class MBErr(Exception):
pass
+# See http://goo.gl/l5NPDW and http://goo.gl/4Diozm for the painful
+# details of this next section, which handles escaping command lines
+# so that they can be copied and pasted into a cmd window.
+UNSAFE_FOR_SET = set('^<>&|')
+UNSAFE_FOR_CMD = UNSAFE_FOR_SET.union(set('()%'))
+ALL_META_CHARS = UNSAFE_FOR_CMD.union(set('"'))
+
+
+def QuoteForSet(arg):
+ if any(a in UNSAFE_FOR_SET for a in arg):
+ arg = ''.join('^' + a if a in UNSAFE_FOR_SET else a for a in arg)
+ return arg
+
+
+def QuoteForCmd(arg):
+ # First, escape the arg so that CommandLineToArgvW will parse it properly.
+ # From //tools/gyp/pylib/gyp/msvs_emulation.py:23.
+ if arg == '' or ' ' in arg or '"' in arg:
+ quote_re = re.compile(r'(\\*)"')
+ arg = '"%s"' % (quote_re.sub(lambda mo: 2 * mo.group(1) + '\\"', arg))
+
+ # Then check to see if the arg contains any metacharacters other than
+ # double quotes; if it does, quote everything (including the double
+ # quotes) for safety.
+ if any(a in UNSAFE_FOR_CMD for a in arg):
+ arg = ''.join('^' + a if a in ALL_META_CHARS else a for a in arg)
+ return arg
+
+
if __name__ == '__main__':
try:
sys.exit(main(sys.argv[1:]))
« 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