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