1"""distutils.spawn 2 3Provides the 'spawn()' function, a front-end to various platform- 4specific functions for launching another program in a sub-process. 5Also provides the 'find_executable()' to search the path for a given 6executable name. 7""" 8 9__revision__ = "$Id$" 10 11import sys 12import os 13 14from distutils.errors import DistutilsPlatformError, DistutilsExecError 15from distutils import log 16 17def spawn(cmd, search_path=1, verbose=0, dry_run=0): 18 """Run another program, specified as a command list 'cmd', in a new process. 19 20 'cmd' is just the argument list for the new process, ie. 21 cmd[0] is the program to run and cmd[1:] are the rest of its arguments. 22 There is no way to run a program with a name different from that of its 23 executable. 24 25 If 'search_path' is true (the default), the system's executable 26 search path will be used to find the program; otherwise, cmd[0] 27 must be the exact path to the executable. If 'dry_run' is true, 28 the command will not actually be run. 29 30 Raise DistutilsExecError if running the program fails in any way; just 31 return on success. 32 """ 33 if os.name == 'posix': 34 _spawn_posix(cmd, search_path, dry_run=dry_run) 35 elif os.name == 'nt': 36 _spawn_nt(cmd, search_path, dry_run=dry_run) 37 elif os.name == 'os2': 38 _spawn_os2(cmd, search_path, dry_run=dry_run) 39 else: 40 raise DistutilsPlatformError, \ 41 "don't know how to spawn programs on platform '%s'" % os.name 42 43def _nt_quote_args(args): 44 """Quote command-line arguments for DOS/Windows conventions. 45 46 Just wraps every argument which contains blanks in double quotes, and 47 returns a new argument list. 48 """ 49 # XXX this doesn't seem very robust to me -- but if the Windows guys 50 # say it'll work, I guess I'll have to accept it. (What if an arg 51 # contains quotes? What other magic characters, other than spaces, 52 # have to be escaped? Is there an escaping mechanism other than 53 # quoting?) 54 for i, arg in enumerate(args): 55 if ' ' in arg: 56 args[i] = '"%s"' % arg 57 return args 58 59def _spawn_nt(cmd, search_path=1, verbose=0, dry_run=0): 60 executable = cmd[0] 61 cmd = _nt_quote_args(cmd) 62 if search_path: 63 # either we find one or it stays the same 64 executable = find_executable(executable) or executable 65 log.info(' '.join([executable] + cmd[1:])) 66 if not dry_run: 67 # spawn for NT requires a full path to the .exe 68 try: 69 rc = os.spawnv(os.P_WAIT, executable, cmd) 70 except OSError, exc: 71 # this seems to happen when the command isn't found 72 raise DistutilsExecError, \ 73 "command '%s' failed: %s" % (cmd[0], exc[-1]) 74 if rc != 0: 75 # and this reflects the command running but failing 76 raise DistutilsExecError, \ 77 "command '%s' failed with exit status %d" % (cmd[0], rc) 78 79def _spawn_os2(cmd, search_path=1, verbose=0, dry_run=0): 80 executable = cmd[0] 81 if search_path: 82 # either we find one or it stays the same 83 executable = find_executable(executable) or executable 84 log.info(' '.join([executable] + cmd[1:])) 85 if not dry_run: 86 # spawnv for OS/2 EMX requires a full path to the .exe 87 try: 88 rc = os.spawnv(os.P_WAIT, executable, cmd) 89 except OSError, exc: 90 # this seems to happen when the command isn't found 91 raise DistutilsExecError, \ 92 "command '%s' failed: %s" % (cmd[0], exc[-1]) 93 if rc != 0: 94 # and this reflects the command running but failing 95 log.debug("command '%s' failed with exit status %d" % (cmd[0], rc)) 96 raise DistutilsExecError, \ 97 "command '%s' failed with exit status %d" % (cmd[0], rc) 98 99 100def _spawn_posix(cmd, search_path=1, verbose=0, dry_run=0): 101 log.info(' '.join(cmd)) 102 if dry_run: 103 return 104 exec_fn = search_path and os.execvp or os.execv 105 pid = os.fork() 106 107 if pid == 0: # in the child 108 try: 109 exec_fn(cmd[0], cmd) 110 except OSError, e: 111 sys.stderr.write("unable to execute %s: %s\n" % 112 (cmd[0], e.strerror)) 113 os._exit(1) 114 115 sys.stderr.write("unable to execute %s for unknown reasons" % cmd[0]) 116 os._exit(1) 117 else: # in the parent 118 # Loop until the child either exits or is terminated by a signal 119 # (ie. keep waiting if it's merely stopped) 120 while 1: 121 try: 122 pid, status = os.waitpid(pid, 0) 123 except OSError, exc: 124 import errno 125 if exc.errno == errno.EINTR: 126 continue 127 raise DistutilsExecError, \ 128 "command '%s' failed: %s" % (cmd[0], exc[-1]) 129 if os.WIFSIGNALED(status): 130 raise DistutilsExecError, \ 131 "command '%s' terminated by signal %d" % \ 132 (cmd[0], os.WTERMSIG(status)) 133 134 elif os.WIFEXITED(status): 135 exit_status = os.WEXITSTATUS(status) 136 if exit_status == 0: 137 return # hey, it succeeded! 138 else: 139 raise DistutilsExecError, \ 140 "command '%s' failed with exit status %d" % \ 141 (cmd[0], exit_status) 142 143 elif os.WIFSTOPPED(status): 144 continue 145 146 else: 147 raise DistutilsExecError, \ 148 "unknown error executing '%s': termination status %d" % \ 149 (cmd[0], status) 150 151def find_executable(executable, path=None): 152 """Tries to find 'executable' in the directories listed in 'path'. 153 154 A string listing directories separated by 'os.pathsep'; defaults to 155 os.environ['PATH']. Returns the complete filename or None if not found. 156 """ 157 if path is None: 158 path = os.environ['PATH'] 159 paths = path.split(os.pathsep) 160 base, ext = os.path.splitext(executable) 161 162 if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'): 163 executable = executable + '.exe' 164 165 if not os.path.isfile(executable): 166 for p in paths: 167 f = os.path.join(p, executable) 168 if os.path.isfile(f): 169 # the file exists, we have a shot at spawn working 170 return f 171 return None 172 else: 173 return executable 174