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

Unified 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, 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 f30fc3a1240742a9b01338575205fee309362fb0..12f942dc87cf75056370a3a99fdb76a03a5505ac 100755
--- a/tools/mb/mb.py
+++ b/tools/mb/mb.py
@@ -18,7 +18,7 @@ import json
import os
import pipes
import pprint
-import shlex
+import re
import shutil
import sys
import subprocess
@@ -37,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 = {}
@@ -63,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 on success, '
- '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()
@@ -111,9 +110,6 @@ class MetaBuildWrapper(object):
default=self.default_config,
help='path to config file '
'(default is //tools/mb/mb_config.pyl)')
- subp.add_argument('-q', '--quiet', action='store_true',
- help='Do not print anything on success, '
- 'just return an exit code.')
subp.set_defaults(func=self.CmdValidate)
subp = subps.add_parser('help',
@@ -135,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':
@@ -145,15 +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':
if vals['gyp_crosscompile']:
self.Print('GYP_CROSSCOMPILE=1')
- cmd = self.GYPCmd('<path>', vals['gyp_defines'], vals['gyp_config'])
+ 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):
@@ -232,8 +232,7 @@ class MetaBuildWrapper(object):
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):
@@ -289,8 +288,7 @@ class MetaBuildWrapper(object):
vals = {
'type': None,
'gn_args': [],
- 'gyp_config': [],
- 'gyp_defines': [],
+ 'gyp_defines': '',
'gyp_crosscompile': False,
}
@@ -315,8 +313,6 @@ 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:
@@ -328,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:
@@ -341,7 +367,7 @@ class MetaBuildWrapper(object):
# the compile targets to the matching GN labels.
contents = self.ReadFile(self.args.swarming_targets_file)
swarming_targets = contents.splitlines()
- gn_isolate_map = ast.literal_eval(self.ReadFile(os.path.join(
+ 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:
@@ -359,13 +385,24 @@ 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 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:
runtime_deps_target = target
- if sys.platform == 'win32':
+ if self.platform == 'win32':
deps_path = self.ToAbsPath(path,
runtime_deps_target + '.exe.runtime_deps')
else:
@@ -392,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,
@@ -402,50 +439,37 @@ 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)
- env = None
+ output_dir = self.ParseGYPConfigPath(path)
+ cmd, env = self.GYPCmd(output_dir, vals['gyp_defines'])
if vals['gyp_crosscompile']:
- if self.args.verbose:
- self.Print('Setting GYP_CROSSCOMPILE=1 in the environment')
- env = os.environ.copy()
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.ReadInputJSON(['files', 'targets'])
self.Print()
@@ -453,11 +477,11 @@ class MetaBuildWrapper(object):
self.PrintJSON(inp)
self.Print()
- cmd = self.GYPCmd(output_dir, vals['gyp_defines'], config=gyp_config)
+ 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()
@@ -467,58 +491,11 @@ class MetaBuildWrapper(object):
return ret
- def RunGNIsolate(self, vals):
- build_path = self.args.path[0]
- inp = self.ReadInputJSON(['targets'])
- if self.args.verbose:
- self.Print()
- self.Print('isolate input:')
- self.PrintJSON(inp)
- self.Print()
- output_path = self.args.output_path[0]
-
- for target in inp['targets']:
- runtime_deps_path = self.ToAbsPath(build_path, target + '.runtime_deps')
-
- if not self.Exists(runtime_deps_path):
- self.WriteFailureAndRaise('"%s" does not exist' % runtime_deps_path,
- output_path)
-
- command, extra_files = self.GetIsolateCommand(target, vals, None)
-
- runtime_deps = self.ReadFile(runtime_deps_path).splitlines()
-
-
- isolate_path = self.ToAbsPath(build_path, target + '.isolate')
- self.WriteFile(isolate_path,
- pprint.pformat({
- 'variables': {
- 'command': command,
- 'files': sorted(runtime_deps + extra_files),
- }
- }) + '\n')
-
- self.WriteJSON(
- {
- 'args': [
- '--isolated',
- self.ToSrcRelPath('%s/%s.isolated' % (build_path, target)),
- '--isolate',
- self.ToSrcRelPath('%s/%s.isolate' % (build_path, target)),
- ],
- 'dir': self.chromium_src_dir,
- 'version': 1,
- },
- isolate_path + 'd.gen.json',
- )
-
- return 0
-
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'])
@@ -526,7 +503,7 @@ 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 ''
+ executable_suffix = '.exe' if self.platform == 'win32' else ''
test_type = gn_isolate_map[target]['type']
cmdline = []
@@ -568,12 +545,19 @@ class MetaBuildWrapper(object):
gtest_filter = gn_isolate_map[target]['gtest_filter']
cmdline = [
'../../testing/test_env.py',
- 'browser_tests<(EXECUTABLE_SUFFIX)',
+ './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 = [
@@ -587,42 +571,40 @@ class MetaBuildWrapper(object):
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
@@ -670,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']:
@@ -682,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
@@ -703,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:')
@@ -741,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))
@@ -763,14 +762,15 @@ class MetaBuildWrapper(object):
# This function largely exists so it can be overridden for testing.
print(*args, **kwargs)
- def Run(self, cmd, env=None):
+ 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, env=env)
- if self.args.verbose:
+ if self.args.verbose or force_verbose:
if out:
self.Print(out, end='')
if err:
@@ -799,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:
@@ -808,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)
@@ -824,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