#!/usr/bin/env python # Copyright 2010 Google Inc. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS-IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Script for running Google-style applications. Unlike normal scripts run through setuptools console_script entry points, Google-style applications must be run as top-level scripts. Given an already-imported module, users can use the RunScriptModule function to set up the appropriate executable environment to spawn a new Python process to run the module as a script. To use this technique in your project, first create a module called e.g. stubs.py with contents like: from google.apputils import run_script_module def RunMyScript(): import my.script run_script_module.RunScriptModule(my.script) def RunMyOtherScript(): import my.other_script run_script_module.RunScriptModule(my.other_script) Then, set up entry points in your setup.py that point to the functions in your stubs module: setup( ... entry_points = { 'console_scripts': [ 'my_script = my.stubs:RunMyScript', 'my_other_script = my.stubs.RunMyOtherScript', ], }, ) When your project is installed, setuptools will generate minimal wrapper scripts to call your stub functions, which in turn execv your script modules. That's it! """ __author__ = 'dborowitz@google.com (Dave Borowitz)' import os import re import sys def FindEnv(progname): """Find the program in the system path. Args: progname: The name of the program. Returns: The full pathname of the program. Raises: AssertionError: if the program was not found. """ for path in os.environ['PATH'].split(':'): fullname = os.path.join(path, progname) if os.access(fullname, os.X_OK): return fullname raise AssertionError( "Could not find an executable named '%s' in the system path" % progname) def GetPdbArgs(python): """Try to get the path to pdb.py and return it in a list. Args: python: The full path to a Python executable. Returns: A list of strings. If a relevant pdb.py was found, this will be ['/path/to/pdb.py']; if not, return ['-m', 'pdb'] and hope for the best. (This latter technique will fail for Python 2.2.) """ # Usually, python is /usr/bin/pythonxx and pdb is /usr/lib/pythonxx/pdb.py components = python.split('/') if len(components) >= 2: pdb_path = '/'.join(components[0:-2] + ['lib'] + components[-1:] + ['pdb.py']) if os.access(pdb_path, os.R_OK): return [pdb_path] # No pdb module found in the python path, default to -m pdb return ['-m', 'pdb'] def StripDelimiters(s, beg, end): if s[0] == beg: assert s[-1] == end return (s[1:-1], True) else: return (s, False) def StripQuotes(s): (s, stripped) = StripDelimiters(s, '"', '"') if not stripped: (s, stripped) = StripDelimiters(s, "'", "'") return s def PrintOurUsage(): """Print usage for the stub script.""" print 'Stub script %s (auto-generated). Options:' % sys.argv[0] print ('--helpstub ' 'Show help for stub script.') print ('--debug_binary ' 'Run python under debugger specified by --debugger.') print ('--debugger= ' "Debugger for --debug_binary. Default: 'gdb --args'.") print ('--debug_script ' 'Run wrapped script with python debugger module (pdb).') print ('--show_command_and_exit ' 'Print command which would be executed and exit.') print ('These options must appear first in the command line, all others will ' 'be passed to the wrapped script.') def RunScriptModule(module): """Run a module as a script. Locates the module's file and runs it in the current interpreter, or optionally a debugger. Args: module: The module object to run. """ args = sys.argv[1:] debug_binary = False debugger = 'gdb --args' debug_script = False show_command_and_exit = False while args: if args[0] == '--helpstub': PrintOurUsage() sys.exit(0) if args[0] == '--debug_binary': debug_binary = True args = args[1:] continue if args[0] == '--debug_script': debug_script = True args = args[1:] continue if args[0] == '--show_command_and_exit': show_command_and_exit = True args = args[1:] continue matchobj = re.match('--debugger=(.+)', args[0]) if matchobj is not None: debugger = StripQuotes(matchobj.group(1)) args = args[1:] continue break # Now look for my main python source file # TODO(dborowitz): This will fail if the module was zipimported, which means # no egg depending on this script runner can be zip_safe. main_filename = module.__file__ assert os.path.exists(main_filename), ('Cannot exec() %r: file not found.' % main_filename) assert os.access(main_filename, os.R_OK), ('Cannot exec() %r: file not' ' readable.' % main_filename) args = [main_filename] + args if debug_binary: debugger_args = debugger.split() program = debugger_args[0] # If pathname is not absolute, determine full path using PATH if not os.path.isabs(program): program = FindEnv(program) python_path = sys.executable command_vec = [python_path] if debug_script: command_vec.extend(GetPdbArgs(python_path)) args = [program] + debugger_args[1:] + command_vec + args elif debug_script: args = [sys.executable] + GetPdbArgs(program) + args else: program = sys.executable args = [sys.executable] + args if show_command_and_exit: print 'program: "%s"' % program print 'args:', args sys.exit(0) try: sys.stdout.flush() os.execv(program, args) except EnvironmentError as e: if not getattr(e, 'filename', None): e.filename = program # Add info to error message raise