1 """distutils.spawn 2 3 Provides the 'spawn()' function, a front-end to various platform- 4 specific functions for launching another program in a sub-process. 5 Also provides the 'find_executable()' to search the path for a given 6 executable name. 7 """ 8 9 __revision__ = "$Id$" 10 11 import sys 12 import os 13 14 from distutils.errors import DistutilsPlatformError, DistutilsExecError 15 from distutils import log 16 17 def 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 43 def _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 59 def _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 79 def _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 if sys.platform == 'darwin': 100 from distutils import sysconfig 101 _cfg_target = None 102 _cfg_target_split = None 103 104 def _spawn_posix(cmd, search_path=1, verbose=0, dry_run=0): 105 log.info(' '.join(cmd)) 106 if dry_run: 107 return 108 exec_fn = search_path and os.execvp or os.execv 109 exec_args = [cmd[0], cmd] 110 if sys.platform == 'darwin': 111 global _cfg_target, _cfg_target_split 112 if _cfg_target is None: 113 _cfg_target = sysconfig.get_config_var( 114 'MACOSX_DEPLOYMENT_TARGET') or '' 115 if _cfg_target: 116 _cfg_target_split = [int(x) for x in _cfg_target.split('.')] 117 if _cfg_target: 118 # ensure that the deployment target of build process is not less 119 # than that used when the interpreter was built. This ensures 120 # extension modules are built with correct compatibility values 121 cur_target = os.environ.get('MACOSX_DEPLOYMENT_TARGET', _cfg_target) 122 if _cfg_target_split > [int(x) for x in cur_target.split('.')]: 123 my_msg = ('$MACOSX_DEPLOYMENT_TARGET mismatch: ' 124 'now "%s" but "%s" during configure' 125 % (cur_target, _cfg_target)) 126 raise DistutilsPlatformError(my_msg) 127 env = dict(os.environ, 128 MACOSX_DEPLOYMENT_TARGET=cur_target) 129 exec_fn = search_path and os.execvpe or os.execve 130 exec_args.append(env) 131 pid = os.fork() 132 133 if pid == 0: # in the child 134 try: 135 exec_fn(*exec_args) 136 except OSError, e: 137 sys.stderr.write("unable to execute %s: %s\n" % 138 (cmd[0], e.strerror)) 139 os._exit(1) 140 141 sys.stderr.write("unable to execute %s for unknown reasons" % cmd[0]) 142 os._exit(1) 143 else: # in the parent 144 # Loop until the child either exits or is terminated by a signal 145 # (ie. keep waiting if it's merely stopped) 146 while 1: 147 try: 148 pid, status = os.waitpid(pid, 0) 149 except OSError, exc: 150 import errno 151 if exc.errno == errno.EINTR: 152 continue 153 raise DistutilsExecError, \ 154 "command '%s' failed: %s" % (cmd[0], exc[-1]) 155 if os.WIFSIGNALED(status): 156 raise DistutilsExecError, \ 157 "command '%s' terminated by signal %d" % \ 158 (cmd[0], os.WTERMSIG(status)) 159 160 elif os.WIFEXITED(status): 161 exit_status = os.WEXITSTATUS(status) 162 if exit_status == 0: 163 return # hey, it succeeded! 164 else: 165 raise DistutilsExecError, \ 166 "command '%s' failed with exit status %d" % \ 167 (cmd[0], exit_status) 168 169 elif os.WIFSTOPPED(status): 170 continue 171 172 else: 173 raise DistutilsExecError, \ 174 "unknown error executing '%s': termination status %d" % \ 175 (cmd[0], status) 176 177 def find_executable(executable, path=None): 178 """Tries to find 'executable' in the directories listed in 'path'. 179 180 A string listing directories separated by 'os.pathsep'; defaults to 181 os.environ['PATH']. Returns the complete filename or None if not found. 182 """ 183 if path is None: 184 path = os.environ['PATH'] 185 paths = path.split(os.pathsep) 186 base, ext = os.path.splitext(executable) 187 188 if (sys.platform == 'win32' or os.name == 'os2') and (ext != '.exe'): 189 executable = executable + '.exe' 190 191 if not os.path.isfile(executable): 192 for p in paths: 193 f = os.path.join(p, executable) 194 if os.path.isfile(f): 195 # the file exists, we have a shot at spawn working 196 return f 197 return None 198 else: 199 return executable 200