##// END OF EJS Templates
Popen doesn't support unicode args on Windows Python 2
MinRK -
Show More
@@ -1,269 +1,272 b''
1 1 """Utilities for launching kernels
2 2
3 3 Authors:
4 4
5 5 * Min Ragan-Kelley
6 6
7 7 """
8 8
9 9 #-----------------------------------------------------------------------------
10 10 # Copyright (C) 2013 The IPython Development Team
11 11 #
12 12 # Distributed under the terms of the BSD License. The full license is in
13 13 # the file COPYING, distributed as part of this software.
14 14 #-----------------------------------------------------------------------------
15 15
16 16 #-----------------------------------------------------------------------------
17 17 # Imports
18 18 #-----------------------------------------------------------------------------
19 19
20 20 import os
21 21 import sys
22 22 from subprocess import Popen, PIPE
23 23
24 from IPython.utils.encoding import getdefaultencoding
24 25 from IPython.utils.py3compat import cast_bytes_py2
25 26
26 27 #-----------------------------------------------------------------------------
27 28 # Launching Kernels
28 29 #-----------------------------------------------------------------------------
29 30
30 31 def swallow_argv(argv, aliases=None, flags=None):
31 32 """strip frontend-specific aliases and flags from an argument list
32 33
33 34 For use primarily in frontend apps that want to pass a subset of command-line
34 35 arguments through to a subprocess, where frontend-specific flags and aliases
35 36 should be removed from the list.
36 37
37 38 Parameters
38 39 ----------
39 40
40 41 argv : list(str)
41 42 The starting argv, to be filtered
42 43 aliases : container of aliases (dict, list, set, etc.)
43 44 The frontend-specific aliases to be removed
44 45 flags : container of flags (dict, list, set, etc.)
45 46 The frontend-specific flags to be removed
46 47
47 48 Returns
48 49 -------
49 50
50 51 argv : list(str)
51 52 The argv list, excluding flags and aliases that have been stripped
52 53 """
53 54
54 55 if aliases is None:
55 56 aliases = set()
56 57 if flags is None:
57 58 flags = set()
58 59
59 60 stripped = list(argv) # copy
60 61
61 62 swallow_next = False
62 63 was_flag = False
63 64 for a in argv:
64 65 if a == '--':
65 66 break
66 67 if swallow_next:
67 68 swallow_next = False
68 69 # last arg was an alias, remove the next one
69 70 # *unless* the last alias has a no-arg flag version, in which
70 71 # case, don't swallow the next arg if it's also a flag:
71 72 if not (was_flag and a.startswith('-')):
72 73 stripped.remove(a)
73 74 continue
74 75 if a.startswith('-'):
75 76 split = a.lstrip('-').split('=')
76 77 name = split[0]
77 78 # we use startswith because argparse accepts any arg to be specified
78 79 # by any leading section, as long as it is unique,
79 80 # so `--no-br` means `--no-browser` in the notebook, etc.
80 81 if any(alias.startswith(name) for alias in aliases):
81 82 stripped.remove(a)
82 83 if len(split) == 1:
83 84 # alias passed with arg via space
84 85 swallow_next = True
85 86 # could have been a flag that matches an alias, e.g. `existing`
86 87 # in which case, we might not swallow the next arg
87 88 was_flag = name in flags
88 89 elif len(split) == 1 and any(flag.startswith(name) for flag in flags):
89 90 # strip flag, but don't swallow next, as flags don't take args
90 91 stripped.remove(a)
91 92
92 93 # return shortened list
93 94 return stripped
94 95
95 96
96 97 def make_ipkernel_cmd(code, executable=None, extra_arguments=[], **kw):
97 98 """Build Popen command list for launching an IPython kernel.
98 99
99 100 Parameters
100 101 ----------
101 102 code : str,
102 103 A string of Python code that imports and executes a kernel entry point.
103 104
104 105 executable : str, optional (default sys.executable)
105 106 The Python executable to use for the kernel process.
106 107
107 108 extra_arguments : list, optional
108 109 A list of extra arguments to pass when executing the launch code.
109 110
110 111 Returns
111 112 -------
112 113
113 114 A Popen command list
114 115 """
115 116
116 117 # Build the kernel launch command.
117 118 if executable is None:
118 119 executable = sys.executable
119 120 arguments = [ executable, '-c', code, '-f', '{connection_file}' ]
120 121 arguments.extend(extra_arguments)
121 122
122 123 # Spawn a kernel.
123 124 if sys.platform == 'win32':
124 125
125 126 # If the kernel is running on pythonw and stdout/stderr are not been
126 127 # re-directed, it will crash when more than 4KB of data is written to
127 128 # stdout or stderr. This is a bug that has been with Python for a very
128 129 # long time; see http://bugs.python.org/issue706263.
129 130 # A cleaner solution to this problem would be to pass os.devnull to
130 131 # Popen directly. Unfortunately, that does not work.
131 132 if executable.endswith('pythonw.exe'):
132 133 arguments.append('--no-stdout')
133 134 arguments.append('--no-stderr')
134 135
135 136 return arguments
136 137
137 138
138 139 def launch_kernel(cmd, stdin=None, stdout=None, stderr=None,
139 140 independent=False,
140 141 cwd=None, ipython_kernel=True,
141 142 **kw
142 143 ):
143 144 """ Launches a localhost kernel, binding to the specified ports.
144 145
145 146 Parameters
146 147 ----------
147 148 cmd : Popen list,
148 149 A string of Python code that imports and executes a kernel entry point.
149 150
150 151 stdin, stdout, stderr : optional (default None)
151 152 Standards streams, as defined in subprocess.Popen.
152 153
153 154 independent : bool, optional (default False)
154 155 If set, the kernel process is guaranteed to survive if this process
155 156 dies. If not set, an effort is made to ensure that the kernel is killed
156 157 when this process dies. Note that in this case it is still good practice
157 158 to kill kernels manually before exiting.
158 159
159 160 cwd : path, optional
160 161 The working dir of the kernel process (default: cwd of this process).
161 162
162 163 ipython_kernel : bool, optional
163 164 Whether the kernel is an official IPython one,
164 165 and should get a bit of special treatment.
165 166
166 167 Returns
167 168 -------
168 169
169 170 Popen instance for the kernel subprocess
170 171 """
171 172
172 173 # Popen will fail (sometimes with a deadlock) if stdin, stdout, and stderr
173 174 # are invalid. Unfortunately, there is in general no way to detect whether
174 175 # they are valid. The following two blocks redirect them to (temporary)
175 176 # pipes in certain important cases.
176 177
177 178 # If this process has been backgrounded, our stdin is invalid. Since there
178 179 # is no compelling reason for the kernel to inherit our stdin anyway, we'll
179 180 # place this one safe and always redirect.
180 181 redirect_in = True
181 182 _stdin = PIPE if stdin is None else stdin
182 183
183 184 # If this process in running on pythonw, we know that stdin, stdout, and
184 185 # stderr are all invalid.
185 186 redirect_out = sys.executable.endswith('pythonw.exe')
186 187 if redirect_out:
187 188 _stdout = PIPE if stdout is None else stdout
188 189 _stderr = PIPE if stderr is None else stderr
189 190 else:
190 191 _stdout, _stderr = stdout, stderr
191 192
193 encoding = getdefaultencoding(prefer_stream=False)
194
192 195 # Spawn a kernel.
193 196 if sys.platform == 'win32':
194
197 # Popen on Python 2 on Windows cannot handle unicode args or cwd
198 cmd = [ cast_bytes_py2(c, encoding) for c in cmd ]
195 199 if cwd:
196 # Popen on Python 2 on Windows cannot handle unicode cwd.
197 200 cwd = cast_bytes_py2(cwd, sys.getfilesystemencoding() or 'ascii')
198 201
199 202 from IPython.kernel.zmq.parentpoller import ParentPollerWindows
200 203 # Create a Win32 event for interrupting the kernel.
201 204 interrupt_event = ParentPollerWindows.create_interrupt_event()
202 205 if ipython_kernel:
203 206 cmd += [ '--interrupt=%i' % interrupt_event ]
204 207
205 208 # If the kernel is running on pythonw and stdout/stderr are not been
206 209 # re-directed, it will crash when more than 4KB of data is written to
207 210 # stdout or stderr. This is a bug that has been with Python for a very
208 211 # long time; see http://bugs.python.org/issue706263.
209 212 # A cleaner solution to this problem would be to pass os.devnull to
210 213 # Popen directly. Unfortunately, that does not work.
211 214 if cmd[0].endswith('pythonw.exe'):
212 215 if stdout is None:
213 216 cmd.append('--no-stdout')
214 217 if stderr is None:
215 218 cmd.append('--no-stderr')
216 219
217 220 # Launch the kernel process.
218 221 if independent:
219 222 proc = Popen(cmd,
220 223 creationflags=512, # CREATE_NEW_PROCESS_GROUP
221 224 stdin=_stdin, stdout=_stdout, stderr=_stderr, env=os.environ)
222 225 else:
223 226 if ipython_kernel:
224 227 try:
225 228 from _winapi import DuplicateHandle, GetCurrentProcess, \
226 229 DUPLICATE_SAME_ACCESS
227 230 except:
228 231 from _subprocess import DuplicateHandle, GetCurrentProcess, \
229 232 DUPLICATE_SAME_ACCESS
230 233 pid = GetCurrentProcess()
231 234 handle = DuplicateHandle(pid, pid, pid, 0,
232 235 True, # Inheritable by new processes.
233 236 DUPLICATE_SAME_ACCESS)
234 237 cmd +=[ '--parent=%i' % handle ]
235 238
236 239
237 240 proc = Popen(cmd,
238 241 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=os.environ)
239 242
240 243 # Attach the interrupt event to the Popen objet so it can be used later.
241 244 proc.win32_interrupt_event = interrupt_event
242 245
243 246 else:
244 247 if independent:
245 248 proc = Popen(cmd, preexec_fn=lambda: os.setsid(),
246 249 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=os.environ)
247 250 else:
248 251 if ipython_kernel:
249 252 cmd += ['--parent=1']
250 253 proc = Popen(cmd,
251 254 stdin=_stdin, stdout=_stdout, stderr=_stderr, cwd=cwd, env=os.environ)
252 255
253 256 # Clean up pipes created to work around Popen bug.
254 257 if redirect_in:
255 258 if stdin is None:
256 259 proc.stdin.close()
257 260 if redirect_out:
258 261 if stdout is None:
259 262 proc.stdout.close()
260 263 if stderr is None:
261 264 proc.stderr.close()
262 265
263 266 return proc
264 267
265 268 __all__ = [
266 269 'swallow_argv',
267 270 'make_ipkernel_cmd',
268 271 'launch_kernel',
269 272 ]
General Comments 0
You need to be logged in to leave comments. Login now