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