217 lines
6.9 KiB
Python
Executable file
217 lines
6.9 KiB
Python
Executable file
#!/usr/bin/env vpython
|
|
# Copyright 2019 The Chromium Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
"""Wraps an executable and any provided arguments into an executable script."""
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
import textwrap
|
|
|
|
|
|
# The bash template passes the python script into vpython via stdin.
|
|
# The interpreter doesn't know about the script, so we have bash
|
|
# inject the script location.
|
|
BASH_TEMPLATE = textwrap.dedent("""\
|
|
#!/usr/bin/env {vpython}
|
|
_SCRIPT_LOCATION = __file__
|
|
{script}
|
|
""")
|
|
|
|
|
|
# The batch template reruns the batch script with vpython, with the -x
|
|
# flag instructing the interpreter to ignore the first line. The interpreter
|
|
# knows about the (batch) script in this case, so it can get the file location
|
|
# directly.
|
|
BATCH_TEMPLATE = textwrap.dedent("""\
|
|
@SETLOCAL ENABLEDELAYEDEXPANSION \
|
|
& {vpython}.bat -x "%~f0" %* \
|
|
& EXIT /B !ERRORLEVEL!
|
|
_SCRIPT_LOCATION = __file__
|
|
{script}
|
|
""")
|
|
|
|
|
|
SCRIPT_TEMPLATES = {
|
|
'bash': BASH_TEMPLATE,
|
|
'batch': BATCH_TEMPLATE,
|
|
}
|
|
|
|
|
|
PY_TEMPLATE = textwrap.dedent("""\
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
_WRAPPED_PATH_RE = re.compile(r'@WrappedPath\(([^)]+)\)')
|
|
_PATH_TO_OUTPUT_DIR = '{path_to_output_dir}'
|
|
_SCRIPT_DIR = os.path.dirname(os.path.realpath(_SCRIPT_LOCATION))
|
|
|
|
|
|
def ExpandWrappedPath(arg):
|
|
m = _WRAPPED_PATH_RE.match(arg)
|
|
if m:
|
|
relpath = os.path.join(
|
|
os.path.relpath(_SCRIPT_DIR), _PATH_TO_OUTPUT_DIR, m.group(1))
|
|
npath = os.path.normpath(relpath)
|
|
if os.path.sep not in npath:
|
|
# If the original path points to something in the current directory,
|
|
# returning the normalized version of it can be a problem.
|
|
# normpath() strips off the './' part of the path
|
|
# ('./foo' becomes 'foo'), which can be a problem if the result
|
|
# is passed to something like os.execvp(); in that case
|
|
# osexecvp() will search $PATH for the executable, rather than
|
|
# just execing the arg directly, and if '.' isn't in $PATH, this
|
|
# results in an error.
|
|
#
|
|
# So, we need to explicitly return './foo' (or '.\\foo' on windows)
|
|
# instead of 'foo'.
|
|
#
|
|
# Hopefully there are no cases where this causes a problem; if
|
|
# there are, we will either need to change the interface to
|
|
# WrappedPath() somehow to distinguish between the two, or
|
|
# somehow ensure that the wrapped executable doesn't hit cases
|
|
# like this.
|
|
return '.' + os.path.sep + npath
|
|
return npath
|
|
return arg
|
|
|
|
|
|
def ExpandWrappedPaths(args):
|
|
for i, arg in enumerate(args):
|
|
args[i] = ExpandWrappedPath(arg)
|
|
return args
|
|
|
|
|
|
def FindIsolatedOutdir(raw_args):
|
|
outdir = None
|
|
i = 0
|
|
remaining_args = []
|
|
while i < len(raw_args):
|
|
if raw_args[i] == '--isolated-outdir' and i < len(raw_args)-1:
|
|
outdir = raw_args[i+1]
|
|
i += 2
|
|
elif raw_args[i].startswith('--isolated-outdir='):
|
|
outdir = raw_args[i][len('--isolated-outdir='):]
|
|
i += 1
|
|
else:
|
|
remaining_args.append(raw_args[i])
|
|
i += 1
|
|
if not outdir and 'ISOLATED_OUTDIR' in os.environ:
|
|
outdir = os.environ['ISOLATED_OUTDIR']
|
|
return outdir, remaining_args
|
|
|
|
|
|
def FilterIsolatedOutdirBasedArgs(outdir, args):
|
|
rargs = []
|
|
i = 0
|
|
while i < len(args):
|
|
if 'ISOLATED_OUTDIR' in args[i]:
|
|
if outdir:
|
|
# Rewrite the arg.
|
|
rargs.append(args[i].replace('${{ISOLATED_OUTDIR}}',
|
|
outdir).replace(
|
|
'$ISOLATED_OUTDIR', outdir))
|
|
i += 1
|
|
else:
|
|
# Simply drop the arg.
|
|
i += 1
|
|
elif (not outdir and
|
|
args[i].startswith('-') and
|
|
'=' not in args[i] and
|
|
i < len(args) - 1 and
|
|
'ISOLATED_OUTDIR' in args[i+1]):
|
|
# Parsing this case is ambiguous; if we're given
|
|
# `--foo $ISOLATED_OUTDIR` we can't tell if $ISOLATED_OUTDIR
|
|
# is meant to be the value of foo, or if foo takes no argument
|
|
# and $ISOLATED_OUTDIR is the first positional arg.
|
|
#
|
|
# We assume the former will be much more common, and so we
|
|
# need to drop --foo and $ISOLATED_OUTDIR.
|
|
i += 2
|
|
else:
|
|
rargs.append(args[i])
|
|
i += 1
|
|
return rargs
|
|
|
|
|
|
def main(raw_args):
|
|
executable_path = ExpandWrappedPath('{executable_path}')
|
|
outdir, remaining_args = FindIsolatedOutdir(raw_args)
|
|
args = {executable_args}
|
|
args = FilterIsolatedOutdirBasedArgs(outdir, args)
|
|
executable_args = ExpandWrappedPaths(args)
|
|
cmd = [executable_path] + args + remaining_args
|
|
if executable_path.endswith('.py'):
|
|
cmd = [sys.executable] + cmd
|
|
return subprocess.call(cmd)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv[1:]))
|
|
""")
|
|
|
|
|
|
def Wrap(args):
|
|
"""Writes a wrapped script according to the provided arguments.
|
|
|
|
Arguments:
|
|
args: an argparse.Namespace object containing command-line arguments
|
|
as parsed by a parser returned by CreateArgumentParser.
|
|
"""
|
|
path_to_output_dir = os.path.relpath(
|
|
args.output_directory,
|
|
os.path.dirname(args.wrapper_script))
|
|
|
|
with open(args.wrapper_script, 'w') as wrapper_script:
|
|
py_contents = PY_TEMPLATE.format(
|
|
path_to_output_dir=path_to_output_dir,
|
|
executable_path=str(args.executable),
|
|
executable_args=str(args.executable_args))
|
|
template = SCRIPT_TEMPLATES[args.script_language]
|
|
wrapper_script.write(
|
|
template.format(script=py_contents, vpython=args.vpython))
|
|
os.chmod(args.wrapper_script, 0o750)
|
|
|
|
return 0
|
|
|
|
|
|
def CreateArgumentParser():
|
|
"""Creates an argparse.ArgumentParser instance."""
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
'--executable',
|
|
help='Executable to wrap.')
|
|
parser.add_argument(
|
|
'--wrapper-script',
|
|
help='Path to which the wrapper script will be written.')
|
|
parser.add_argument(
|
|
'--output-directory',
|
|
help='Path to the output directory.')
|
|
parser.add_argument(
|
|
'--script-language',
|
|
choices=SCRIPT_TEMPLATES.keys(),
|
|
help='Language in which the wrapper script will be written.')
|
|
parser.add_argument('--use-vpython3',
|
|
dest='vpython',
|
|
action='store_const',
|
|
const='vpython3',
|
|
default='vpython',
|
|
help='Use vpython3 instead of vpython')
|
|
parser.add_argument(
|
|
'executable_args', nargs='*',
|
|
help='Arguments to wrap into the executable.')
|
|
return parser
|
|
|
|
|
|
def main(raw_args):
|
|
parser = CreateArgumentParser()
|
|
args = parser.parse_args(raw_args)
|
|
return Wrap(args)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv[1:]))
|