| #!/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:])) |