##// END OF EJS Templates
remove appnope from external...
MinRK -
Show More
@@ -1,278 +1,273 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Event loop integration for the ZeroMQ-based kernels."""
2 """Event loop integration for the ZeroMQ-based kernels."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6
6
7 import os
7 import os
8 import sys
8 import sys
9
9
10 import zmq
10 import zmq
11
11
12 from IPython.config.application import Application
12 from IPython.config.application import Application
13 from IPython.utils import io
13 from IPython.utils import io
14
14 from IPython.lib.inputhook import _use_appnope
15
16 def _on_os_x_10_9():
17 import platform
18 from distutils.version import LooseVersion as V
19 return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9')
20
15
21 def _notify_stream_qt(kernel, stream):
16 def _notify_stream_qt(kernel, stream):
22
17
23 from IPython.external.qt_for_kernel import QtCore
18 from IPython.external.qt_for_kernel import QtCore
24
19
25 if _on_os_x_10_9() and kernel._darwin_app_nap:
20 if _use_appnope() and kernel._darwin_app_nap:
26 from IPython.external.appnope import nope_scope as context
21 from appnope import nope_scope as context
27 else:
22 else:
28 from IPython.core.interactiveshell import NoOpContext as context
23 from IPython.core.interactiveshell import NoOpContext as context
29
24
30 def process_stream_events():
25 def process_stream_events():
31 while stream.getsockopt(zmq.EVENTS) & zmq.POLLIN:
26 while stream.getsockopt(zmq.EVENTS) & zmq.POLLIN:
32 with context():
27 with context():
33 kernel.do_one_iteration()
28 kernel.do_one_iteration()
34
29
35 fd = stream.getsockopt(zmq.FD)
30 fd = stream.getsockopt(zmq.FD)
36 notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app)
31 notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app)
37 notifier.activated.connect(process_stream_events)
32 notifier.activated.connect(process_stream_events)
38
33
39 # mapping of keys to loop functions
34 # mapping of keys to loop functions
40 loop_map = {
35 loop_map = {
41 'inline': None,
36 'inline': None,
42 'nbagg': None,
37 'nbagg': None,
43 'notebook': None,
38 'notebook': None,
44 None : None,
39 None : None,
45 }
40 }
46
41
47 def register_integration(*toolkitnames):
42 def register_integration(*toolkitnames):
48 """Decorator to register an event loop to integrate with the IPython kernel
43 """Decorator to register an event loop to integrate with the IPython kernel
49
44
50 The decorator takes names to register the event loop as for the %gui magic.
45 The decorator takes names to register the event loop as for the %gui magic.
51 You can provide alternative names for the same toolkit.
46 You can provide alternative names for the same toolkit.
52
47
53 The decorated function should take a single argument, the IPython kernel
48 The decorated function should take a single argument, the IPython kernel
54 instance, arrange for the event loop to call ``kernel.do_one_iteration()``
49 instance, arrange for the event loop to call ``kernel.do_one_iteration()``
55 at least every ``kernel._poll_interval`` seconds, and start the event loop.
50 at least every ``kernel._poll_interval`` seconds, and start the event loop.
56
51
57 :mod:`IPython.kernel.zmq.eventloops` provides and registers such functions
52 :mod:`IPython.kernel.zmq.eventloops` provides and registers such functions
58 for a few common event loops.
53 for a few common event loops.
59 """
54 """
60 def decorator(func):
55 def decorator(func):
61 for name in toolkitnames:
56 for name in toolkitnames:
62 loop_map[name] = func
57 loop_map[name] = func
63 return func
58 return func
64
59
65 return decorator
60 return decorator
66
61
67
62
68 @register_integration('qt', 'qt4')
63 @register_integration('qt', 'qt4')
69 def loop_qt4(kernel):
64 def loop_qt4(kernel):
70 """Start a kernel with PyQt4 event loop integration."""
65 """Start a kernel with PyQt4 event loop integration."""
71
66
72 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
67 from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4
73
68
74 kernel.app = get_app_qt4([" "])
69 kernel.app = get_app_qt4([" "])
75 kernel.app.setQuitOnLastWindowClosed(False)
70 kernel.app.setQuitOnLastWindowClosed(False)
76
71
77 for s in kernel.shell_streams:
72 for s in kernel.shell_streams:
78 _notify_stream_qt(kernel, s)
73 _notify_stream_qt(kernel, s)
79
74
80 start_event_loop_qt4(kernel.app)
75 start_event_loop_qt4(kernel.app)
81
76
82 @register_integration('qt5')
77 @register_integration('qt5')
83 def loop_qt5(kernel):
78 def loop_qt5(kernel):
84 """Start a kernel with PyQt5 event loop integration."""
79 """Start a kernel with PyQt5 event loop integration."""
85 os.environ['QT_API'] = 'pyqt5'
80 os.environ['QT_API'] = 'pyqt5'
86 return loop_qt4(kernel)
81 return loop_qt4(kernel)
87
82
88
83
89 @register_integration('wx')
84 @register_integration('wx')
90 def loop_wx(kernel):
85 def loop_wx(kernel):
91 """Start a kernel with wx event loop support."""
86 """Start a kernel with wx event loop support."""
92
87
93 import wx
88 import wx
94 from IPython.lib.guisupport import start_event_loop_wx
89 from IPython.lib.guisupport import start_event_loop_wx
95
90
96 if _on_os_x_10_9() and kernel._darwin_app_nap:
91 if _use_appnope() and kernel._darwin_app_nap:
97 # we don't hook up App Nap contexts for Wx,
92 # we don't hook up App Nap contexts for Wx,
98 # just disable it outright.
93 # just disable it outright.
99 from IPython.external.appnope import nope
94 from appnope import nope
100 nope()
95 nope()
101
96
102 doi = kernel.do_one_iteration
97 doi = kernel.do_one_iteration
103 # Wx uses milliseconds
98 # Wx uses milliseconds
104 poll_interval = int(1000*kernel._poll_interval)
99 poll_interval = int(1000*kernel._poll_interval)
105
100
106 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
101 # We have to put the wx.Timer in a wx.Frame for it to fire properly.
107 # We make the Frame hidden when we create it in the main app below.
102 # We make the Frame hidden when we create it in the main app below.
108 class TimerFrame(wx.Frame):
103 class TimerFrame(wx.Frame):
109 def __init__(self, func):
104 def __init__(self, func):
110 wx.Frame.__init__(self, None, -1)
105 wx.Frame.__init__(self, None, -1)
111 self.timer = wx.Timer(self)
106 self.timer = wx.Timer(self)
112 # Units for the timer are in milliseconds
107 # Units for the timer are in milliseconds
113 self.timer.Start(poll_interval)
108 self.timer.Start(poll_interval)
114 self.Bind(wx.EVT_TIMER, self.on_timer)
109 self.Bind(wx.EVT_TIMER, self.on_timer)
115 self.func = func
110 self.func = func
116
111
117 def on_timer(self, event):
112 def on_timer(self, event):
118 self.func()
113 self.func()
119
114
120 # We need a custom wx.App to create our Frame subclass that has the
115 # We need a custom wx.App to create our Frame subclass that has the
121 # wx.Timer to drive the ZMQ event loop.
116 # wx.Timer to drive the ZMQ event loop.
122 class IPWxApp(wx.App):
117 class IPWxApp(wx.App):
123 def OnInit(self):
118 def OnInit(self):
124 self.frame = TimerFrame(doi)
119 self.frame = TimerFrame(doi)
125 self.frame.Show(False)
120 self.frame.Show(False)
126 return True
121 return True
127
122
128 # The redirect=False here makes sure that wx doesn't replace
123 # The redirect=False here makes sure that wx doesn't replace
129 # sys.stdout/stderr with its own classes.
124 # sys.stdout/stderr with its own classes.
130 kernel.app = IPWxApp(redirect=False)
125 kernel.app = IPWxApp(redirect=False)
131
126
132 # The import of wx on Linux sets the handler for signal.SIGINT
127 # The import of wx on Linux sets the handler for signal.SIGINT
133 # to 0. This is a bug in wx or gtk. We fix by just setting it
128 # to 0. This is a bug in wx or gtk. We fix by just setting it
134 # back to the Python default.
129 # back to the Python default.
135 import signal
130 import signal
136 if not callable(signal.getsignal(signal.SIGINT)):
131 if not callable(signal.getsignal(signal.SIGINT)):
137 signal.signal(signal.SIGINT, signal.default_int_handler)
132 signal.signal(signal.SIGINT, signal.default_int_handler)
138
133
139 start_event_loop_wx(kernel.app)
134 start_event_loop_wx(kernel.app)
140
135
141
136
142 @register_integration('tk')
137 @register_integration('tk')
143 def loop_tk(kernel):
138 def loop_tk(kernel):
144 """Start a kernel with the Tk event loop."""
139 """Start a kernel with the Tk event loop."""
145
140
146 try:
141 try:
147 from tkinter import Tk # Py 3
142 from tkinter import Tk # Py 3
148 except ImportError:
143 except ImportError:
149 from Tkinter import Tk # Py 2
144 from Tkinter import Tk # Py 2
150 doi = kernel.do_one_iteration
145 doi = kernel.do_one_iteration
151 # Tk uses milliseconds
146 # Tk uses milliseconds
152 poll_interval = int(1000*kernel._poll_interval)
147 poll_interval = int(1000*kernel._poll_interval)
153 # For Tkinter, we create a Tk object and call its withdraw method.
148 # For Tkinter, we create a Tk object and call its withdraw method.
154 class Timer(object):
149 class Timer(object):
155 def __init__(self, func):
150 def __init__(self, func):
156 self.app = Tk()
151 self.app = Tk()
157 self.app.withdraw()
152 self.app.withdraw()
158 self.func = func
153 self.func = func
159
154
160 def on_timer(self):
155 def on_timer(self):
161 self.func()
156 self.func()
162 self.app.after(poll_interval, self.on_timer)
157 self.app.after(poll_interval, self.on_timer)
163
158
164 def start(self):
159 def start(self):
165 self.on_timer() # Call it once to get things going.
160 self.on_timer() # Call it once to get things going.
166 self.app.mainloop()
161 self.app.mainloop()
167
162
168 kernel.timer = Timer(doi)
163 kernel.timer = Timer(doi)
169 kernel.timer.start()
164 kernel.timer.start()
170
165
171
166
172 @register_integration('gtk')
167 @register_integration('gtk')
173 def loop_gtk(kernel):
168 def loop_gtk(kernel):
174 """Start the kernel, coordinating with the GTK event loop"""
169 """Start the kernel, coordinating with the GTK event loop"""
175 from .gui.gtkembed import GTKEmbed
170 from .gui.gtkembed import GTKEmbed
176
171
177 gtk_kernel = GTKEmbed(kernel)
172 gtk_kernel = GTKEmbed(kernel)
178 gtk_kernel.start()
173 gtk_kernel.start()
179
174
180
175
181 @register_integration('gtk3')
176 @register_integration('gtk3')
182 def loop_gtk3(kernel):
177 def loop_gtk3(kernel):
183 """Start the kernel, coordinating with the GTK event loop"""
178 """Start the kernel, coordinating with the GTK event loop"""
184 from .gui.gtk3embed import GTKEmbed
179 from .gui.gtk3embed import GTKEmbed
185
180
186 gtk_kernel = GTKEmbed(kernel)
181 gtk_kernel = GTKEmbed(kernel)
187 gtk_kernel.start()
182 gtk_kernel.start()
188
183
189
184
190 @register_integration('osx')
185 @register_integration('osx')
191 def loop_cocoa(kernel):
186 def loop_cocoa(kernel):
192 """Start the kernel, coordinating with the Cocoa CFRunLoop event loop
187 """Start the kernel, coordinating with the Cocoa CFRunLoop event loop
193 via the matplotlib MacOSX backend.
188 via the matplotlib MacOSX backend.
194 """
189 """
195 import matplotlib
190 import matplotlib
196 if matplotlib.__version__ < '1.1.0':
191 if matplotlib.__version__ < '1.1.0':
197 kernel.log.warn(
192 kernel.log.warn(
198 "MacOSX backend in matplotlib %s doesn't have a Timer, "
193 "MacOSX backend in matplotlib %s doesn't have a Timer, "
199 "falling back on Tk for CFRunLoop integration. Note that "
194 "falling back on Tk for CFRunLoop integration. Note that "
200 "even this won't work if Tk is linked against X11 instead of "
195 "even this won't work if Tk is linked against X11 instead of "
201 "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, "
196 "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, "
202 "you must use matplotlib >= 1.1.0, or a native libtk."
197 "you must use matplotlib >= 1.1.0, or a native libtk."
203 )
198 )
204 return loop_tk(kernel)
199 return loop_tk(kernel)
205
200
206 from matplotlib.backends.backend_macosx import TimerMac, show
201 from matplotlib.backends.backend_macosx import TimerMac, show
207
202
208 # scale interval for sec->ms
203 # scale interval for sec->ms
209 poll_interval = int(1000*kernel._poll_interval)
204 poll_interval = int(1000*kernel._poll_interval)
210
205
211 real_excepthook = sys.excepthook
206 real_excepthook = sys.excepthook
212 def handle_int(etype, value, tb):
207 def handle_int(etype, value, tb):
213 """don't let KeyboardInterrupts look like crashes"""
208 """don't let KeyboardInterrupts look like crashes"""
214 if etype is KeyboardInterrupt:
209 if etype is KeyboardInterrupt:
215 io.raw_print("KeyboardInterrupt caught in CFRunLoop")
210 io.raw_print("KeyboardInterrupt caught in CFRunLoop")
216 else:
211 else:
217 real_excepthook(etype, value, tb)
212 real_excepthook(etype, value, tb)
218
213
219 # add doi() as a Timer to the CFRunLoop
214 # add doi() as a Timer to the CFRunLoop
220 def doi():
215 def doi():
221 # restore excepthook during IPython code
216 # restore excepthook during IPython code
222 sys.excepthook = real_excepthook
217 sys.excepthook = real_excepthook
223 kernel.do_one_iteration()
218 kernel.do_one_iteration()
224 # and back:
219 # and back:
225 sys.excepthook = handle_int
220 sys.excepthook = handle_int
226
221
227 t = TimerMac(poll_interval)
222 t = TimerMac(poll_interval)
228 t.add_callback(doi)
223 t.add_callback(doi)
229 t.start()
224 t.start()
230
225
231 # but still need a Poller for when there are no active windows,
226 # but still need a Poller for when there are no active windows,
232 # during which time mainloop() returns immediately
227 # during which time mainloop() returns immediately
233 poller = zmq.Poller()
228 poller = zmq.Poller()
234 if kernel.control_stream:
229 if kernel.control_stream:
235 poller.register(kernel.control_stream.socket, zmq.POLLIN)
230 poller.register(kernel.control_stream.socket, zmq.POLLIN)
236 for stream in kernel.shell_streams:
231 for stream in kernel.shell_streams:
237 poller.register(stream.socket, zmq.POLLIN)
232 poller.register(stream.socket, zmq.POLLIN)
238
233
239 while True:
234 while True:
240 try:
235 try:
241 # double nested try/except, to properly catch KeyboardInterrupt
236 # double nested try/except, to properly catch KeyboardInterrupt
242 # due to pyzmq Issue #130
237 # due to pyzmq Issue #130
243 try:
238 try:
244 # don't let interrupts during mainloop invoke crash_handler:
239 # don't let interrupts during mainloop invoke crash_handler:
245 sys.excepthook = handle_int
240 sys.excepthook = handle_int
246 show.mainloop()
241 show.mainloop()
247 sys.excepthook = real_excepthook
242 sys.excepthook = real_excepthook
248 # use poller if mainloop returned (no windows)
243 # use poller if mainloop returned (no windows)
249 # scale by extra factor of 10, since it's a real poll
244 # scale by extra factor of 10, since it's a real poll
250 poller.poll(10*poll_interval)
245 poller.poll(10*poll_interval)
251 kernel.do_one_iteration()
246 kernel.do_one_iteration()
252 except:
247 except:
253 raise
248 raise
254 except KeyboardInterrupt:
249 except KeyboardInterrupt:
255 # Ctrl-C shouldn't crash the kernel
250 # Ctrl-C shouldn't crash the kernel
256 io.raw_print("KeyboardInterrupt caught in kernel")
251 io.raw_print("KeyboardInterrupt caught in kernel")
257 finally:
252 finally:
258 # ensure excepthook is restored
253 # ensure excepthook is restored
259 sys.excepthook = real_excepthook
254 sys.excepthook = real_excepthook
260
255
261
256
262
257
263 def enable_gui(gui, kernel=None):
258 def enable_gui(gui, kernel=None):
264 """Enable integration with a given GUI"""
259 """Enable integration with a given GUI"""
265 if gui not in loop_map:
260 if gui not in loop_map:
266 e = "Invalid GUI request %r, valid ones are:%s" % (gui, loop_map.keys())
261 e = "Invalid GUI request %r, valid ones are:%s" % (gui, loop_map.keys())
267 raise ValueError(e)
262 raise ValueError(e)
268 if kernel is None:
263 if kernel is None:
269 if Application.initialized():
264 if Application.initialized():
270 kernel = getattr(Application.instance(), 'kernel', None)
265 kernel = getattr(Application.instance(), 'kernel', None)
271 if kernel is None:
266 if kernel is None:
272 raise RuntimeError("You didn't specify a kernel,"
267 raise RuntimeError("You didn't specify a kernel,"
273 " and no IPython Application with a kernel appears to be running."
268 " and no IPython Application with a kernel appears to be running."
274 )
269 )
275 loop = loop_map[gui]
270 loop = loop_map[gui]
276 if loop and kernel.eventloop is not None and kernel.eventloop is not loop:
271 if loop and kernel.eventloop is not None and kernel.eventloop is not loop:
277 raise RuntimeError("Cannot activate multiple GUI eventloops")
272 raise RuntimeError("Cannot activate multiple GUI eventloops")
278 kernel.eventloop = loop
273 kernel.eventloop = loop
@@ -1,584 +1,587 b''
1 # coding: utf-8
1 # coding: utf-8
2 """
2 """
3 Inputhook management for GUI event loop integration.
3 Inputhook management for GUI event loop integration.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 # Copyright (c) IPython Development Team.
7 # Copyright (C) 2008-2011 The IPython Development Team
7 # Distributed under the terms of the Modified BSD License.
8 #
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
12
13 #-----------------------------------------------------------------------------
14 # Imports
15 #-----------------------------------------------------------------------------
16
8
17 try:
9 try:
18 import ctypes
10 import ctypes
19 except ImportError:
11 except ImportError:
20 ctypes = None
12 ctypes = None
21 except SystemError: # IronPython issue, 2/8/2014
13 except SystemError: # IronPython issue, 2/8/2014
22 ctypes = None
14 ctypes = None
23 import os
15 import os
16 import platform
24 import sys
17 import sys
25 from distutils.version import LooseVersion as V
18 from distutils.version import LooseVersion as V
26
19
27 from IPython.utils.warn import warn
20 from IPython.utils.warn import warn
28
21
29 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
30 # Constants
23 # Constants
31 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
32
25
33 # Constants for identifying the GUI toolkits.
26 # Constants for identifying the GUI toolkits.
34 GUI_WX = 'wx'
27 GUI_WX = 'wx'
35 GUI_QT = 'qt'
28 GUI_QT = 'qt'
36 GUI_QT4 = 'qt4'
29 GUI_QT4 = 'qt4'
37 GUI_GTK = 'gtk'
30 GUI_GTK = 'gtk'
38 GUI_TK = 'tk'
31 GUI_TK = 'tk'
39 GUI_OSX = 'osx'
32 GUI_OSX = 'osx'
40 GUI_GLUT = 'glut'
33 GUI_GLUT = 'glut'
41 GUI_PYGLET = 'pyglet'
34 GUI_PYGLET = 'pyglet'
42 GUI_GTK3 = 'gtk3'
35 GUI_GTK3 = 'gtk3'
43 GUI_NONE = 'none' # i.e. disable
36 GUI_NONE = 'none' # i.e. disable
44
37
45 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
46 # Utilities
39 # Utilities
47 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
48
41
49 def _stdin_ready_posix():
42 def _stdin_ready_posix():
50 """Return True if there's something to read on stdin (posix version)."""
43 """Return True if there's something to read on stdin (posix version)."""
51 infds, outfds, erfds = select.select([sys.stdin],[],[],0)
44 infds, outfds, erfds = select.select([sys.stdin],[],[],0)
52 return bool(infds)
45 return bool(infds)
53
46
54 def _stdin_ready_nt():
47 def _stdin_ready_nt():
55 """Return True if there's something to read on stdin (nt version)."""
48 """Return True if there's something to read on stdin (nt version)."""
56 return msvcrt.kbhit()
49 return msvcrt.kbhit()
57
50
58 def _stdin_ready_other():
51 def _stdin_ready_other():
59 """Return True, assuming there's something to read on stdin."""
52 """Return True, assuming there's something to read on stdin."""
60 return True #
53 return True
54
55 def _use_appnope():
56 """Should we use appnope for dealing with OS X app nap?
61
57
58 Checks if we are on OS X 10.9 or greater.
59 """
60 return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9')
62
61
63 def _ignore_CTRL_C_posix():
62 def _ignore_CTRL_C_posix():
64 """Ignore CTRL+C (SIGINT)."""
63 """Ignore CTRL+C (SIGINT)."""
65 signal.signal(signal.SIGINT, signal.SIG_IGN)
64 signal.signal(signal.SIGINT, signal.SIG_IGN)
66
65
67 def _allow_CTRL_C_posix():
66 def _allow_CTRL_C_posix():
68 """Take CTRL+C into account (SIGINT)."""
67 """Take CTRL+C into account (SIGINT)."""
69 signal.signal(signal.SIGINT, signal.default_int_handler)
68 signal.signal(signal.SIGINT, signal.default_int_handler)
70
69
71 def _ignore_CTRL_C_other():
70 def _ignore_CTRL_C_other():
72 """Ignore CTRL+C (not implemented)."""
71 """Ignore CTRL+C (not implemented)."""
73 pass
72 pass
74
73
75 def _allow_CTRL_C_other():
74 def _allow_CTRL_C_other():
76 """Take CTRL+C into account (not implemented)."""
75 """Take CTRL+C into account (not implemented)."""
77 pass
76 pass
78
77
79 if os.name == 'posix':
78 if os.name == 'posix':
80 import select
79 import select
81 import signal
80 import signal
82 stdin_ready = _stdin_ready_posix
81 stdin_ready = _stdin_ready_posix
83 ignore_CTRL_C = _ignore_CTRL_C_posix
82 ignore_CTRL_C = _ignore_CTRL_C_posix
84 allow_CTRL_C = _allow_CTRL_C_posix
83 allow_CTRL_C = _allow_CTRL_C_posix
85 elif os.name == 'nt':
84 elif os.name == 'nt':
86 import msvcrt
85 import msvcrt
87 stdin_ready = _stdin_ready_nt
86 stdin_ready = _stdin_ready_nt
88 ignore_CTRL_C = _ignore_CTRL_C_other
87 ignore_CTRL_C = _ignore_CTRL_C_other
89 allow_CTRL_C = _allow_CTRL_C_other
88 allow_CTRL_C = _allow_CTRL_C_other
90 else:
89 else:
91 stdin_ready = _stdin_ready_other
90 stdin_ready = _stdin_ready_other
92 ignore_CTRL_C = _ignore_CTRL_C_other
91 ignore_CTRL_C = _ignore_CTRL_C_other
93 allow_CTRL_C = _allow_CTRL_C_other
92 allow_CTRL_C = _allow_CTRL_C_other
94
93
95
94
96 #-----------------------------------------------------------------------------
95 #-----------------------------------------------------------------------------
97 # Main InputHookManager class
96 # Main InputHookManager class
98 #-----------------------------------------------------------------------------
97 #-----------------------------------------------------------------------------
99
98
100
99
101 class InputHookManager(object):
100 class InputHookManager(object):
102 """Manage PyOS_InputHook for different GUI toolkits.
101 """Manage PyOS_InputHook for different GUI toolkits.
103
102
104 This class installs various hooks under ``PyOSInputHook`` to handle
103 This class installs various hooks under ``PyOSInputHook`` to handle
105 GUI event loop integration.
104 GUI event loop integration.
106 """
105 """
107
106
108 def __init__(self):
107 def __init__(self):
109 if ctypes is None:
108 if ctypes is None:
110 warn("IPython GUI event loop requires ctypes, %gui will not be available")
109 warn("IPython GUI event loop requires ctypes, %gui will not be available")
111 return
110 return
112 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
111 self.PYFUNC = ctypes.PYFUNCTYPE(ctypes.c_int)
113 self.guihooks = {}
112 self.guihooks = {}
114 self.aliases = {}
113 self.aliases = {}
115 self.apps = {}
114 self.apps = {}
116 self._reset()
115 self._reset()
117
116
118 def _reset(self):
117 def _reset(self):
119 self._callback_pyfunctype = None
118 self._callback_pyfunctype = None
120 self._callback = None
119 self._callback = None
121 self._installed = False
120 self._installed = False
122 self._current_gui = None
121 self._current_gui = None
123
122
124 def get_pyos_inputhook(self):
123 def get_pyos_inputhook(self):
125 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
124 """Return the current PyOS_InputHook as a ctypes.c_void_p."""
126 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
125 return ctypes.c_void_p.in_dll(ctypes.pythonapi,"PyOS_InputHook")
127
126
128 def get_pyos_inputhook_as_func(self):
127 def get_pyos_inputhook_as_func(self):
129 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
128 """Return the current PyOS_InputHook as a ctypes.PYFUNCYPE."""
130 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
129 return self.PYFUNC.in_dll(ctypes.pythonapi,"PyOS_InputHook")
131
130
132 def set_inputhook(self, callback):
131 def set_inputhook(self, callback):
133 """Set PyOS_InputHook to callback and return the previous one."""
132 """Set PyOS_InputHook to callback and return the previous one."""
134 # On platforms with 'readline' support, it's all too likely to
133 # On platforms with 'readline' support, it's all too likely to
135 # have a KeyboardInterrupt signal delivered *even before* an
134 # have a KeyboardInterrupt signal delivered *even before* an
136 # initial ``try:`` clause in the callback can be executed, so
135 # initial ``try:`` clause in the callback can be executed, so
137 # we need to disable CTRL+C in this situation.
136 # we need to disable CTRL+C in this situation.
138 ignore_CTRL_C()
137 ignore_CTRL_C()
139 self._callback = callback
138 self._callback = callback
140 self._callback_pyfunctype = self.PYFUNC(callback)
139 self._callback_pyfunctype = self.PYFUNC(callback)
141 pyos_inputhook_ptr = self.get_pyos_inputhook()
140 pyos_inputhook_ptr = self.get_pyos_inputhook()
142 original = self.get_pyos_inputhook_as_func()
141 original = self.get_pyos_inputhook_as_func()
143 pyos_inputhook_ptr.value = \
142 pyos_inputhook_ptr.value = \
144 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
143 ctypes.cast(self._callback_pyfunctype, ctypes.c_void_p).value
145 self._installed = True
144 self._installed = True
146 return original
145 return original
147
146
148 def clear_inputhook(self, app=None):
147 def clear_inputhook(self, app=None):
149 """Set PyOS_InputHook to NULL and return the previous one.
148 """Set PyOS_InputHook to NULL and return the previous one.
150
149
151 Parameters
150 Parameters
152 ----------
151 ----------
153 app : optional, ignored
152 app : optional, ignored
154 This parameter is allowed only so that clear_inputhook() can be
153 This parameter is allowed only so that clear_inputhook() can be
155 called with a similar interface as all the ``enable_*`` methods. But
154 called with a similar interface as all the ``enable_*`` methods. But
156 the actual value of the parameter is ignored. This uniform interface
155 the actual value of the parameter is ignored. This uniform interface
157 makes it easier to have user-level entry points in the main IPython
156 makes it easier to have user-level entry points in the main IPython
158 app like :meth:`enable_gui`."""
157 app like :meth:`enable_gui`."""
159 pyos_inputhook_ptr = self.get_pyos_inputhook()
158 pyos_inputhook_ptr = self.get_pyos_inputhook()
160 original = self.get_pyos_inputhook_as_func()
159 original = self.get_pyos_inputhook_as_func()
161 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
160 pyos_inputhook_ptr.value = ctypes.c_void_p(None).value
162 allow_CTRL_C()
161 allow_CTRL_C()
163 self._reset()
162 self._reset()
164 return original
163 return original
165
164
166 def clear_app_refs(self, gui=None):
165 def clear_app_refs(self, gui=None):
167 """Clear IPython's internal reference to an application instance.
166 """Clear IPython's internal reference to an application instance.
168
167
169 Whenever we create an app for a user on qt4 or wx, we hold a
168 Whenever we create an app for a user on qt4 or wx, we hold a
170 reference to the app. This is needed because in some cases bad things
169 reference to the app. This is needed because in some cases bad things
171 can happen if a user doesn't hold a reference themselves. This
170 can happen if a user doesn't hold a reference themselves. This
172 method is provided to clear the references we are holding.
171 method is provided to clear the references we are holding.
173
172
174 Parameters
173 Parameters
175 ----------
174 ----------
176 gui : None or str
175 gui : None or str
177 If None, clear all app references. If ('wx', 'qt4') clear
176 If None, clear all app references. If ('wx', 'qt4') clear
178 the app for that toolkit. References are not held for gtk or tk
177 the app for that toolkit. References are not held for gtk or tk
179 as those toolkits don't have the notion of an app.
178 as those toolkits don't have the notion of an app.
180 """
179 """
181 if gui is None:
180 if gui is None:
182 self.apps = {}
181 self.apps = {}
183 elif gui in self.apps:
182 elif gui in self.apps:
184 del self.apps[gui]
183 del self.apps[gui]
185
184
186 def register(self, toolkitname, *aliases):
185 def register(self, toolkitname, *aliases):
187 """Register a class to provide the event loop for a given GUI.
186 """Register a class to provide the event loop for a given GUI.
188
187
189 This is intended to be used as a class decorator. It should be passed
188 This is intended to be used as a class decorator. It should be passed
190 the names with which to register this GUI integration. The classes
189 the names with which to register this GUI integration. The classes
191 themselves should subclass :class:`InputHookBase`.
190 themselves should subclass :class:`InputHookBase`.
192
191
193 ::
192 ::
194
193
195 @inputhook_manager.register('qt')
194 @inputhook_manager.register('qt')
196 class QtInputHook(InputHookBase):
195 class QtInputHook(InputHookBase):
197 def enable(self, app=None):
196 def enable(self, app=None):
198 ...
197 ...
199 """
198 """
200 def decorator(cls):
199 def decorator(cls):
201 inst = cls(self)
200 inst = cls(self)
202 self.guihooks[toolkitname] = inst
201 self.guihooks[toolkitname] = inst
203 for a in aliases:
202 for a in aliases:
204 self.aliases[a] = toolkitname
203 self.aliases[a] = toolkitname
205 return cls
204 return cls
206 return decorator
205 return decorator
207
206
208 def current_gui(self):
207 def current_gui(self):
209 """Return a string indicating the currently active GUI or None."""
208 """Return a string indicating the currently active GUI or None."""
210 return self._current_gui
209 return self._current_gui
211
210
212 def enable_gui(self, gui=None, app=None):
211 def enable_gui(self, gui=None, app=None):
213 """Switch amongst GUI input hooks by name.
212 """Switch amongst GUI input hooks by name.
214
213
215 This is a higher level method than :meth:`set_inputhook` - it uses the
214 This is a higher level method than :meth:`set_inputhook` - it uses the
216 GUI name to look up a registered object which enables the input hook
215 GUI name to look up a registered object which enables the input hook
217 for that GUI.
216 for that GUI.
218
217
219 Parameters
218 Parameters
220 ----------
219 ----------
221 gui : optional, string or None
220 gui : optional, string or None
222 If None (or 'none'), clears input hook, otherwise it must be one
221 If None (or 'none'), clears input hook, otherwise it must be one
223 of the recognized GUI names (see ``GUI_*`` constants in module).
222 of the recognized GUI names (see ``GUI_*`` constants in module).
224
223
225 app : optional, existing application object.
224 app : optional, existing application object.
226 For toolkits that have the concept of a global app, you can supply an
225 For toolkits that have the concept of a global app, you can supply an
227 existing one. If not given, the toolkit will be probed for one, and if
226 existing one. If not given, the toolkit will be probed for one, and if
228 none is found, a new one will be created. Note that GTK does not have
227 none is found, a new one will be created. Note that GTK does not have
229 this concept, and passing an app if ``gui=="GTK"`` will raise an error.
228 this concept, and passing an app if ``gui=="GTK"`` will raise an error.
230
229
231 Returns
230 Returns
232 -------
231 -------
233 The output of the underlying gui switch routine, typically the actual
232 The output of the underlying gui switch routine, typically the actual
234 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
233 PyOS_InputHook wrapper object or the GUI toolkit app created, if there was
235 one.
234 one.
236 """
235 """
237 if gui in (None, GUI_NONE):
236 if gui in (None, GUI_NONE):
238 return self.disable_gui()
237 return self.disable_gui()
239
238
240 if gui in self.aliases:
239 if gui in self.aliases:
241 return self.enable_gui(self.aliases[gui], app)
240 return self.enable_gui(self.aliases[gui], app)
242
241
243 try:
242 try:
244 gui_hook = self.guihooks[gui]
243 gui_hook = self.guihooks[gui]
245 except KeyError:
244 except KeyError:
246 e = "Invalid GUI request {!r}, valid ones are: {}"
245 e = "Invalid GUI request {!r}, valid ones are: {}"
247 raise ValueError(e.format(gui, ', '.join(self.guihooks)))
246 raise ValueError(e.format(gui, ', '.join(self.guihooks)))
248 self._current_gui = gui
247 self._current_gui = gui
249
248
250 app = gui_hook.enable(app)
249 app = gui_hook.enable(app)
251 if app is not None:
250 if app is not None:
252 app._in_event_loop = True
251 app._in_event_loop = True
253 self.apps[gui] = app
252 self.apps[gui] = app
254 return app
253 return app
255
254
256 def disable_gui(self):
255 def disable_gui(self):
257 """Disable GUI event loop integration.
256 """Disable GUI event loop integration.
258
257
259 If an application was registered, this sets its ``_in_event_loop``
258 If an application was registered, this sets its ``_in_event_loop``
260 attribute to False. It then calls :meth:`clear_inputhook`.
259 attribute to False. It then calls :meth:`clear_inputhook`.
261 """
260 """
262 gui = self._current_gui
261 gui = self._current_gui
263 if gui in self.apps:
262 if gui in self.apps:
264 self.apps[gui]._in_event_loop = False
263 self.apps[gui]._in_event_loop = False
265 return self.clear_inputhook()
264 return self.clear_inputhook()
266
265
267 class InputHookBase(object):
266 class InputHookBase(object):
268 """Base class for input hooks for specific toolkits.
267 """Base class for input hooks for specific toolkits.
269
268
270 Subclasses should define an :meth:`enable` method with one argument, ``app``,
269 Subclasses should define an :meth:`enable` method with one argument, ``app``,
271 which will either be an instance of the toolkit's application class, or None.
270 which will either be an instance of the toolkit's application class, or None.
272 They may also define a :meth:`disable` method with no arguments.
271 They may also define a :meth:`disable` method with no arguments.
273 """
272 """
274 def __init__(self, manager):
273 def __init__(self, manager):
275 self.manager = manager
274 self.manager = manager
276
275
277 def disable(self):
276 def disable(self):
278 pass
277 pass
279
278
280 inputhook_manager = InputHookManager()
279 inputhook_manager = InputHookManager()
281
280
282 @inputhook_manager.register('osx')
281 @inputhook_manager.register('osx')
283 class NullInputHook(InputHookBase):
282 class NullInputHook(InputHookBase):
284 """A null inputhook that doesn't need to do anything"""
283 """A null inputhook that doesn't need to do anything"""
285 def enable(self, app=None):
284 def enable(self, app=None):
286 pass
285 pass
287
286
288 @inputhook_manager.register('wx')
287 @inputhook_manager.register('wx')
289 class WxInputHook(InputHookBase):
288 class WxInputHook(InputHookBase):
290 def enable(self, app=None):
289 def enable(self, app=None):
291 """Enable event loop integration with wxPython.
290 """Enable event loop integration with wxPython.
292
291
293 Parameters
292 Parameters
294 ----------
293 ----------
295 app : WX Application, optional.
294 app : WX Application, optional.
296 Running application to use. If not given, we probe WX for an
295 Running application to use. If not given, we probe WX for an
297 existing application object, and create a new one if none is found.
296 existing application object, and create a new one if none is found.
298
297
299 Notes
298 Notes
300 -----
299 -----
301 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
300 This methods sets the ``PyOS_InputHook`` for wxPython, which allows
302 the wxPython to integrate with terminal based applications like
301 the wxPython to integrate with terminal based applications like
303 IPython.
302 IPython.
304
303
305 If ``app`` is not given we probe for an existing one, and return it if
304 If ``app`` is not given we probe for an existing one, and return it if
306 found. If no existing app is found, we create an :class:`wx.App` as
305 found. If no existing app is found, we create an :class:`wx.App` as
307 follows::
306 follows::
308
307
309 import wx
308 import wx
310 app = wx.App(redirect=False, clearSigInt=False)
309 app = wx.App(redirect=False, clearSigInt=False)
311 """
310 """
312 import wx
311 import wx
313
312
314 wx_version = V(wx.__version__).version
313 wx_version = V(wx.__version__).version
315
314
316 if wx_version < [2, 8]:
315 if wx_version < [2, 8]:
317 raise ValueError("requires wxPython >= 2.8, but you have %s" % wx.__version__)
316 raise ValueError("requires wxPython >= 2.8, but you have %s" % wx.__version__)
318
317
319 from IPython.lib.inputhookwx import inputhook_wx
318 from IPython.lib.inputhookwx import inputhook_wx
320 from IPython.external.appnope import nope
321 self.manager.set_inputhook(inputhook_wx)
319 self.manager.set_inputhook(inputhook_wx)
322 nope()
320 if _use_appnope():
321 from appnope import nope
322 nope()
323
323
324 import wx
324 import wx
325 if app is None:
325 if app is None:
326 app = wx.GetApp()
326 app = wx.GetApp()
327 if app is None:
327 if app is None:
328 app = wx.App(redirect=False, clearSigInt=False)
328 app = wx.App(redirect=False, clearSigInt=False)
329
329
330 return app
330 return app
331
331
332 def disable(self):
332 def disable(self):
333 """Disable event loop integration with wxPython.
333 """Disable event loop integration with wxPython.
334
334
335 This restores appnapp on OS X
335 This restores appnapp on OS X
336 """
336 """
337 from IPython.external.appnope import nap
337 if _use_appnope():
338 nap()
338 from appnope import nap
339 nap()
339
340
340 @inputhook_manager.register('qt', 'qt4')
341 @inputhook_manager.register('qt', 'qt4')
341 class Qt4InputHook(InputHookBase):
342 class Qt4InputHook(InputHookBase):
342 def enable(self, app=None):
343 def enable(self, app=None):
343 """Enable event loop integration with PyQt4.
344 """Enable event loop integration with PyQt4.
344
345
345 Parameters
346 Parameters
346 ----------
347 ----------
347 app : Qt Application, optional.
348 app : Qt Application, optional.
348 Running application to use. If not given, we probe Qt for an
349 Running application to use. If not given, we probe Qt for an
349 existing application object, and create a new one if none is found.
350 existing application object, and create a new one if none is found.
350
351
351 Notes
352 Notes
352 -----
353 -----
353 This methods sets the PyOS_InputHook for PyQt4, which allows
354 This methods sets the PyOS_InputHook for PyQt4, which allows
354 the PyQt4 to integrate with terminal based applications like
355 the PyQt4 to integrate with terminal based applications like
355 IPython.
356 IPython.
356
357
357 If ``app`` is not given we probe for an existing one, and return it if
358 If ``app`` is not given we probe for an existing one, and return it if
358 found. If no existing app is found, we create an :class:`QApplication`
359 found. If no existing app is found, we create an :class:`QApplication`
359 as follows::
360 as follows::
360
361
361 from PyQt4 import QtCore
362 from PyQt4 import QtCore
362 app = QtGui.QApplication(sys.argv)
363 app = QtGui.QApplication(sys.argv)
363 """
364 """
364 from IPython.lib.inputhookqt4 import create_inputhook_qt4
365 from IPython.lib.inputhookqt4 import create_inputhook_qt4
365 from IPython.external.appnope import nope
366 app, inputhook_qt4 = create_inputhook_qt4(self, app)
366 app, inputhook_qt4 = create_inputhook_qt4(self.manager, app)
367 self.manager.set_inputhook(inputhook_qt4)
367 self.manager.set_inputhook(inputhook_qt4)
368 nope()
368 if _use_appnope():
369 from appnope import nope
370 nope()
369
371
370 return app
372 return app
371
373
372 def disable_qt4(self):
374 def disable_qt4(self):
373 """Disable event loop integration with PyQt4.
375 """Disable event loop integration with PyQt4.
374
376
375 This restores appnapp on OS X
377 This restores appnapp on OS X
376 """
378 """
377 from IPython.external.appnope import nap
379 if _use_appnope():
378 nap()
380 from appnope import nap
381 nap()
379
382
380
383
381 @inputhook_manager.register('qt5')
384 @inputhook_manager.register('qt5')
382 class Qt5InputHook(Qt4InputHook):
385 class Qt5InputHook(Qt4InputHook):
383 def enable(self, app=None):
386 def enable(self, app=None):
384 os.environ['QT_API'] = 'pyqt5'
387 os.environ['QT_API'] = 'pyqt5'
385 return Qt4InputHook.enable(self, app)
388 return Qt4InputHook.enable(self, app)
386
389
387
390
388 @inputhook_manager.register('gtk')
391 @inputhook_manager.register('gtk')
389 class GtkInputHook(InputHookBase):
392 class GtkInputHook(InputHookBase):
390 def enable(self, app=None):
393 def enable(self, app=None):
391 """Enable event loop integration with PyGTK.
394 """Enable event loop integration with PyGTK.
392
395
393 Parameters
396 Parameters
394 ----------
397 ----------
395 app : ignored
398 app : ignored
396 Ignored, it's only a placeholder to keep the call signature of all
399 Ignored, it's only a placeholder to keep the call signature of all
397 gui activation methods consistent, which simplifies the logic of
400 gui activation methods consistent, which simplifies the logic of
398 supporting magics.
401 supporting magics.
399
402
400 Notes
403 Notes
401 -----
404 -----
402 This methods sets the PyOS_InputHook for PyGTK, which allows
405 This methods sets the PyOS_InputHook for PyGTK, which allows
403 the PyGTK to integrate with terminal based applications like
406 the PyGTK to integrate with terminal based applications like
404 IPython.
407 IPython.
405 """
408 """
406 import gtk
409 import gtk
407 try:
410 try:
408 gtk.set_interactive(True)
411 gtk.set_interactive(True)
409 except AttributeError:
412 except AttributeError:
410 # For older versions of gtk, use our own ctypes version
413 # For older versions of gtk, use our own ctypes version
411 from IPython.lib.inputhookgtk import inputhook_gtk
414 from IPython.lib.inputhookgtk import inputhook_gtk
412 self.manager.set_inputhook(inputhook_gtk)
415 self.manager.set_inputhook(inputhook_gtk)
413
416
414
417
415 @inputhook_manager.register('tk')
418 @inputhook_manager.register('tk')
416 class TkInputHook(InputHookBase):
419 class TkInputHook(InputHookBase):
417 def enable(self, app=None):
420 def enable(self, app=None):
418 """Enable event loop integration with Tk.
421 """Enable event loop integration with Tk.
419
422
420 Parameters
423 Parameters
421 ----------
424 ----------
422 app : toplevel :class:`Tkinter.Tk` widget, optional.
425 app : toplevel :class:`Tkinter.Tk` widget, optional.
423 Running toplevel widget to use. If not given, we probe Tk for an
426 Running toplevel widget to use. If not given, we probe Tk for an
424 existing one, and create a new one if none is found.
427 existing one, and create a new one if none is found.
425
428
426 Notes
429 Notes
427 -----
430 -----
428 If you have already created a :class:`Tkinter.Tk` object, the only
431 If you have already created a :class:`Tkinter.Tk` object, the only
429 thing done by this method is to register with the
432 thing done by this method is to register with the
430 :class:`InputHookManager`, since creating that object automatically
433 :class:`InputHookManager`, since creating that object automatically
431 sets ``PyOS_InputHook``.
434 sets ``PyOS_InputHook``.
432 """
435 """
433 if app is None:
436 if app is None:
434 try:
437 try:
435 from tkinter import Tk # Py 3
438 from tkinter import Tk # Py 3
436 except ImportError:
439 except ImportError:
437 from Tkinter import Tk # Py 2
440 from Tkinter import Tk # Py 2
438 app = Tk()
441 app = Tk()
439 app.withdraw()
442 app.withdraw()
440 self.manager.apps[GUI_TK] = app
443 self.manager.apps[GUI_TK] = app
441 return app
444 return app
442
445
443
446
444 @inputhook_manager.register('glut')
447 @inputhook_manager.register('glut')
445 class GlutInputHook(InputHookBase):
448 class GlutInputHook(InputHookBase):
446 def enable(self, app=None):
449 def enable(self, app=None):
447 """Enable event loop integration with GLUT.
450 """Enable event loop integration with GLUT.
448
451
449 Parameters
452 Parameters
450 ----------
453 ----------
451
454
452 app : ignored
455 app : ignored
453 Ignored, it's only a placeholder to keep the call signature of all
456 Ignored, it's only a placeholder to keep the call signature of all
454 gui activation methods consistent, which simplifies the logic of
457 gui activation methods consistent, which simplifies the logic of
455 supporting magics.
458 supporting magics.
456
459
457 Notes
460 Notes
458 -----
461 -----
459
462
460 This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to
463 This methods sets the PyOS_InputHook for GLUT, which allows the GLUT to
461 integrate with terminal based applications like IPython. Due to GLUT
464 integrate with terminal based applications like IPython. Due to GLUT
462 limitations, it is currently not possible to start the event loop
465 limitations, it is currently not possible to start the event loop
463 without first creating a window. You should thus not create another
466 without first creating a window. You should thus not create another
464 window but use instead the created one. See 'gui-glut.py' in the
467 window but use instead the created one. See 'gui-glut.py' in the
465 docs/examples/lib directory.
468 docs/examples/lib directory.
466
469
467 The default screen mode is set to:
470 The default screen mode is set to:
468 glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH
471 glut.GLUT_DOUBLE | glut.GLUT_RGBA | glut.GLUT_DEPTH
469 """
472 """
470
473
471 import OpenGL.GLUT as glut
474 import OpenGL.GLUT as glut
472 from IPython.lib.inputhookglut import glut_display_mode, \
475 from IPython.lib.inputhookglut import glut_display_mode, \
473 glut_close, glut_display, \
476 glut_close, glut_display, \
474 glut_idle, inputhook_glut
477 glut_idle, inputhook_glut
475
478
476 if GUI_GLUT not in self.manager.apps:
479 if GUI_GLUT not in self.manager.apps:
477 glut.glutInit( sys.argv )
480 glut.glutInit( sys.argv )
478 glut.glutInitDisplayMode( glut_display_mode )
481 glut.glutInitDisplayMode( glut_display_mode )
479 # This is specific to freeglut
482 # This is specific to freeglut
480 if bool(glut.glutSetOption):
483 if bool(glut.glutSetOption):
481 glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE,
484 glut.glutSetOption( glut.GLUT_ACTION_ON_WINDOW_CLOSE,
482 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS )
485 glut.GLUT_ACTION_GLUTMAINLOOP_RETURNS )
483 glut.glutCreateWindow( sys.argv[0] )
486 glut.glutCreateWindow( sys.argv[0] )
484 glut.glutReshapeWindow( 1, 1 )
487 glut.glutReshapeWindow( 1, 1 )
485 glut.glutHideWindow( )
488 glut.glutHideWindow( )
486 glut.glutWMCloseFunc( glut_close )
489 glut.glutWMCloseFunc( glut_close )
487 glut.glutDisplayFunc( glut_display )
490 glut.glutDisplayFunc( glut_display )
488 glut.glutIdleFunc( glut_idle )
491 glut.glutIdleFunc( glut_idle )
489 else:
492 else:
490 glut.glutWMCloseFunc( glut_close )
493 glut.glutWMCloseFunc( glut_close )
491 glut.glutDisplayFunc( glut_display )
494 glut.glutDisplayFunc( glut_display )
492 glut.glutIdleFunc( glut_idle)
495 glut.glutIdleFunc( glut_idle)
493 self.manager.set_inputhook( inputhook_glut )
496 self.manager.set_inputhook( inputhook_glut )
494
497
495
498
496 def disable(self):
499 def disable(self):
497 """Disable event loop integration with glut.
500 """Disable event loop integration with glut.
498
501
499 This sets PyOS_InputHook to NULL and set the display function to a
502 This sets PyOS_InputHook to NULL and set the display function to a
500 dummy one and set the timer to a dummy timer that will be triggered
503 dummy one and set the timer to a dummy timer that will be triggered
501 very far in the future.
504 very far in the future.
502 """
505 """
503 import OpenGL.GLUT as glut
506 import OpenGL.GLUT as glut
504 from glut_support import glutMainLoopEvent
507 from glut_support import glutMainLoopEvent
505
508
506 glut.glutHideWindow() # This is an event to be processed below
509 glut.glutHideWindow() # This is an event to be processed below
507 glutMainLoopEvent()
510 glutMainLoopEvent()
508 super(GlutInputHook, self).disable()
511 super(GlutInputHook, self).disable()
509
512
510 @inputhook_manager.register('pyglet')
513 @inputhook_manager.register('pyglet')
511 class PygletInputHook(InputHookBase):
514 class PygletInputHook(InputHookBase):
512 def enable(self, app=None):
515 def enable(self, app=None):
513 """Enable event loop integration with pyglet.
516 """Enable event loop integration with pyglet.
514
517
515 Parameters
518 Parameters
516 ----------
519 ----------
517 app : ignored
520 app : ignored
518 Ignored, it's only a placeholder to keep the call signature of all
521 Ignored, it's only a placeholder to keep the call signature of all
519 gui activation methods consistent, which simplifies the logic of
522 gui activation methods consistent, which simplifies the logic of
520 supporting magics.
523 supporting magics.
521
524
522 Notes
525 Notes
523 -----
526 -----
524 This methods sets the ``PyOS_InputHook`` for pyglet, which allows
527 This methods sets the ``PyOS_InputHook`` for pyglet, which allows
525 pyglet to integrate with terminal based applications like
528 pyglet to integrate with terminal based applications like
526 IPython.
529 IPython.
527
530
528 """
531 """
529 from IPython.lib.inputhookpyglet import inputhook_pyglet
532 from IPython.lib.inputhookpyglet import inputhook_pyglet
530 self.manager.set_inputhook(inputhook_pyglet)
533 self.manager.set_inputhook(inputhook_pyglet)
531 return app
534 return app
532
535
533
536
534 @inputhook_manager.register('gtk3')
537 @inputhook_manager.register('gtk3')
535 class Gtk3InputHook(InputHookBase):
538 class Gtk3InputHook(InputHookBase):
536 def enable(self, app=None):
539 def enable(self, app=None):
537 """Enable event loop integration with Gtk3 (gir bindings).
540 """Enable event loop integration with Gtk3 (gir bindings).
538
541
539 Parameters
542 Parameters
540 ----------
543 ----------
541 app : ignored
544 app : ignored
542 Ignored, it's only a placeholder to keep the call signature of all
545 Ignored, it's only a placeholder to keep the call signature of all
543 gui activation methods consistent, which simplifies the logic of
546 gui activation methods consistent, which simplifies the logic of
544 supporting magics.
547 supporting magics.
545
548
546 Notes
549 Notes
547 -----
550 -----
548 This methods sets the PyOS_InputHook for Gtk3, which allows
551 This methods sets the PyOS_InputHook for Gtk3, which allows
549 the Gtk3 to integrate with terminal based applications like
552 the Gtk3 to integrate with terminal based applications like
550 IPython.
553 IPython.
551 """
554 """
552 from IPython.lib.inputhookgtk3 import inputhook_gtk3
555 from IPython.lib.inputhookgtk3 import inputhook_gtk3
553 self.manager.set_inputhook(inputhook_gtk3)
556 self.manager.set_inputhook(inputhook_gtk3)
554
557
555
558
556 clear_inputhook = inputhook_manager.clear_inputhook
559 clear_inputhook = inputhook_manager.clear_inputhook
557 set_inputhook = inputhook_manager.set_inputhook
560 set_inputhook = inputhook_manager.set_inputhook
558 current_gui = inputhook_manager.current_gui
561 current_gui = inputhook_manager.current_gui
559 clear_app_refs = inputhook_manager.clear_app_refs
562 clear_app_refs = inputhook_manager.clear_app_refs
560 enable_gui = inputhook_manager.enable_gui
563 enable_gui = inputhook_manager.enable_gui
561 disable_gui = inputhook_manager.disable_gui
564 disable_gui = inputhook_manager.disable_gui
562 register = inputhook_manager.register
565 register = inputhook_manager.register
563 guis = inputhook_manager.guihooks
566 guis = inputhook_manager.guihooks
564
567
565 # Deprecated methods: kept for backwards compatibility, do not use in new code
568 # Deprecated methods: kept for backwards compatibility, do not use in new code
566 def _make_deprecated_enable(name):
569 def _make_deprecated_enable(name):
567 def enable_toolkit(app=None):
570 def enable_toolkit(app=None):
568 warn("This function is deprecated - use enable_gui(%r) instead" % name)
571 warn("This function is deprecated - use enable_gui(%r) instead" % name)
569 inputhook_manager.enable_gui(name, app)
572 inputhook_manager.enable_gui(name, app)
570
573
571 enable_osx = _make_deprecated_enable('osx')
574 enable_osx = _make_deprecated_enable('osx')
572 enable_wx = _make_deprecated_enable('wx')
575 enable_wx = _make_deprecated_enable('wx')
573 enable_qt4 = _make_deprecated_enable('qt4')
576 enable_qt4 = _make_deprecated_enable('qt4')
574 enable_gtk = _make_deprecated_enable('gtk')
577 enable_gtk = _make_deprecated_enable('gtk')
575 enable_tk = _make_deprecated_enable('tk')
578 enable_tk = _make_deprecated_enable('tk')
576 enable_glut = _make_deprecated_enable('glut')
579 enable_glut = _make_deprecated_enable('glut')
577 enable_pyglet = _make_deprecated_enable('pyglet')
580 enable_pyglet = _make_deprecated_enable('pyglet')
578 enable_gtk3 = _make_deprecated_enable('gtk3')
581 enable_gtk3 = _make_deprecated_enable('gtk3')
579
582
580 def _deprecated_disable():
583 def _deprecated_disable():
581 warn("This function is deprecated: use disable_gui() instead")
584 warn("This function is deprecated: use disable_gui() instead")
582 inputhook_manager.disable_gui()
585 inputhook_manager.disable_gui()
583 disable_wx = disable_qt4 = disable_gtk = disable_gtk3 = disable_glut = \
586 disable_wx = disable_qt4 = disable_gtk = disable_gtk3 = disable_glut = \
584 disable_pyglet = disable_osx = _deprecated_disable
587 disable_pyglet = disable_osx = _deprecated_disable
@@ -1,345 +1,346 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2 # -*- coding: utf-8 -*-
3 """Setup script for IPython.
3 """Setup script for IPython.
4
4
5 Under Posix environments it works like a typical setup.py script.
5 Under Posix environments it works like a typical setup.py script.
6 Under Windows, the command sdist is not supported, since IPython
6 Under Windows, the command sdist is not supported, since IPython
7 requires utilities which are not available under Windows."""
7 requires utilities which are not available under Windows."""
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (c) 2008-2011, IPython Development Team.
10 # Copyright (c) 2008-2011, IPython Development Team.
11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
14 #
14 #
15 # Distributed under the terms of the Modified BSD License.
15 # Distributed under the terms of the Modified BSD License.
16 #
16 #
17 # The full license is in the file COPYING.rst, distributed with this software.
17 # The full license is in the file COPYING.rst, distributed with this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Minimal Python version sanity check
21 # Minimal Python version sanity check
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 from __future__ import print_function
23 from __future__ import print_function
24
24
25 import sys
25 import sys
26
26
27 # This check is also made in IPython/__init__, don't forget to update both when
27 # This check is also made in IPython/__init__, don't forget to update both when
28 # changing Python version requirements.
28 # changing Python version requirements.
29 v = sys.version_info
29 v = sys.version_info
30 if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)):
30 if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)):
31 error = "ERROR: IPython requires Python version 2.7 or 3.3 or above."
31 error = "ERROR: IPython requires Python version 2.7 or 3.3 or above."
32 print(error, file=sys.stderr)
32 print(error, file=sys.stderr)
33 sys.exit(1)
33 sys.exit(1)
34
34
35 PY3 = (sys.version_info[0] >= 3)
35 PY3 = (sys.version_info[0] >= 3)
36
36
37 # At least we're on the python version we need, move on.
37 # At least we're on the python version we need, move on.
38
38
39 #-------------------------------------------------------------------------------
39 #-------------------------------------------------------------------------------
40 # Imports
40 # Imports
41 #-------------------------------------------------------------------------------
41 #-------------------------------------------------------------------------------
42
42
43 # Stdlib imports
43 # Stdlib imports
44 import os
44 import os
45 import shutil
45 import shutil
46
46
47 from glob import glob
47 from glob import glob
48
48
49 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
49 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
50 # update it when the contents of directories change.
50 # update it when the contents of directories change.
51 if os.path.exists('MANIFEST'): os.remove('MANIFEST')
51 if os.path.exists('MANIFEST'): os.remove('MANIFEST')
52
52
53 from distutils.core import setup
53 from distutils.core import setup
54
54
55 # Our own imports
55 # Our own imports
56 from setupbase import target_update
56 from setupbase import target_update
57
57
58 from setupbase import (
58 from setupbase import (
59 setup_args,
59 setup_args,
60 find_packages,
60 find_packages,
61 find_package_data,
61 find_package_data,
62 check_package_data_first,
62 check_package_data_first,
63 find_entry_points,
63 find_entry_points,
64 build_scripts_entrypt,
64 build_scripts_entrypt,
65 find_data_files,
65 find_data_files,
66 check_for_dependencies,
66 check_for_dependencies,
67 git_prebuild,
67 git_prebuild,
68 check_submodule_status,
68 check_submodule_status,
69 update_submodules,
69 update_submodules,
70 require_submodules,
70 require_submodules,
71 UpdateSubmodules,
71 UpdateSubmodules,
72 get_bdist_wheel,
72 get_bdist_wheel,
73 CompileCSS,
73 CompileCSS,
74 JavascriptVersion,
74 JavascriptVersion,
75 css_js_prerelease,
75 css_js_prerelease,
76 install_symlinked,
76 install_symlinked,
77 install_lib_symlink,
77 install_lib_symlink,
78 install_scripts_for_symlink,
78 install_scripts_for_symlink,
79 unsymlink,
79 unsymlink,
80 )
80 )
81 from setupext import setupext
81 from setupext import setupext
82
82
83 isfile = os.path.isfile
83 isfile = os.path.isfile
84 pjoin = os.path.join
84 pjoin = os.path.join
85
85
86 #-------------------------------------------------------------------------------
86 #-------------------------------------------------------------------------------
87 # Handle OS specific things
87 # Handle OS specific things
88 #-------------------------------------------------------------------------------
88 #-------------------------------------------------------------------------------
89
89
90 if os.name in ('nt','dos'):
90 if os.name in ('nt','dos'):
91 os_name = 'windows'
91 os_name = 'windows'
92 else:
92 else:
93 os_name = os.name
93 os_name = os.name
94
94
95 # Under Windows, 'sdist' has not been supported. Now that the docs build with
95 # Under Windows, 'sdist' has not been supported. Now that the docs build with
96 # Sphinx it might work, but let's not turn it on until someone confirms that it
96 # Sphinx it might work, but let's not turn it on until someone confirms that it
97 # actually works.
97 # actually works.
98 if os_name == 'windows' and 'sdist' in sys.argv:
98 if os_name == 'windows' and 'sdist' in sys.argv:
99 print('The sdist command is not available under Windows. Exiting.')
99 print('The sdist command is not available under Windows. Exiting.')
100 sys.exit(1)
100 sys.exit(1)
101
101
102 #-------------------------------------------------------------------------------
102 #-------------------------------------------------------------------------------
103 # Make sure we aren't trying to run without submodules
103 # Make sure we aren't trying to run without submodules
104 #-------------------------------------------------------------------------------
104 #-------------------------------------------------------------------------------
105 here = os.path.abspath(os.path.dirname(__file__))
105 here = os.path.abspath(os.path.dirname(__file__))
106
106
107 def require_clean_submodules():
107 def require_clean_submodules():
108 """Check on git submodules before distutils can do anything
108 """Check on git submodules before distutils can do anything
109
109
110 Since distutils cannot be trusted to update the tree
110 Since distutils cannot be trusted to update the tree
111 after everything has been set in motion,
111 after everything has been set in motion,
112 this is not a distutils command.
112 this is not a distutils command.
113 """
113 """
114 # PACKAGERS: Add a return here to skip checks for git submodules
114 # PACKAGERS: Add a return here to skip checks for git submodules
115
115
116 # don't do anything if nothing is actually supposed to happen
116 # don't do anything if nothing is actually supposed to happen
117 for do_nothing in ('-h', '--help', '--help-commands', 'clean', 'submodule'):
117 for do_nothing in ('-h', '--help', '--help-commands', 'clean', 'submodule'):
118 if do_nothing in sys.argv:
118 if do_nothing in sys.argv:
119 return
119 return
120
120
121 status = check_submodule_status(here)
121 status = check_submodule_status(here)
122
122
123 if status == "missing":
123 if status == "missing":
124 print("checking out submodules for the first time")
124 print("checking out submodules for the first time")
125 update_submodules(here)
125 update_submodules(here)
126 elif status == "unclean":
126 elif status == "unclean":
127 print('\n'.join([
127 print('\n'.join([
128 "Cannot build / install IPython with unclean submodules",
128 "Cannot build / install IPython with unclean submodules",
129 "Please update submodules with",
129 "Please update submodules with",
130 " python setup.py submodule",
130 " python setup.py submodule",
131 "or",
131 "or",
132 " git submodule update",
132 " git submodule update",
133 "or commit any submodule changes you have made."
133 "or commit any submodule changes you have made."
134 ]))
134 ]))
135 sys.exit(1)
135 sys.exit(1)
136
136
137 require_clean_submodules()
137 require_clean_submodules()
138
138
139 #-------------------------------------------------------------------------------
139 #-------------------------------------------------------------------------------
140 # Things related to the IPython documentation
140 # Things related to the IPython documentation
141 #-------------------------------------------------------------------------------
141 #-------------------------------------------------------------------------------
142
142
143 # update the manuals when building a source dist
143 # update the manuals when building a source dist
144 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
144 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
145
145
146 # List of things to be updated. Each entry is a triplet of args for
146 # List of things to be updated. Each entry is a triplet of args for
147 # target_update()
147 # target_update()
148 to_update = [
148 to_update = [
149 # FIXME - Disabled for now: we need to redo an automatic way
149 # FIXME - Disabled for now: we need to redo an automatic way
150 # of generating the magic info inside the rst.
150 # of generating the magic info inside the rst.
151 #('docs/magic.tex',
151 #('docs/magic.tex',
152 #['IPython/Magic.py'],
152 #['IPython/Magic.py'],
153 #"cd doc && ./update_magic.sh" ),
153 #"cd doc && ./update_magic.sh" ),
154
154
155 ('docs/man/ipcluster.1.gz',
155 ('docs/man/ipcluster.1.gz',
156 ['docs/man/ipcluster.1'],
156 ['docs/man/ipcluster.1'],
157 'cd docs/man && gzip -9c ipcluster.1 > ipcluster.1.gz'),
157 'cd docs/man && gzip -9c ipcluster.1 > ipcluster.1.gz'),
158
158
159 ('docs/man/ipcontroller.1.gz',
159 ('docs/man/ipcontroller.1.gz',
160 ['docs/man/ipcontroller.1'],
160 ['docs/man/ipcontroller.1'],
161 'cd docs/man && gzip -9c ipcontroller.1 > ipcontroller.1.gz'),
161 'cd docs/man && gzip -9c ipcontroller.1 > ipcontroller.1.gz'),
162
162
163 ('docs/man/ipengine.1.gz',
163 ('docs/man/ipengine.1.gz',
164 ['docs/man/ipengine.1'],
164 ['docs/man/ipengine.1'],
165 'cd docs/man && gzip -9c ipengine.1 > ipengine.1.gz'),
165 'cd docs/man && gzip -9c ipengine.1 > ipengine.1.gz'),
166
166
167 ('docs/man/ipython.1.gz',
167 ('docs/man/ipython.1.gz',
168 ['docs/man/ipython.1'],
168 ['docs/man/ipython.1'],
169 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
169 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
170
170
171 ]
171 ]
172
172
173
173
174 [ target_update(*t) for t in to_update ]
174 [ target_update(*t) for t in to_update ]
175
175
176 #---------------------------------------------------------------------------
176 #---------------------------------------------------------------------------
177 # Find all the packages, package data, and data_files
177 # Find all the packages, package data, and data_files
178 #---------------------------------------------------------------------------
178 #---------------------------------------------------------------------------
179
179
180 packages = find_packages()
180 packages = find_packages()
181 package_data = find_package_data()
181 package_data = find_package_data()
182
182
183 data_files = find_data_files()
183 data_files = find_data_files()
184
184
185 setup_args['packages'] = packages
185 setup_args['packages'] = packages
186 setup_args['package_data'] = package_data
186 setup_args['package_data'] = package_data
187 setup_args['data_files'] = data_files
187 setup_args['data_files'] = data_files
188
188
189 #---------------------------------------------------------------------------
189 #---------------------------------------------------------------------------
190 # custom distutils commands
190 # custom distutils commands
191 #---------------------------------------------------------------------------
191 #---------------------------------------------------------------------------
192 # imports here, so they are after setuptools import if there was one
192 # imports here, so they are after setuptools import if there was one
193 from distutils.command.sdist import sdist
193 from distutils.command.sdist import sdist
194 from distutils.command.upload import upload
194 from distutils.command.upload import upload
195
195
196 class UploadWindowsInstallers(upload):
196 class UploadWindowsInstallers(upload):
197
197
198 description = "Upload Windows installers to PyPI (only used from tools/release_windows.py)"
198 description = "Upload Windows installers to PyPI (only used from tools/release_windows.py)"
199 user_options = upload.user_options + [
199 user_options = upload.user_options + [
200 ('files=', 'f', 'exe file (or glob) to upload')
200 ('files=', 'f', 'exe file (or glob) to upload')
201 ]
201 ]
202 def initialize_options(self):
202 def initialize_options(self):
203 upload.initialize_options(self)
203 upload.initialize_options(self)
204 meta = self.distribution.metadata
204 meta = self.distribution.metadata
205 base = '{name}-{version}'.format(
205 base = '{name}-{version}'.format(
206 name=meta.get_name(),
206 name=meta.get_name(),
207 version=meta.get_version()
207 version=meta.get_version()
208 )
208 )
209 self.files = os.path.join('dist', '%s.*.exe' % base)
209 self.files = os.path.join('dist', '%s.*.exe' % base)
210
210
211 def run(self):
211 def run(self):
212 for dist_file in glob(self.files):
212 for dist_file in glob(self.files):
213 self.upload_file('bdist_wininst', 'any', dist_file)
213 self.upload_file('bdist_wininst', 'any', dist_file)
214
214
215 setup_args['cmdclass'] = {
215 setup_args['cmdclass'] = {
216 'build_py': css_js_prerelease(
216 'build_py': css_js_prerelease(
217 check_package_data_first(git_prebuild('IPython'))),
217 check_package_data_first(git_prebuild('IPython'))),
218 'sdist' : css_js_prerelease(git_prebuild('IPython', sdist)),
218 'sdist' : css_js_prerelease(git_prebuild('IPython', sdist)),
219 'upload_wininst' : UploadWindowsInstallers,
219 'upload_wininst' : UploadWindowsInstallers,
220 'submodule' : UpdateSubmodules,
220 'submodule' : UpdateSubmodules,
221 'css' : CompileCSS,
221 'css' : CompileCSS,
222 'symlink': install_symlinked,
222 'symlink': install_symlinked,
223 'install_lib_symlink': install_lib_symlink,
223 'install_lib_symlink': install_lib_symlink,
224 'install_scripts_sym': install_scripts_for_symlink,
224 'install_scripts_sym': install_scripts_for_symlink,
225 'unsymlink': unsymlink,
225 'unsymlink': unsymlink,
226 'jsversion' : JavascriptVersion,
226 'jsversion' : JavascriptVersion,
227 }
227 }
228
228
229 #---------------------------------------------------------------------------
229 #---------------------------------------------------------------------------
230 # Handle scripts, dependencies, and setuptools specific things
230 # Handle scripts, dependencies, and setuptools specific things
231 #---------------------------------------------------------------------------
231 #---------------------------------------------------------------------------
232
232
233 # For some commands, use setuptools. Note that we do NOT list install here!
233 # For some commands, use setuptools. Note that we do NOT list install here!
234 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
234 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
235 needs_setuptools = set(('develop', 'release', 'bdist_egg', 'bdist_rpm',
235 needs_setuptools = set(('develop', 'release', 'bdist_egg', 'bdist_rpm',
236 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
236 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
237 'egg_info', 'easy_install', 'upload', 'install_egg_info',
237 'egg_info', 'easy_install', 'upload', 'install_egg_info',
238 ))
238 ))
239
239
240 if len(needs_setuptools.intersection(sys.argv)) > 0:
240 if len(needs_setuptools.intersection(sys.argv)) > 0:
241 import setuptools
241 import setuptools
242
242
243 # This dict is used for passing extra arguments that are setuptools
243 # This dict is used for passing extra arguments that are setuptools
244 # specific to setup
244 # specific to setup
245 setuptools_extra_args = {}
245 setuptools_extra_args = {}
246
246
247 # setuptools requirements
247 # setuptools requirements
248
248
249 pyzmq = 'pyzmq>=13'
249 pyzmq = 'pyzmq>=13'
250
250
251 extras_require = dict(
251 extras_require = dict(
252 parallel = [pyzmq],
252 parallel = [pyzmq],
253 qtconsole = [pyzmq, 'pygments'],
253 qtconsole = [pyzmq, 'pygments'],
254 doc = ['Sphinx>=1.1', 'numpydoc'],
254 doc = ['Sphinx>=1.1', 'numpydoc'],
255 test = ['nose>=0.10.1', 'requests'],
255 test = ['nose>=0.10.1', 'requests'],
256 terminal = [],
256 terminal = [],
257 nbformat = ['jsonschema>=2.0'],
257 nbformat = ['jsonschema>=2.0'],
258 notebook = ['tornado>=4.0', pyzmq, 'jinja2', 'pygments', 'mistune>=0.5'],
258 notebook = ['tornado>=4.0', pyzmq, 'jinja2', 'pygments', 'mistune>=0.5'],
259 nbconvert = ['pygments', 'jinja2', 'mistune>=0.3.1']
259 nbconvert = ['pygments', 'jinja2', 'mistune>=0.3.1']
260 )
260 )
261
261
262 if not sys.platform.startswith('win'):
262 if not sys.platform.startswith('win'):
263 extras_require['notebook'].append('terminado>=0.3.3')
263 extras_require['notebook'].append('terminado>=0.3.3')
264
264
265 if sys.version_info < (3, 3):
265 if sys.version_info < (3, 3):
266 extras_require['test'].append('mock')
266 extras_require['test'].append('mock')
267
267
268 extras_require['notebook'].extend(extras_require['nbformat'])
268 extras_require['notebook'].extend(extras_require['nbformat'])
269 extras_require['nbconvert'].extend(extras_require['nbformat'])
269 extras_require['nbconvert'].extend(extras_require['nbformat'])
270
270
271 install_requires = [
271 install_requires = [
272 'decorator',
272 'decorator',
273 'path.py', # required by pickleshare, remove when pickleshare is added here
273 'path.py', # required by pickleshare, remove when pickleshare is added here
274 ]
274 ]
275
275
276 # add readline
276 # add readline
277 if sys.platform == 'darwin':
277 if sys.platform == 'darwin':
278 install_requires.append('appnope')
278 if 'bdist_wheel' in sys.argv[1:] or not setupext.check_for_readline():
279 if 'bdist_wheel' in sys.argv[1:] or not setupext.check_for_readline():
279 install_requires.append('gnureadline')
280 install_requires.append('gnureadline')
280 elif sys.platform.startswith('win'):
281 elif sys.platform.startswith('win'):
281 extras_require['terminal'].append('pyreadline>=2.0')
282 extras_require['terminal'].append('pyreadline>=2.0')
282
283
283 everything = set()
284 everything = set()
284 for deps in extras_require.values():
285 for deps in extras_require.values():
285 everything.update(deps)
286 everything.update(deps)
286 extras_require['all'] = everything
287 extras_require['all'] = everything
287
288
288 if 'setuptools' in sys.modules:
289 if 'setuptools' in sys.modules:
289 # setup.py develop should check for submodules
290 # setup.py develop should check for submodules
290 from setuptools.command.develop import develop
291 from setuptools.command.develop import develop
291 setup_args['cmdclass']['develop'] = require_submodules(develop)
292 setup_args['cmdclass']['develop'] = require_submodules(develop)
292 setup_args['cmdclass']['bdist_wheel'] = css_js_prerelease(get_bdist_wheel())
293 setup_args['cmdclass']['bdist_wheel'] = css_js_prerelease(get_bdist_wheel())
293
294
294 setuptools_extra_args['zip_safe'] = False
295 setuptools_extra_args['zip_safe'] = False
295 setuptools_extra_args['entry_points'] = {
296 setuptools_extra_args['entry_points'] = {
296 'console_scripts': find_entry_points(),
297 'console_scripts': find_entry_points(),
297 'pygments.lexers': [
298 'pygments.lexers': [
298 'ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer',
299 'ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer',
299 'ipython = IPython.lib.lexers:IPythonLexer',
300 'ipython = IPython.lib.lexers:IPythonLexer',
300 'ipython3 = IPython.lib.lexers:IPython3Lexer',
301 'ipython3 = IPython.lib.lexers:IPython3Lexer',
301 ],
302 ],
302 }
303 }
303 setup_args['extras_require'] = extras_require
304 setup_args['extras_require'] = extras_require
304 requires = setup_args['install_requires'] = install_requires
305 requires = setup_args['install_requires'] = install_requires
305
306
306 # Script to be run by the windows binary installer after the default setup
307 # Script to be run by the windows binary installer after the default setup
307 # routine, to add shortcuts and similar windows-only things. Windows
308 # routine, to add shortcuts and similar windows-only things. Windows
308 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
309 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
309 # doesn't find them.
310 # doesn't find them.
310 if 'bdist_wininst' in sys.argv:
311 if 'bdist_wininst' in sys.argv:
311 if len(sys.argv) > 2 and \
312 if len(sys.argv) > 2 and \
312 ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
313 ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
313 print("ERROR: bdist_wininst must be run alone. Exiting.", file=sys.stderr)
314 print("ERROR: bdist_wininst must be run alone. Exiting.", file=sys.stderr)
314 sys.exit(1)
315 sys.exit(1)
315 setup_args['data_files'].append(
316 setup_args['data_files'].append(
316 ['Scripts', ('scripts/ipython.ico', 'scripts/ipython_nb.ico')])
317 ['Scripts', ('scripts/ipython.ico', 'scripts/ipython_nb.ico')])
317 setup_args['scripts'] = [pjoin('scripts','ipython_win_post_install.py')]
318 setup_args['scripts'] = [pjoin('scripts','ipython_win_post_install.py')]
318 setup_args['options'] = {"bdist_wininst":
319 setup_args['options'] = {"bdist_wininst":
319 {"install_script":
320 {"install_script":
320 "ipython_win_post_install.py"}}
321 "ipython_win_post_install.py"}}
321
322
322 else:
323 else:
323 # If we are installing without setuptools, call this function which will
324 # If we are installing without setuptools, call this function which will
324 # check for dependencies an inform the user what is needed. This is
325 # check for dependencies an inform the user what is needed. This is
325 # just to make life easy for users.
326 # just to make life easy for users.
326 for install_cmd in ('install', 'symlink'):
327 for install_cmd in ('install', 'symlink'):
327 if install_cmd in sys.argv:
328 if install_cmd in sys.argv:
328 check_for_dependencies()
329 check_for_dependencies()
329 break
330 break
330 # scripts has to be a non-empty list, or install_scripts isn't called
331 # scripts has to be a non-empty list, or install_scripts isn't called
331 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
332 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
332
333
333 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
334 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
334
335
335 #---------------------------------------------------------------------------
336 #---------------------------------------------------------------------------
336 # Do the actual setup now
337 # Do the actual setup now
337 #---------------------------------------------------------------------------
338 #---------------------------------------------------------------------------
338
339
339 setup_args.update(setuptools_extra_args)
340 setup_args.update(setuptools_extra_args)
340
341
341 def main():
342 def main():
342 setup(**setup_args)
343 setup(**setup_args)
343
344
344 if __name__ == '__main__':
345 if __name__ == '__main__':
345 main()
346 main()
@@ -1,768 +1,769 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 This module defines the things that are used in setup.py for building IPython
3 This module defines the things that are used in setup.py for building IPython
4
4
5 This includes:
5 This includes:
6
6
7 * The basic arguments to setup
7 * The basic arguments to setup
8 * Functions for finding things like packages, package data, etc.
8 * Functions for finding things like packages, package data, etc.
9 * A function for checking dependencies.
9 * A function for checking dependencies.
10 """
10 """
11
11
12 # Copyright (c) IPython Development Team.
12 # Copyright (c) IPython Development Team.
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14
14
15 from __future__ import print_function
15 from __future__ import print_function
16
16
17 import errno
17 import errno
18 import os
18 import os
19 import sys
19 import sys
20
20
21 from distutils import log
21 from distutils import log
22 from distutils.command.build_py import build_py
22 from distutils.command.build_py import build_py
23 from distutils.command.build_scripts import build_scripts
23 from distutils.command.build_scripts import build_scripts
24 from distutils.command.install import install
24 from distutils.command.install import install
25 from distutils.command.install_scripts import install_scripts
25 from distutils.command.install_scripts import install_scripts
26 from distutils.cmd import Command
26 from distutils.cmd import Command
27 from distutils.errors import DistutilsExecError
27 from distutils.errors import DistutilsExecError
28 from fnmatch import fnmatch
28 from fnmatch import fnmatch
29 from glob import glob
29 from glob import glob
30 from subprocess import Popen, PIPE
30 from subprocess import Popen, PIPE
31
31
32 from setupext import install_data_ext
32 from setupext import install_data_ext
33
33
34 #-------------------------------------------------------------------------------
34 #-------------------------------------------------------------------------------
35 # Useful globals and utility functions
35 # Useful globals and utility functions
36 #-------------------------------------------------------------------------------
36 #-------------------------------------------------------------------------------
37
37
38 # A few handy globals
38 # A few handy globals
39 isfile = os.path.isfile
39 isfile = os.path.isfile
40 pjoin = os.path.join
40 pjoin = os.path.join
41 repo_root = os.path.dirname(os.path.abspath(__file__))
41 repo_root = os.path.dirname(os.path.abspath(__file__))
42
42
43 def oscmd(s):
43 def oscmd(s):
44 print(">", s)
44 print(">", s)
45 os.system(s)
45 os.system(s)
46
46
47 # Py3 compatibility hacks, without assuming IPython itself is installed with
47 # Py3 compatibility hacks, without assuming IPython itself is installed with
48 # the full py3compat machinery.
48 # the full py3compat machinery.
49
49
50 try:
50 try:
51 execfile
51 execfile
52 except NameError:
52 except NameError:
53 def execfile(fname, globs, locs=None):
53 def execfile(fname, globs, locs=None):
54 locs = locs or globs
54 locs = locs or globs
55 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
55 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
56
56
57 # A little utility we'll need below, since glob() does NOT allow you to do
57 # A little utility we'll need below, since glob() does NOT allow you to do
58 # exclusion on multiple endings!
58 # exclusion on multiple endings!
59 def file_doesnt_endwith(test,endings):
59 def file_doesnt_endwith(test,endings):
60 """Return true if test is a file and its name does NOT end with any
60 """Return true if test is a file and its name does NOT end with any
61 of the strings listed in endings."""
61 of the strings listed in endings."""
62 if not isfile(test):
62 if not isfile(test):
63 return False
63 return False
64 for e in endings:
64 for e in endings:
65 if test.endswith(e):
65 if test.endswith(e):
66 return False
66 return False
67 return True
67 return True
68
68
69 #---------------------------------------------------------------------------
69 #---------------------------------------------------------------------------
70 # Basic project information
70 # Basic project information
71 #---------------------------------------------------------------------------
71 #---------------------------------------------------------------------------
72
72
73 # release.py contains version, authors, license, url, keywords, etc.
73 # release.py contains version, authors, license, url, keywords, etc.
74 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
74 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
75
75
76 # Create a dict with the basic information
76 # Create a dict with the basic information
77 # This dict is eventually passed to setup after additional keys are added.
77 # This dict is eventually passed to setup after additional keys are added.
78 setup_args = dict(
78 setup_args = dict(
79 name = name,
79 name = name,
80 version = version,
80 version = version,
81 description = description,
81 description = description,
82 long_description = long_description,
82 long_description = long_description,
83 author = author,
83 author = author,
84 author_email = author_email,
84 author_email = author_email,
85 url = url,
85 url = url,
86 download_url = download_url,
86 download_url = download_url,
87 license = license,
87 license = license,
88 platforms = platforms,
88 platforms = platforms,
89 keywords = keywords,
89 keywords = keywords,
90 classifiers = classifiers,
90 classifiers = classifiers,
91 cmdclass = {'install_data': install_data_ext},
91 cmdclass = {'install_data': install_data_ext},
92 )
92 )
93
93
94
94
95 #---------------------------------------------------------------------------
95 #---------------------------------------------------------------------------
96 # Find packages
96 # Find packages
97 #---------------------------------------------------------------------------
97 #---------------------------------------------------------------------------
98
98
99 def find_packages():
99 def find_packages():
100 """
100 """
101 Find all of IPython's packages.
101 Find all of IPython's packages.
102 """
102 """
103 excludes = ['deathrow', 'quarantine']
103 excludes = ['deathrow', 'quarantine']
104 packages = []
104 packages = []
105 for dir,subdirs,files in os.walk('IPython'):
105 for dir,subdirs,files in os.walk('IPython'):
106 package = dir.replace(os.path.sep, '.')
106 package = dir.replace(os.path.sep, '.')
107 if any(package.startswith('IPython.'+exc) for exc in excludes):
107 if any(package.startswith('IPython.'+exc) for exc in excludes):
108 # package is to be excluded (e.g. deathrow)
108 # package is to be excluded (e.g. deathrow)
109 continue
109 continue
110 if '__init__.py' not in files:
110 if '__init__.py' not in files:
111 # not a package
111 # not a package
112 continue
112 continue
113 packages.append(package)
113 packages.append(package)
114 return packages
114 return packages
115
115
116 #---------------------------------------------------------------------------
116 #---------------------------------------------------------------------------
117 # Find package data
117 # Find package data
118 #---------------------------------------------------------------------------
118 #---------------------------------------------------------------------------
119
119
120 def find_package_data():
120 def find_package_data():
121 """
121 """
122 Find IPython's package_data.
122 Find IPython's package_data.
123 """
123 """
124 # This is not enough for these things to appear in an sdist.
124 # This is not enough for these things to appear in an sdist.
125 # We need to muck with the MANIFEST to get this to work
125 # We need to muck with the MANIFEST to get this to work
126
126
127 # exclude components and less from the walk;
127 # exclude components and less from the walk;
128 # we will build the components separately
128 # we will build the components separately
129 excludes = [
129 excludes = [
130 pjoin('static', 'components'),
130 pjoin('static', 'components'),
131 pjoin('static', '*', 'less'),
131 pjoin('static', '*', 'less'),
132 ]
132 ]
133
133
134 # walk notebook resources:
134 # walk notebook resources:
135 cwd = os.getcwd()
135 cwd = os.getcwd()
136 os.chdir(os.path.join('IPython', 'html'))
136 os.chdir(os.path.join('IPython', 'html'))
137 static_data = []
137 static_data = []
138 for parent, dirs, files in os.walk('static'):
138 for parent, dirs, files in os.walk('static'):
139 if any(fnmatch(parent, pat) for pat in excludes):
139 if any(fnmatch(parent, pat) for pat in excludes):
140 # prevent descending into subdirs
140 # prevent descending into subdirs
141 dirs[:] = []
141 dirs[:] = []
142 continue
142 continue
143 for f in files:
143 for f in files:
144 static_data.append(pjoin(parent, f))
144 static_data.append(pjoin(parent, f))
145
145
146 components = pjoin("static", "components")
146 components = pjoin("static", "components")
147 # select the components we actually need to install
147 # select the components we actually need to install
148 # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
148 # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
149 static_data.extend([
149 static_data.extend([
150 pjoin(components, "backbone", "backbone-min.js"),
150 pjoin(components, "backbone", "backbone-min.js"),
151 pjoin(components, "bootstrap", "js", "bootstrap.min.js"),
151 pjoin(components, "bootstrap", "js", "bootstrap.min.js"),
152 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
152 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
153 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
153 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
154 pjoin(components, "es6-promise", "*.js"),
154 pjoin(components, "es6-promise", "*.js"),
155 pjoin(components, "font-awesome", "fonts", "*.*"),
155 pjoin(components, "font-awesome", "fonts", "*.*"),
156 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
156 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
157 pjoin(components, "jquery", "jquery.min.js"),
157 pjoin(components, "jquery", "jquery.min.js"),
158 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
158 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
159 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
159 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
160 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
160 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
161 pjoin(components, "marked", "lib", "marked.js"),
161 pjoin(components, "marked", "lib", "marked.js"),
162 pjoin(components, "requirejs", "require.js"),
162 pjoin(components, "requirejs", "require.js"),
163 pjoin(components, "underscore", "underscore-min.js"),
163 pjoin(components, "underscore", "underscore-min.js"),
164 pjoin(components, "moment", "moment.js"),
164 pjoin(components, "moment", "moment.js"),
165 pjoin(components, "moment", "min", "moment.min.js"),
165 pjoin(components, "moment", "min", "moment.min.js"),
166 pjoin(components, "term.js", "src", "term.js"),
166 pjoin(components, "term.js", "src", "term.js"),
167 pjoin(components, "text-encoding", "lib", "encoding.js"),
167 pjoin(components, "text-encoding", "lib", "encoding.js"),
168 ])
168 ])
169
169
170 # Ship all of Codemirror's CSS and JS
170 # Ship all of Codemirror's CSS and JS
171 for parent, dirs, files in os.walk(pjoin(components, 'codemirror')):
171 for parent, dirs, files in os.walk(pjoin(components, 'codemirror')):
172 for f in files:
172 for f in files:
173 if f.endswith(('.js', '.css')):
173 if f.endswith(('.js', '.css')):
174 static_data.append(pjoin(parent, f))
174 static_data.append(pjoin(parent, f))
175
175
176 os.chdir(os.path.join('tests',))
176 os.chdir(os.path.join('tests',))
177 js_tests = glob('*.js') + glob('*/*.js')
177 js_tests = glob('*.js') + glob('*/*.js')
178
178
179 os.chdir(os.path.join(cwd, 'IPython', 'nbconvert'))
179 os.chdir(os.path.join(cwd, 'IPython', 'nbconvert'))
180 nbconvert_templates = [os.path.join(dirpath, '*.*')
180 nbconvert_templates = [os.path.join(dirpath, '*.*')
181 for dirpath, _, _ in os.walk('templates')]
181 for dirpath, _, _ in os.walk('templates')]
182
182
183 os.chdir(cwd)
183 os.chdir(cwd)
184
184
185 package_data = {
185 package_data = {
186 'IPython.config.profile' : ['README*', '*/*.py'],
186 'IPython.config.profile' : ['README*', '*/*.py'],
187 'IPython.core.tests' : ['*.png', '*.jpg'],
187 'IPython.core.tests' : ['*.png', '*.jpg'],
188 'IPython.lib.tests' : ['*.wav'],
188 'IPython.lib.tests' : ['*.wav'],
189 'IPython.testing.plugin' : ['*.txt'],
189 'IPython.testing.plugin' : ['*.txt'],
190 'IPython.html' : ['templates/*'] + static_data,
190 'IPython.html' : ['templates/*'] + static_data,
191 'IPython.html.tests' : js_tests,
191 'IPython.html.tests' : js_tests,
192 'IPython.qt.console' : ['resources/icon/*.svg'],
192 'IPython.qt.console' : ['resources/icon/*.svg'],
193 'IPython.nbconvert' : nbconvert_templates +
193 'IPython.nbconvert' : nbconvert_templates +
194 [
194 [
195 'tests/files/*.*',
195 'tests/files/*.*',
196 'exporters/tests/files/*.*',
196 'exporters/tests/files/*.*',
197 'preprocessors/tests/files/*.*',
197 'preprocessors/tests/files/*.*',
198 ],
198 ],
199 'IPython.nbconvert.filters' : ['marked.js'],
199 'IPython.nbconvert.filters' : ['marked.js'],
200 'IPython.nbformat' : [
200 'IPython.nbformat' : [
201 'tests/*.ipynb',
201 'tests/*.ipynb',
202 'v3/nbformat.v3.schema.json',
202 'v3/nbformat.v3.schema.json',
203 'v4/nbformat.v4.schema.json',
203 'v4/nbformat.v4.schema.json',
204 ],
204 ],
205 'IPython.kernel': ['resources/*.*'],
205 'IPython.kernel': ['resources/*.*'],
206 }
206 }
207
207
208 return package_data
208 return package_data
209
209
210
210
211 def check_package_data(package_data):
211 def check_package_data(package_data):
212 """verify that package_data globs make sense"""
212 """verify that package_data globs make sense"""
213 print("checking package data")
213 print("checking package data")
214 for pkg, data in package_data.items():
214 for pkg, data in package_data.items():
215 pkg_root = pjoin(*pkg.split('.'))
215 pkg_root = pjoin(*pkg.split('.'))
216 for d in data:
216 for d in data:
217 path = pjoin(pkg_root, d)
217 path = pjoin(pkg_root, d)
218 if '*' in path:
218 if '*' in path:
219 assert len(glob(path)) > 0, "No files match pattern %s" % path
219 assert len(glob(path)) > 0, "No files match pattern %s" % path
220 else:
220 else:
221 assert os.path.exists(path), "Missing package data: %s" % path
221 assert os.path.exists(path), "Missing package data: %s" % path
222
222
223
223
224 def check_package_data_first(command):
224 def check_package_data_first(command):
225 """decorator for checking package_data before running a given command
225 """decorator for checking package_data before running a given command
226
226
227 Probably only needs to wrap build_py
227 Probably only needs to wrap build_py
228 """
228 """
229 class DecoratedCommand(command):
229 class DecoratedCommand(command):
230 def run(self):
230 def run(self):
231 check_package_data(self.package_data)
231 check_package_data(self.package_data)
232 command.run(self)
232 command.run(self)
233 return DecoratedCommand
233 return DecoratedCommand
234
234
235
235
236 #---------------------------------------------------------------------------
236 #---------------------------------------------------------------------------
237 # Find data files
237 # Find data files
238 #---------------------------------------------------------------------------
238 #---------------------------------------------------------------------------
239
239
240 def make_dir_struct(tag,base,out_base):
240 def make_dir_struct(tag,base,out_base):
241 """Make the directory structure of all files below a starting dir.
241 """Make the directory structure of all files below a starting dir.
242
242
243 This is just a convenience routine to help build a nested directory
243 This is just a convenience routine to help build a nested directory
244 hierarchy because distutils is too stupid to do this by itself.
244 hierarchy because distutils is too stupid to do this by itself.
245
245
246 XXX - this needs a proper docstring!
246 XXX - this needs a proper docstring!
247 """
247 """
248
248
249 # we'll use these a lot below
249 # we'll use these a lot below
250 lbase = len(base)
250 lbase = len(base)
251 pathsep = os.path.sep
251 pathsep = os.path.sep
252 lpathsep = len(pathsep)
252 lpathsep = len(pathsep)
253
253
254 out = []
254 out = []
255 for (dirpath,dirnames,filenames) in os.walk(base):
255 for (dirpath,dirnames,filenames) in os.walk(base):
256 # we need to strip out the dirpath from the base to map it to the
256 # we need to strip out the dirpath from the base to map it to the
257 # output (installation) path. This requires possibly stripping the
257 # output (installation) path. This requires possibly stripping the
258 # path separator, because otherwise pjoin will not work correctly
258 # path separator, because otherwise pjoin will not work correctly
259 # (pjoin('foo/','/bar') returns '/bar').
259 # (pjoin('foo/','/bar') returns '/bar').
260
260
261 dp_eff = dirpath[lbase:]
261 dp_eff = dirpath[lbase:]
262 if dp_eff.startswith(pathsep):
262 if dp_eff.startswith(pathsep):
263 dp_eff = dp_eff[lpathsep:]
263 dp_eff = dp_eff[lpathsep:]
264 # The output path must be anchored at the out_base marker
264 # The output path must be anchored at the out_base marker
265 out_path = pjoin(out_base,dp_eff)
265 out_path = pjoin(out_base,dp_eff)
266 # Now we can generate the final filenames. Since os.walk only produces
266 # Now we can generate the final filenames. Since os.walk only produces
267 # filenames, we must join back with the dirpath to get full valid file
267 # filenames, we must join back with the dirpath to get full valid file
268 # paths:
268 # paths:
269 pfiles = [pjoin(dirpath,f) for f in filenames]
269 pfiles = [pjoin(dirpath,f) for f in filenames]
270 # Finally, generate the entry we need, which is a pari of (output
270 # Finally, generate the entry we need, which is a pari of (output
271 # path, files) for use as a data_files parameter in install_data.
271 # path, files) for use as a data_files parameter in install_data.
272 out.append((out_path, pfiles))
272 out.append((out_path, pfiles))
273
273
274 return out
274 return out
275
275
276
276
277 def find_data_files():
277 def find_data_files():
278 """
278 """
279 Find IPython's data_files.
279 Find IPython's data_files.
280
280
281 Just man pages at this point.
281 Just man pages at this point.
282 """
282 """
283
283
284 manpagebase = pjoin('share', 'man', 'man1')
284 manpagebase = pjoin('share', 'man', 'man1')
285
285
286 # Simple file lists can be made by hand
286 # Simple file lists can be made by hand
287 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
287 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
288 if not manpages:
288 if not manpages:
289 # When running from a source tree, the manpages aren't gzipped
289 # When running from a source tree, the manpages aren't gzipped
290 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
290 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
291
291
292 # And assemble the entire output list
292 # And assemble the entire output list
293 data_files = [ (manpagebase, manpages) ]
293 data_files = [ (manpagebase, manpages) ]
294
294
295 return data_files
295 return data_files
296
296
297
297
298 def make_man_update_target(manpage):
298 def make_man_update_target(manpage):
299 """Return a target_update-compliant tuple for the given manpage.
299 """Return a target_update-compliant tuple for the given manpage.
300
300
301 Parameters
301 Parameters
302 ----------
302 ----------
303 manpage : string
303 manpage : string
304 Name of the manpage, must include the section number (trailing number).
304 Name of the manpage, must include the section number (trailing number).
305
305
306 Example
306 Example
307 -------
307 -------
308
308
309 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
309 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
310 ('docs/man/ipython.1.gz',
310 ('docs/man/ipython.1.gz',
311 ['docs/man/ipython.1'],
311 ['docs/man/ipython.1'],
312 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
312 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
313 """
313 """
314 man_dir = pjoin('docs', 'man')
314 man_dir = pjoin('docs', 'man')
315 manpage_gz = manpage + '.gz'
315 manpage_gz = manpage + '.gz'
316 manpath = pjoin(man_dir, manpage)
316 manpath = pjoin(man_dir, manpage)
317 manpath_gz = pjoin(man_dir, manpage_gz)
317 manpath_gz = pjoin(man_dir, manpage_gz)
318 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
318 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
319 locals() )
319 locals() )
320 return (manpath_gz, [manpath], gz_cmd)
320 return (manpath_gz, [manpath], gz_cmd)
321
321
322 # The two functions below are copied from IPython.utils.path, so we don't need
322 # The two functions below are copied from IPython.utils.path, so we don't need
323 # to import IPython during setup, which fails on Python 3.
323 # to import IPython during setup, which fails on Python 3.
324
324
325 def target_outdated(target,deps):
325 def target_outdated(target,deps):
326 """Determine whether a target is out of date.
326 """Determine whether a target is out of date.
327
327
328 target_outdated(target,deps) -> 1/0
328 target_outdated(target,deps) -> 1/0
329
329
330 deps: list of filenames which MUST exist.
330 deps: list of filenames which MUST exist.
331 target: single filename which may or may not exist.
331 target: single filename which may or may not exist.
332
332
333 If target doesn't exist or is older than any file listed in deps, return
333 If target doesn't exist or is older than any file listed in deps, return
334 true, otherwise return false.
334 true, otherwise return false.
335 """
335 """
336 try:
336 try:
337 target_time = os.path.getmtime(target)
337 target_time = os.path.getmtime(target)
338 except os.error:
338 except os.error:
339 return 1
339 return 1
340 for dep in deps:
340 for dep in deps:
341 dep_time = os.path.getmtime(dep)
341 dep_time = os.path.getmtime(dep)
342 if dep_time > target_time:
342 if dep_time > target_time:
343 #print "For target",target,"Dep failed:",dep # dbg
343 #print "For target",target,"Dep failed:",dep # dbg
344 #print "times (dep,tar):",dep_time,target_time # dbg
344 #print "times (dep,tar):",dep_time,target_time # dbg
345 return 1
345 return 1
346 return 0
346 return 0
347
347
348
348
349 def target_update(target,deps,cmd):
349 def target_update(target,deps,cmd):
350 """Update a target with a given command given a list of dependencies.
350 """Update a target with a given command given a list of dependencies.
351
351
352 target_update(target,deps,cmd) -> runs cmd if target is outdated.
352 target_update(target,deps,cmd) -> runs cmd if target is outdated.
353
353
354 This is just a wrapper around target_outdated() which calls the given
354 This is just a wrapper around target_outdated() which calls the given
355 command if target is outdated."""
355 command if target is outdated."""
356
356
357 if target_outdated(target,deps):
357 if target_outdated(target,deps):
358 os.system(cmd)
358 os.system(cmd)
359
359
360 #---------------------------------------------------------------------------
360 #---------------------------------------------------------------------------
361 # Find scripts
361 # Find scripts
362 #---------------------------------------------------------------------------
362 #---------------------------------------------------------------------------
363
363
364 def find_entry_points():
364 def find_entry_points():
365 """Defines the command line entry points for IPython
365 """Defines the command line entry points for IPython
366
366
367 This always uses setuptools-style entry points. When setuptools is not in
367 This always uses setuptools-style entry points. When setuptools is not in
368 use, our own build_scripts_entrypt class below parses these and builds
368 use, our own build_scripts_entrypt class below parses these and builds
369 command line scripts.
369 command line scripts.
370
370
371 Each of our entry points gets both a plain name, e.g. ipython, and one
371 Each of our entry points gets both a plain name, e.g. ipython, and one
372 suffixed with the Python major version number, e.g. ipython3.
372 suffixed with the Python major version number, e.g. ipython3.
373 """
373 """
374 ep = [
374 ep = [
375 'ipython%s = IPython:start_ipython',
375 'ipython%s = IPython:start_ipython',
376 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
376 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
377 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
377 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
378 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
378 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
379 'iptest%s = IPython.testing.iptestcontroller:main',
379 'iptest%s = IPython.testing.iptestcontroller:main',
380 ]
380 ]
381 suffix = str(sys.version_info[0])
381 suffix = str(sys.version_info[0])
382 return [e % '' for e in ep] + [e % suffix for e in ep]
382 return [e % '' for e in ep] + [e % suffix for e in ep]
383
383
384 script_src = """#!{executable}
384 script_src = """#!{executable}
385 # This script was automatically generated by setup.py
385 # This script was automatically generated by setup.py
386 if __name__ == '__main__':
386 if __name__ == '__main__':
387 from {mod} import {func}
387 from {mod} import {func}
388 {func}()
388 {func}()
389 """
389 """
390
390
391 class build_scripts_entrypt(build_scripts):
391 class build_scripts_entrypt(build_scripts):
392 """Build the command line scripts
392 """Build the command line scripts
393
393
394 Parse setuptools style entry points and write simple scripts to run the
394 Parse setuptools style entry points and write simple scripts to run the
395 target functions.
395 target functions.
396
396
397 On Windows, this also creates .cmd wrappers for the scripts so that you can
397 On Windows, this also creates .cmd wrappers for the scripts so that you can
398 easily launch them from a command line.
398 easily launch them from a command line.
399 """
399 """
400 def run(self):
400 def run(self):
401 self.mkpath(self.build_dir)
401 self.mkpath(self.build_dir)
402 outfiles = []
402 outfiles = []
403 for script in find_entry_points():
403 for script in find_entry_points():
404 name, entrypt = script.split('=')
404 name, entrypt = script.split('=')
405 name = name.strip()
405 name = name.strip()
406 entrypt = entrypt.strip()
406 entrypt = entrypt.strip()
407 outfile = os.path.join(self.build_dir, name)
407 outfile = os.path.join(self.build_dir, name)
408 outfiles.append(outfile)
408 outfiles.append(outfile)
409 print('Writing script to', outfile)
409 print('Writing script to', outfile)
410
410
411 mod, func = entrypt.split(':')
411 mod, func = entrypt.split(':')
412 with open(outfile, 'w') as f:
412 with open(outfile, 'w') as f:
413 f.write(script_src.format(executable=sys.executable,
413 f.write(script_src.format(executable=sys.executable,
414 mod=mod, func=func))
414 mod=mod, func=func))
415
415
416 if sys.platform == 'win32':
416 if sys.platform == 'win32':
417 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
417 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
418 # command line
418 # command line
419 cmd_file = os.path.join(self.build_dir, name + '.cmd')
419 cmd_file = os.path.join(self.build_dir, name + '.cmd')
420 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
420 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
421 python=sys.executable, script=name)
421 python=sys.executable, script=name)
422 log.info("Writing %s wrapper script" % cmd_file)
422 log.info("Writing %s wrapper script" % cmd_file)
423 with open(cmd_file, 'w') as f:
423 with open(cmd_file, 'w') as f:
424 f.write(cmd)
424 f.write(cmd)
425
425
426 return outfiles, outfiles
426 return outfiles, outfiles
427
427
428 class install_lib_symlink(Command):
428 class install_lib_symlink(Command):
429 user_options = [
429 user_options = [
430 ('install-dir=', 'd', "directory to install to"),
430 ('install-dir=', 'd', "directory to install to"),
431 ]
431 ]
432
432
433 def initialize_options(self):
433 def initialize_options(self):
434 self.install_dir = None
434 self.install_dir = None
435
435
436 def finalize_options(self):
436 def finalize_options(self):
437 self.set_undefined_options('symlink',
437 self.set_undefined_options('symlink',
438 ('install_lib', 'install_dir'),
438 ('install_lib', 'install_dir'),
439 )
439 )
440
440
441 def run(self):
441 def run(self):
442 if sys.platform == 'win32':
442 if sys.platform == 'win32':
443 raise Exception("This doesn't work on Windows.")
443 raise Exception("This doesn't work on Windows.")
444 pkg = os.path.join(os.getcwd(), 'IPython')
444 pkg = os.path.join(os.getcwd(), 'IPython')
445 dest = os.path.join(self.install_dir, 'IPython')
445 dest = os.path.join(self.install_dir, 'IPython')
446 if os.path.islink(dest):
446 if os.path.islink(dest):
447 print('removing existing symlink at %s' % dest)
447 print('removing existing symlink at %s' % dest)
448 os.unlink(dest)
448 os.unlink(dest)
449 print('symlinking %s -> %s' % (pkg, dest))
449 print('symlinking %s -> %s' % (pkg, dest))
450 os.symlink(pkg, dest)
450 os.symlink(pkg, dest)
451
451
452 class unsymlink(install):
452 class unsymlink(install):
453 def run(self):
453 def run(self):
454 dest = os.path.join(self.install_lib, 'IPython')
454 dest = os.path.join(self.install_lib, 'IPython')
455 if os.path.islink(dest):
455 if os.path.islink(dest):
456 print('removing symlink at %s' % dest)
456 print('removing symlink at %s' % dest)
457 os.unlink(dest)
457 os.unlink(dest)
458 else:
458 else:
459 print('No symlink exists at %s' % dest)
459 print('No symlink exists at %s' % dest)
460
460
461 class install_symlinked(install):
461 class install_symlinked(install):
462 def run(self):
462 def run(self):
463 if sys.platform == 'win32':
463 if sys.platform == 'win32':
464 raise Exception("This doesn't work on Windows.")
464 raise Exception("This doesn't work on Windows.")
465
465
466 # Run all sub-commands (at least those that need to be run)
466 # Run all sub-commands (at least those that need to be run)
467 for cmd_name in self.get_sub_commands():
467 for cmd_name in self.get_sub_commands():
468 self.run_command(cmd_name)
468 self.run_command(cmd_name)
469
469
470 # 'sub_commands': a list of commands this command might have to run to
470 # 'sub_commands': a list of commands this command might have to run to
471 # get its work done. See cmd.py for more info.
471 # get its work done. See cmd.py for more info.
472 sub_commands = [('install_lib_symlink', lambda self:True),
472 sub_commands = [('install_lib_symlink', lambda self:True),
473 ('install_scripts_sym', lambda self:True),
473 ('install_scripts_sym', lambda self:True),
474 ]
474 ]
475
475
476 class install_scripts_for_symlink(install_scripts):
476 class install_scripts_for_symlink(install_scripts):
477 """Redefined to get options from 'symlink' instead of 'install'.
477 """Redefined to get options from 'symlink' instead of 'install'.
478
478
479 I love distutils almost as much as I love setuptools.
479 I love distutils almost as much as I love setuptools.
480 """
480 """
481 def finalize_options(self):
481 def finalize_options(self):
482 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
482 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
483 self.set_undefined_options('symlink',
483 self.set_undefined_options('symlink',
484 ('install_scripts', 'install_dir'),
484 ('install_scripts', 'install_dir'),
485 ('force', 'force'),
485 ('force', 'force'),
486 ('skip_build', 'skip_build'),
486 ('skip_build', 'skip_build'),
487 )
487 )
488
488
489 #---------------------------------------------------------------------------
489 #---------------------------------------------------------------------------
490 # Verify all dependencies
490 # Verify all dependencies
491 #---------------------------------------------------------------------------
491 #---------------------------------------------------------------------------
492
492
493 def check_for_dependencies():
493 def check_for_dependencies():
494 """Check for IPython's dependencies.
494 """Check for IPython's dependencies.
495
495
496 This function should NOT be called if running under setuptools!
496 This function should NOT be called if running under setuptools!
497 """
497 """
498 from setupext.setupext import (
498 from setupext.setupext import (
499 print_line, print_raw, print_status,
499 print_line, print_raw, print_status,
500 check_for_sphinx, check_for_pygments,
500 check_for_sphinx, check_for_pygments,
501 check_for_nose, check_for_pexpect,
501 check_for_nose, check_for_pexpect,
502 check_for_pyzmq, check_for_readline,
502 check_for_pyzmq, check_for_readline,
503 check_for_jinja2, check_for_tornado
503 check_for_jinja2, check_for_tornado
504 )
504 )
505 print_line()
505 print_line()
506 print_raw("BUILDING IPYTHON")
506 print_raw("BUILDING IPYTHON")
507 print_status('python', sys.version)
507 print_status('python', sys.version)
508 print_status('platform', sys.platform)
508 print_status('platform', sys.platform)
509 if sys.platform == 'win32':
509 if sys.platform == 'win32':
510 print_status('Windows version', sys.getwindowsversion())
510 print_status('Windows version', sys.getwindowsversion())
511
511
512 print_raw("")
512 print_raw("")
513 print_raw("OPTIONAL DEPENDENCIES")
513 print_raw("OPTIONAL DEPENDENCIES")
514
514
515 check_for_sphinx()
515 check_for_sphinx()
516 check_for_pygments()
516 check_for_pygments()
517 check_for_nose()
517 check_for_nose()
518 if os.name == 'posix':
518 if os.name == 'posix':
519 check_for_pexpect()
519 check_for_pexpect()
520 check_for_pyzmq()
520 check_for_pyzmq()
521 check_for_tornado()
521 check_for_tornado()
522 check_for_readline()
522 check_for_readline()
523 check_for_jinja2()
523 check_for_jinja2()
524
524
525 #---------------------------------------------------------------------------
525 #---------------------------------------------------------------------------
526 # VCS related
526 # VCS related
527 #---------------------------------------------------------------------------
527 #---------------------------------------------------------------------------
528
528
529 # utils.submodule has checks for submodule status
529 # utils.submodule has checks for submodule status
530 execfile(pjoin('IPython','utils','submodule.py'), globals())
530 execfile(pjoin('IPython','utils','submodule.py'), globals())
531
531
532 class UpdateSubmodules(Command):
532 class UpdateSubmodules(Command):
533 """Update git submodules
533 """Update git submodules
534
534
535 IPython's external javascript dependencies live in a separate repo.
535 IPython's external javascript dependencies live in a separate repo.
536 """
536 """
537 description = "Update git submodules"
537 description = "Update git submodules"
538 user_options = []
538 user_options = []
539
539
540 def initialize_options(self):
540 def initialize_options(self):
541 pass
541 pass
542
542
543 def finalize_options(self):
543 def finalize_options(self):
544 pass
544 pass
545
545
546 def run(self):
546 def run(self):
547 failure = False
547 failure = False
548 try:
548 try:
549 self.spawn('git submodule init'.split())
549 self.spawn('git submodule init'.split())
550 self.spawn('git submodule update --recursive'.split())
550 self.spawn('git submodule update --recursive'.split())
551 except Exception as e:
551 except Exception as e:
552 failure = e
552 failure = e
553 print(e)
553 print(e)
554
554
555 if not check_submodule_status(repo_root) == 'clean':
555 if not check_submodule_status(repo_root) == 'clean':
556 print("submodules could not be checked out")
556 print("submodules could not be checked out")
557 sys.exit(1)
557 sys.exit(1)
558
558
559
559
560 def git_prebuild(pkg_dir, build_cmd=build_py):
560 def git_prebuild(pkg_dir, build_cmd=build_py):
561 """Return extended build or sdist command class for recording commit
561 """Return extended build or sdist command class for recording commit
562
562
563 records git commit in IPython.utils._sysinfo.commit
563 records git commit in IPython.utils._sysinfo.commit
564
564
565 for use in IPython.utils.sysinfo.sys_info() calls after installation.
565 for use in IPython.utils.sysinfo.sys_info() calls after installation.
566
566
567 Also ensures that submodules exist prior to running
567 Also ensures that submodules exist prior to running
568 """
568 """
569
569
570 class MyBuildPy(build_cmd):
570 class MyBuildPy(build_cmd):
571 ''' Subclass to write commit data into installation tree '''
571 ''' Subclass to write commit data into installation tree '''
572 def run(self):
572 def run(self):
573 build_cmd.run(self)
573 build_cmd.run(self)
574 # this one will only fire for build commands
574 # this one will only fire for build commands
575 if hasattr(self, 'build_lib'):
575 if hasattr(self, 'build_lib'):
576 self._record_commit(self.build_lib)
576 self._record_commit(self.build_lib)
577
577
578 def make_release_tree(self, base_dir, files):
578 def make_release_tree(self, base_dir, files):
579 # this one will fire for sdist
579 # this one will fire for sdist
580 build_cmd.make_release_tree(self, base_dir, files)
580 build_cmd.make_release_tree(self, base_dir, files)
581 self._record_commit(base_dir)
581 self._record_commit(base_dir)
582
582
583 def _record_commit(self, base_dir):
583 def _record_commit(self, base_dir):
584 import subprocess
584 import subprocess
585 proc = subprocess.Popen('git rev-parse --short HEAD',
585 proc = subprocess.Popen('git rev-parse --short HEAD',
586 stdout=subprocess.PIPE,
586 stdout=subprocess.PIPE,
587 stderr=subprocess.PIPE,
587 stderr=subprocess.PIPE,
588 shell=True)
588 shell=True)
589 repo_commit, _ = proc.communicate()
589 repo_commit, _ = proc.communicate()
590 repo_commit = repo_commit.strip().decode("ascii")
590 repo_commit = repo_commit.strip().decode("ascii")
591
591
592 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
592 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
593 if os.path.isfile(out_pth) and not repo_commit:
593 if os.path.isfile(out_pth) and not repo_commit:
594 # nothing to write, don't clobber
594 # nothing to write, don't clobber
595 return
595 return
596
596
597 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
597 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
598
598
599 # remove to avoid overwriting original via hard link
599 # remove to avoid overwriting original via hard link
600 try:
600 try:
601 os.remove(out_pth)
601 os.remove(out_pth)
602 except (IOError, OSError):
602 except (IOError, OSError):
603 pass
603 pass
604 with open(out_pth, 'w') as out_file:
604 with open(out_pth, 'w') as out_file:
605 out_file.writelines([
605 out_file.writelines([
606 '# GENERATED BY setup.py\n',
606 '# GENERATED BY setup.py\n',
607 'commit = u"%s"\n' % repo_commit,
607 'commit = u"%s"\n' % repo_commit,
608 ])
608 ])
609 return require_submodules(MyBuildPy)
609 return require_submodules(MyBuildPy)
610
610
611
611
612 def require_submodules(command):
612 def require_submodules(command):
613 """decorator for instructing a command to check for submodules before running"""
613 """decorator for instructing a command to check for submodules before running"""
614 class DecoratedCommand(command):
614 class DecoratedCommand(command):
615 def run(self):
615 def run(self):
616 if not check_submodule_status(repo_root) == 'clean':
616 if not check_submodule_status(repo_root) == 'clean':
617 print("submodules missing! Run `setup.py submodule` and try again")
617 print("submodules missing! Run `setup.py submodule` and try again")
618 sys.exit(1)
618 sys.exit(1)
619 command.run(self)
619 command.run(self)
620 return DecoratedCommand
620 return DecoratedCommand
621
621
622 #---------------------------------------------------------------------------
622 #---------------------------------------------------------------------------
623 # bdist related
623 # bdist related
624 #---------------------------------------------------------------------------
624 #---------------------------------------------------------------------------
625
625
626 def get_bdist_wheel():
626 def get_bdist_wheel():
627 """Construct bdist_wheel command for building wheels
627 """Construct bdist_wheel command for building wheels
628
628
629 Constructs py2-none-any tag, instead of py2.7-none-any
629 Constructs py2-none-any tag, instead of py2.7-none-any
630 """
630 """
631 class RequiresWheel(Command):
631 class RequiresWheel(Command):
632 description = "Dummy command for missing bdist_wheel"
632 description = "Dummy command for missing bdist_wheel"
633 user_options = []
633 user_options = []
634
634
635 def initialize_options(self):
635 def initialize_options(self):
636 pass
636 pass
637
637
638 def finalize_options(self):
638 def finalize_options(self):
639 pass
639 pass
640
640
641 def run(self):
641 def run(self):
642 print("bdist_wheel requires the wheel package")
642 print("bdist_wheel requires the wheel package")
643 sys.exit(1)
643 sys.exit(1)
644
644
645 if 'setuptools' not in sys.modules:
645 if 'setuptools' not in sys.modules:
646 return RequiresWheel
646 return RequiresWheel
647 else:
647 else:
648 try:
648 try:
649 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
649 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
650 except ImportError:
650 except ImportError:
651 return RequiresWheel
651 return RequiresWheel
652
652
653 class bdist_wheel_tag(bdist_wheel):
653 class bdist_wheel_tag(bdist_wheel):
654
654
655 def add_requirements(self, metadata_path):
655 def add_requirements(self, metadata_path):
656 """transform platform-dependent requirements"""
656 """transform platform-dependent requirements"""
657 pkg_info = read_pkg_info(metadata_path)
657 pkg_info = read_pkg_info(metadata_path)
658 # pkg_info is an email.Message object (?!)
658 # pkg_info is an email.Message object (?!)
659 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
659 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
660 # and transform them to conditionals
660 # and transform them to conditionals
661 requires = pkg_info.get_all('Requires-Dist')
661 requires = pkg_info.get_all('Requires-Dist')
662 del pkg_info['Requires-Dist']
662 del pkg_info['Requires-Dist']
663 def _remove_startswith(lis, prefix):
663 def _remove_startswith(lis, prefix):
664 """like list.remove, but with startswith instead of =="""
664 """like list.remove, but with startswith instead of =="""
665 found = False
665 found = False
666 for idx, item in enumerate(lis):
666 for idx, item in enumerate(lis):
667 if item.startswith(prefix):
667 if item.startswith(prefix):
668 found = True
668 found = True
669 break
669 break
670 if found:
670 if found:
671 lis.pop(idx)
671 lis.pop(idx)
672
672
673 for pkg in ("gnureadline", "pyreadline", "mock", "terminado"):
673 for pkg in ("gnureadline", "pyreadline", "mock", "appnope", "terminado"):
674 _remove_startswith(requires, pkg)
674 _remove_startswith(requires, pkg)
675 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
675 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
676 requires.append("terminado (>=0.3.3); extra == 'notebook' and sys.platform != 'win32'")
676 requires.append("terminado (>=0.3.3); extra == 'notebook' and sys.platform != 'win32'")
677 requires.append("terminado (>=0.3.3); extra == 'all' and sys.platform != 'win32'")
677 requires.append("terminado (>=0.3.3); extra == 'all' and sys.platform != 'win32'")
678 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
678 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
679 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
679 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
680 requires.append("mock; extra == 'test' and python_version < '3.3'")
680 requires.append("mock; extra == 'test' and python_version < '3.3'")
681 requires.append("appnope; sys.platform == 'darwin'")
681 for r in requires:
682 for r in requires:
682 pkg_info['Requires-Dist'] = r
683 pkg_info['Requires-Dist'] = r
683 write_pkg_info(metadata_path, pkg_info)
684 write_pkg_info(metadata_path, pkg_info)
684
685
685 return bdist_wheel_tag
686 return bdist_wheel_tag
686
687
687 #---------------------------------------------------------------------------
688 #---------------------------------------------------------------------------
688 # Notebook related
689 # Notebook related
689 #---------------------------------------------------------------------------
690 #---------------------------------------------------------------------------
690
691
691 class CompileCSS(Command):
692 class CompileCSS(Command):
692 """Recompile Notebook CSS
693 """Recompile Notebook CSS
693
694
694 Regenerate the compiled CSS from LESS sources.
695 Regenerate the compiled CSS from LESS sources.
695
696
696 Requires various dev dependencies, such as invoke and lessc.
697 Requires various dev dependencies, such as invoke and lessc.
697 """
698 """
698 description = "Recompile Notebook CSS"
699 description = "Recompile Notebook CSS"
699 user_options = [
700 user_options = [
700 ('minify', 'x', "minify CSS"),
701 ('minify', 'x', "minify CSS"),
701 ('force', 'f', "force recompilation of CSS"),
702 ('force', 'f', "force recompilation of CSS"),
702 ]
703 ]
703
704
704 def initialize_options(self):
705 def initialize_options(self):
705 self.minify = False
706 self.minify = False
706 self.force = False
707 self.force = False
707
708
708 def finalize_options(self):
709 def finalize_options(self):
709 self.minify = bool(self.minify)
710 self.minify = bool(self.minify)
710 self.force = bool(self.force)
711 self.force = bool(self.force)
711
712
712 def run(self):
713 def run(self):
713 cmd = ['invoke', 'css']
714 cmd = ['invoke', 'css']
714 if self.minify:
715 if self.minify:
715 cmd.append('--minify')
716 cmd.append('--minify')
716 if self.force:
717 if self.force:
717 cmd.append('--force')
718 cmd.append('--force')
718 try:
719 try:
719 p = Popen(cmd, cwd=pjoin(repo_root, "IPython", "html"), stderr=PIPE)
720 p = Popen(cmd, cwd=pjoin(repo_root, "IPython", "html"), stderr=PIPE)
720 except OSError:
721 except OSError:
721 raise DistutilsExecError("invoke is required to rebuild css (pip install invoke)")
722 raise DistutilsExecError("invoke is required to rebuild css (pip install invoke)")
722 out, err = p.communicate()
723 out, err = p.communicate()
723 if p.returncode:
724 if p.returncode:
724 if sys.version_info[0] >= 3:
725 if sys.version_info[0] >= 3:
725 err = err.decode('utf8', 'replace')
726 err = err.decode('utf8', 'replace')
726 raise DistutilsExecError(err.strip())
727 raise DistutilsExecError(err.strip())
727
728
728
729
729 class JavascriptVersion(Command):
730 class JavascriptVersion(Command):
730 """write the javascript version to notebook javascript"""
731 """write the javascript version to notebook javascript"""
731 description = "Write IPython version to javascript"
732 description = "Write IPython version to javascript"
732 user_options = []
733 user_options = []
733
734
734 def initialize_options(self):
735 def initialize_options(self):
735 pass
736 pass
736
737
737 def finalize_options(self):
738 def finalize_options(self):
738 pass
739 pass
739
740
740 def run(self):
741 def run(self):
741 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
742 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
742 with open(nsfile) as f:
743 with open(nsfile) as f:
743 lines = f.readlines()
744 lines = f.readlines()
744 with open(nsfile, 'w') as f:
745 with open(nsfile, 'w') as f:
745 found = False
746 found = False
746 for line in lines:
747 for line in lines:
747 if line.strip().startswith("IPython.version"):
748 if line.strip().startswith("IPython.version"):
748 line = ' IPython.version = "{0}";\n'.format(version)
749 line = ' IPython.version = "{0}";\n'.format(version)
749 found = True
750 found = True
750 f.write(line)
751 f.write(line)
751 if not found:
752 if not found:
752 raise RuntimeError("Didn't find IPython.version line in %s" % nsfile)
753 raise RuntimeError("Didn't find IPython.version line in %s" % nsfile)
753
754
754
755
755 def css_js_prerelease(command):
756 def css_js_prerelease(command):
756 """decorator for building js/minified css prior to a release"""
757 """decorator for building js/minified css prior to a release"""
757 class DecoratedCommand(command):
758 class DecoratedCommand(command):
758 def run(self):
759 def run(self):
759 self.distribution.run_command('jsversion')
760 self.distribution.run_command('jsversion')
760 css = self.distribution.get_command_obj('css')
761 css = self.distribution.get_command_obj('css')
761 css.minify = True
762 css.minify = True
762 try:
763 try:
763 self.distribution.run_command('css')
764 self.distribution.run_command('css')
764 except Exception as e:
765 except Exception as e:
765 log.warn("rebuilding css and sourcemaps failed (not a problem)")
766 log.warn("rebuilding css and sourcemaps failed (not a problem)")
766 log.warn(str(e))
767 log.warn(str(e))
767 command.run(self)
768 command.run(self)
768 return DecoratedCommand
769 return DecoratedCommand
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now