##// END OF EJS Templates
remove pexpect from external...
MinRK -
Show More
@@ -1,78 +1,78 b''
1 """Tests for two-process terminal frontend
1 """Tests for two-process terminal frontend
2
2
3 Currently only has the most simple test possible, starting a console and running
3 Currently only has the most simple test possible, starting a console and running
4 a single command.
4 a single command.
5
5
6 Authors:
6 Authors:
7
7
8 * Min RK
8 * Min RK
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Imports
12 # Imports
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 import sys
15 import sys
16
16
17 from nose import SkipTest
17 from nose import SkipTest
18
18
19 import IPython.testing.tools as tt
19 import IPython.testing.tools as tt
20 from IPython.testing import decorators as dec
20 from IPython.testing import decorators as dec
21
21
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 # Tests
23 # Tests
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25
25
26 @dec.skip_win32
26 @dec.skip_win32
27 def test_console_starts():
27 def test_console_starts():
28 """test that `ipython console` starts a terminal"""
28 """test that `ipython console` starts a terminal"""
29 p, pexpect, t = start_console()
29 p, pexpect, t = start_console()
30 p.sendline('5')
30 p.sendline('5')
31 idx = p.expect([r'Out\[\d+\]: 5', pexpect.EOF], timeout=t)
31 idx = p.expect([r'Out\[\d+\]: 5', pexpect.EOF], timeout=t)
32 idx = p.expect([r'In \[\d+\]', pexpect.EOF], timeout=t)
32 idx = p.expect([r'In \[\d+\]', pexpect.EOF], timeout=t)
33 stop_console(p, pexpect, t)
33 stop_console(p, pexpect, t)
34
34
35 def test_help_output():
35 def test_help_output():
36 """ipython console --help-all works"""
36 """ipython console --help-all works"""
37 tt.help_all_output_test('console')
37 tt.help_all_output_test('console')
38
38
39
39
40 def test_display_text():
40 def test_display_text():
41 "Ensure display protocol plain/text key is supported"
41 "Ensure display protocol plain/text key is supported"
42 # equivalent of:
42 # equivalent of:
43 #
43 #
44 # x = %lsmagic
44 # x = %lsmagic
45 # from IPython.display import display; display(x);
45 # from IPython.display import display; display(x);
46 p, pexpect, t = start_console()
46 p, pexpect, t = start_console()
47 p.sendline('x = %lsmagic')
47 p.sendline('x = %lsmagic')
48 idx = p.expect([r'In \[\d+\]', pexpect.EOF], timeout=t)
48 idx = p.expect([r'In \[\d+\]', pexpect.EOF], timeout=t)
49 p.sendline('from IPython.display import display; display(x);')
49 p.sendline('from IPython.display import display; display(x);')
50 p.expect([r'Available line magics:', pexpect.EOF], timeout=t)
50 p.expect([r'Available line magics:', pexpect.EOF], timeout=t)
51 stop_console(p, pexpect, t)
51 stop_console(p, pexpect, t)
52
52
53 def stop_console(p, pexpect, t):
53 def stop_console(p, pexpect, t):
54 "Stop a running `ipython console` running via pexpect"
54 "Stop a running `ipython console` running via pexpect"
55 # send ctrl-D;ctrl-D to exit
55 # send ctrl-D;ctrl-D to exit
56 p.sendeof()
56 p.sendeof()
57 p.sendeof()
57 p.sendeof()
58 p.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=t)
58 p.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=t)
59 if p.isalive():
59 if p.isalive():
60 p.terminate()
60 p.terminate()
61
61
62
62
63 def start_console():
63 def start_console():
64 "Start `ipython console` using pexpect"
64 "Start `ipython console` using pexpect"
65 from IPython.external import pexpect
65 import pexpect
66
66
67 args = ['-m', 'IPython', 'console', '--colors=NoColor']
67 args = ['-m', 'IPython', 'console', '--colors=NoColor']
68 cmd = sys.executable
68 cmd = sys.executable
69
69
70 try:
70 try:
71 p = pexpect.spawn(cmd, args=args)
71 p = pexpect.spawn(cmd, args=args)
72 except IOError:
72 except IOError:
73 raise SkipTest("Couldn't find command %s" % cmd)
73 raise SkipTest("Couldn't find command %s" % cmd)
74
74
75 # timeout after one minute
75 # timeout after one minute
76 t = 60
76 t = 60
77 idx = p.expect([r'In \[\d+\]', pexpect.EOF], timeout=t)
77 idx = p.expect([r'In \[\d+\]', pexpect.EOF], timeout=t)
78 return p, pexpect, t
78 return p, pexpect, t
@@ -1,125 +1,125 b''
1 """Test embedding of IPython"""
1 """Test embedding of IPython"""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
4 # Copyright (C) 2013 The IPython Development Team
5 #
5 #
6 # Distributed under the terms of the BSD License. The full license is in
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Imports
11 # Imports
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 import os
14 import os
15 import sys
15 import sys
16 import nose.tools as nt
16 import nose.tools as nt
17 from IPython.utils.process import process_handler
17 from IPython.utils.process import process_handler
18 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
18 from IPython.utils.tempdir import NamedFileInTemporaryDirectory
19 from IPython.testing.decorators import skip_win32
19 from IPython.testing.decorators import skip_win32
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Tests
22 # Tests
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25
25
26 _sample_embed = b"""
26 _sample_embed = b"""
27 from __future__ import print_function
27 from __future__ import print_function
28 import IPython
28 import IPython
29
29
30 a = 3
30 a = 3
31 b = 14
31 b = 14
32 print(a, '.', b)
32 print(a, '.', b)
33
33
34 IPython.embed()
34 IPython.embed()
35
35
36 print('bye!')
36 print('bye!')
37 """
37 """
38
38
39 _exit = b"exit\r"
39 _exit = b"exit\r"
40
40
41 def test_ipython_embed():
41 def test_ipython_embed():
42 """test that `IPython.embed()` works"""
42 """test that `IPython.embed()` works"""
43 with NamedFileInTemporaryDirectory('file_with_embed.py') as f:
43 with NamedFileInTemporaryDirectory('file_with_embed.py') as f:
44 f.write(_sample_embed)
44 f.write(_sample_embed)
45 f.flush()
45 f.flush()
46 f.close() # otherwise msft won't be able to read the file
46 f.close() # otherwise msft won't be able to read the file
47
47
48 # run `python file_with_embed.py`
48 # run `python file_with_embed.py`
49 cmd = [sys.executable, f.name]
49 cmd = [sys.executable, f.name]
50
50
51 out, p = process_handler(cmd, lambda p: (p.communicate(_exit), p))
51 out, p = process_handler(cmd, lambda p: (p.communicate(_exit), p))
52 std = out[0].decode('UTF-8')
52 std = out[0].decode('UTF-8')
53 nt.assert_equal(p.returncode, 0)
53 nt.assert_equal(p.returncode, 0)
54 nt.assert_in('3 . 14', std)
54 nt.assert_in('3 . 14', std)
55 if os.name != 'nt':
55 if os.name != 'nt':
56 # TODO: Fix up our different stdout references, see issue gh-14
56 # TODO: Fix up our different stdout references, see issue gh-14
57 nt.assert_in('IPython', std)
57 nt.assert_in('IPython', std)
58 nt.assert_in('bye!', std)
58 nt.assert_in('bye!', std)
59
59
60 @skip_win32
60 @skip_win32
61 def test_nest_embed():
61 def test_nest_embed():
62 """test that `IPython.embed()` is nestable"""
62 """test that `IPython.embed()` is nestable"""
63 from IPython.external import pexpect
63 import pexpect
64 ipy_prompt = r']:' #ansi color codes give problems matching beyond this
64 ipy_prompt = r']:' #ansi color codes give problems matching beyond this
65
65
66
66
67 child = pexpect.spawn('%s -m IPython'%(sys.executable, ))
67 child = pexpect.spawn('%s -m IPython'%(sys.executable, ))
68 child.expect(ipy_prompt)
68 child.expect(ipy_prompt)
69 child.sendline("from __future__ import print_function")
69 child.sendline("from __future__ import print_function")
70 child.expect(ipy_prompt)
70 child.expect(ipy_prompt)
71 child.sendline("import IPython")
71 child.sendline("import IPython")
72 child.expect(ipy_prompt)
72 child.expect(ipy_prompt)
73 child.sendline("ip0 = get_ipython()")
73 child.sendline("ip0 = get_ipython()")
74 #enter first nested embed
74 #enter first nested embed
75 child.sendline("IPython.embed()")
75 child.sendline("IPython.embed()")
76 #skip the banner until we get to a prompt
76 #skip the banner until we get to a prompt
77 try:
77 try:
78 prompted = -1
78 prompted = -1
79 while prompted != 0:
79 while prompted != 0:
80 prompted = child.expect([ipy_prompt, '\r\n'])
80 prompted = child.expect([ipy_prompt, '\r\n'])
81 except pexpect.TIMEOUT as e:
81 except pexpect.TIMEOUT as e:
82 print(e)
82 print(e)
83 #child.interact()
83 #child.interact()
84 child.sendline("embed1 = get_ipython()"); child.expect(ipy_prompt)
84 child.sendline("embed1 = get_ipython()"); child.expect(ipy_prompt)
85 child.sendline("print('true' if embed1 is not ip0 else 'false')")
85 child.sendline("print('true' if embed1 is not ip0 else 'false')")
86 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
86 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
87 child.expect(ipy_prompt)
87 child.expect(ipy_prompt)
88 child.sendline("print('true' if IPython.get_ipython() is embed1 else 'false')")
88 child.sendline("print('true' if IPython.get_ipython() is embed1 else 'false')")
89 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
89 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
90 child.expect(ipy_prompt)
90 child.expect(ipy_prompt)
91 #enter second nested embed
91 #enter second nested embed
92 child.sendline("IPython.embed()")
92 child.sendline("IPython.embed()")
93 #skip the banner until we get to a prompt
93 #skip the banner until we get to a prompt
94 try:
94 try:
95 prompted = -1
95 prompted = -1
96 while prompted != 0:
96 while prompted != 0:
97 prompted = child.expect([ipy_prompt, '\r\n'])
97 prompted = child.expect([ipy_prompt, '\r\n'])
98 except pexpect.TIMEOUT as e:
98 except pexpect.TIMEOUT as e:
99 print(e)
99 print(e)
100 #child.interact()
100 #child.interact()
101 child.sendline("embed2 = get_ipython()"); child.expect(ipy_prompt)
101 child.sendline("embed2 = get_ipython()"); child.expect(ipy_prompt)
102 child.sendline("print('true' if embed2 is not embed1 else 'false')")
102 child.sendline("print('true' if embed2 is not embed1 else 'false')")
103 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
103 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
104 child.expect(ipy_prompt)
104 child.expect(ipy_prompt)
105 child.sendline("print('true' if embed2 is IPython.get_ipython() else 'false')")
105 child.sendline("print('true' if embed2 is IPython.get_ipython() else 'false')")
106 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
106 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
107 child.expect(ipy_prompt)
107 child.expect(ipy_prompt)
108 child.sendline('exit')
108 child.sendline('exit')
109 #back at first embed
109 #back at first embed
110 child.expect(ipy_prompt)
110 child.expect(ipy_prompt)
111 child.sendline("print('true' if get_ipython() is embed1 else 'false')")
111 child.sendline("print('true' if get_ipython() is embed1 else 'false')")
112 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
112 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
113 child.expect(ipy_prompt)
113 child.expect(ipy_prompt)
114 child.sendline("print('true' if IPython.get_ipython() is embed1 else 'false')")
114 child.sendline("print('true' if IPython.get_ipython() is embed1 else 'false')")
115 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
115 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
116 child.expect(ipy_prompt)
116 child.expect(ipy_prompt)
117 child.sendline('exit')
117 child.sendline('exit')
118 #back at launching scope
118 #back at launching scope
119 child.expect(ipy_prompt)
119 child.expect(ipy_prompt)
120 child.sendline("print('true' if get_ipython() is ip0 else 'false')")
120 child.sendline("print('true' if get_ipython() is ip0 else 'false')")
121 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
121 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
122 child.expect(ipy_prompt)
122 child.expect(ipy_prompt)
123 child.sendline("print('true' if IPython.get_ipython() is ip0 else 'false')")
123 child.sendline("print('true' if IPython.get_ipython() is ip0 else 'false')")
124 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
124 assert(child.expect(['true\r\n', 'false\r\n']) == 0)
125 child.expect(ipy_prompt)
125 child.expect(ipy_prompt)
@@ -1,519 +1,513 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """IPython Test Suite Runner.
2 """IPython Test Suite Runner.
3
3
4 This module provides a main entry point to a user script to test IPython
4 This module provides a main entry point to a user script to test IPython
5 itself from the command line. There are two ways of running this script:
5 itself from the command line. There are two ways of running this script:
6
6
7 1. With the syntax `iptest all`. This runs our entire test suite by
7 1. With the syntax `iptest all`. This runs our entire test suite by
8 calling this script (with different arguments) recursively. This
8 calling this script (with different arguments) recursively. This
9 causes modules and package to be tested in different processes, using nose
9 causes modules and package to be tested in different processes, using nose
10 or trial where appropriate.
10 or trial where appropriate.
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 the script simply calls nose, but with special command line flags and
12 the script simply calls nose, but with special command line flags and
13 plugins loaded.
13 plugins loaded.
14
14
15 """
15 """
16
16
17 # Copyright (c) IPython Development Team.
17 # Copyright (c) IPython Development Team.
18 # Distributed under the terms of the Modified BSD License.
18 # Distributed under the terms of the Modified BSD License.
19
19
20 from __future__ import print_function
20 from __future__ import print_function
21
21
22 import glob
22 import glob
23 from io import BytesIO
23 from io import BytesIO
24 import os
24 import os
25 import os.path as path
25 import os.path as path
26 import sys
26 import sys
27 from threading import Thread, Lock, Event
27 from threading import Thread, Lock, Event
28 import warnings
28 import warnings
29
29
30 import nose.plugins.builtin
30 import nose.plugins.builtin
31 from nose.plugins.xunit import Xunit
31 from nose.plugins.xunit import Xunit
32 from nose import SkipTest
32 from nose import SkipTest
33 from nose.core import TestProgram
33 from nose.core import TestProgram
34 from nose.plugins import Plugin
34 from nose.plugins import Plugin
35 from nose.util import safe_str
35 from nose.util import safe_str
36
36
37 from IPython.utils.process import is_cmd_found
37 from IPython.utils.process import is_cmd_found
38 from IPython.utils.py3compat import bytes_to_str
38 from IPython.utils.py3compat import bytes_to_str
39 from IPython.utils.importstring import import_item
39 from IPython.utils.importstring import import_item
40 from IPython.testing.plugin.ipdoctest import IPythonDoctest
40 from IPython.testing.plugin.ipdoctest import IPythonDoctest
41 from IPython.external.decorators import KnownFailure, knownfailureif
41 from IPython.external.decorators import KnownFailure, knownfailureif
42
42
43 pjoin = path.join
43 pjoin = path.join
44
44
45
46 #-----------------------------------------------------------------------------
47 # Globals
48 #-----------------------------------------------------------------------------
49
50
51 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
52 # Warnings control
46 # Warnings control
53 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
54
48
55 # Twisted generates annoying warnings with Python 2.6, as will do other code
49 # Twisted generates annoying warnings with Python 2.6, as will do other code
56 # that imports 'sets' as of today
50 # that imports 'sets' as of today
57 warnings.filterwarnings('ignore', 'the sets module is deprecated',
51 warnings.filterwarnings('ignore', 'the sets module is deprecated',
58 DeprecationWarning )
52 DeprecationWarning )
59
53
60 # This one also comes from Twisted
54 # This one also comes from Twisted
61 warnings.filterwarnings('ignore', 'the sha module is deprecated',
55 warnings.filterwarnings('ignore', 'the sha module is deprecated',
62 DeprecationWarning)
56 DeprecationWarning)
63
57
64 # Wx on Fedora11 spits these out
58 # Wx on Fedora11 spits these out
65 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
59 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
66 UserWarning)
60 UserWarning)
67
61
68 # ------------------------------------------------------------------------------
62 # ------------------------------------------------------------------------------
69 # Monkeypatch Xunit to count known failures as skipped.
63 # Monkeypatch Xunit to count known failures as skipped.
70 # ------------------------------------------------------------------------------
64 # ------------------------------------------------------------------------------
71 def monkeypatch_xunit():
65 def monkeypatch_xunit():
72 try:
66 try:
73 knownfailureif(True)(lambda: None)()
67 knownfailureif(True)(lambda: None)()
74 except Exception as e:
68 except Exception as e:
75 KnownFailureTest = type(e)
69 KnownFailureTest = type(e)
76
70
77 def addError(self, test, err, capt=None):
71 def addError(self, test, err, capt=None):
78 if issubclass(err[0], KnownFailureTest):
72 if issubclass(err[0], KnownFailureTest):
79 err = (SkipTest,) + err[1:]
73 err = (SkipTest,) + err[1:]
80 return self.orig_addError(test, err, capt)
74 return self.orig_addError(test, err, capt)
81
75
82 Xunit.orig_addError = Xunit.addError
76 Xunit.orig_addError = Xunit.addError
83 Xunit.addError = addError
77 Xunit.addError = addError
84
78
85 #-----------------------------------------------------------------------------
79 #-----------------------------------------------------------------------------
86 # Check which dependencies are installed and greater than minimum version.
80 # Check which dependencies are installed and greater than minimum version.
87 #-----------------------------------------------------------------------------
81 #-----------------------------------------------------------------------------
88 def extract_version(mod):
82 def extract_version(mod):
89 return mod.__version__
83 return mod.__version__
90
84
91 def test_for(item, min_version=None, callback=extract_version):
85 def test_for(item, min_version=None, callback=extract_version):
92 """Test to see if item is importable, and optionally check against a minimum
86 """Test to see if item is importable, and optionally check against a minimum
93 version.
87 version.
94
88
95 If min_version is given, the default behavior is to check against the
89 If min_version is given, the default behavior is to check against the
96 `__version__` attribute of the item, but specifying `callback` allows you to
90 `__version__` attribute of the item, but specifying `callback` allows you to
97 extract the value you are interested in. e.g::
91 extract the value you are interested in. e.g::
98
92
99 In [1]: import sys
93 In [1]: import sys
100
94
101 In [2]: from IPython.testing.iptest import test_for
95 In [2]: from IPython.testing.iptest import test_for
102
96
103 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
97 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
104 Out[3]: True
98 Out[3]: True
105
99
106 """
100 """
107 try:
101 try:
108 check = import_item(item)
102 check = import_item(item)
109 except (ImportError, RuntimeError):
103 except (ImportError, RuntimeError):
110 # GTK reports Runtime error if it can't be initialized even if it's
104 # GTK reports Runtime error if it can't be initialized even if it's
111 # importable.
105 # importable.
112 return False
106 return False
113 else:
107 else:
114 if min_version:
108 if min_version:
115 if callback:
109 if callback:
116 # extra processing step to get version to compare
110 # extra processing step to get version to compare
117 check = callback(check)
111 check = callback(check)
118
112
119 return check >= min_version
113 return check >= min_version
120 else:
114 else:
121 return True
115 return True
122
116
123 # Global dict where we can store information on what we have and what we don't
117 # Global dict where we can store information on what we have and what we don't
124 # have available at test run time
118 # have available at test run time
125 have = {}
119 have = {}
126
120
127 have['curses'] = test_for('_curses')
121 have['curses'] = test_for('_curses')
128 have['matplotlib'] = test_for('matplotlib')
122 have['matplotlib'] = test_for('matplotlib')
129 have['numpy'] = test_for('numpy')
123 have['numpy'] = test_for('numpy')
130 have['pexpect'] = test_for('IPython.external.pexpect')
124 have['pexpect'] = test_for('pexpect')
131 have['pymongo'] = test_for('pymongo')
125 have['pymongo'] = test_for('pymongo')
132 have['pygments'] = test_for('pygments')
126 have['pygments'] = test_for('pygments')
133 have['qt'] = test_for('IPython.external.qt')
127 have['qt'] = test_for('IPython.external.qt')
134 have['sqlite3'] = test_for('sqlite3')
128 have['sqlite3'] = test_for('sqlite3')
135 have['tornado'] = test_for('tornado.version_info', (4,0), callback=None)
129 have['tornado'] = test_for('tornado.version_info', (4,0), callback=None)
136 have['jinja2'] = test_for('jinja2')
130 have['jinja2'] = test_for('jinja2')
137 have['mistune'] = test_for('mistune')
131 have['mistune'] = test_for('mistune')
138 have['requests'] = test_for('requests')
132 have['requests'] = test_for('requests')
139 have['sphinx'] = test_for('sphinx')
133 have['sphinx'] = test_for('sphinx')
140 have['jsonschema'] = test_for('jsonschema')
134 have['jsonschema'] = test_for('jsonschema')
141 have['terminado'] = test_for('terminado')
135 have['terminado'] = test_for('terminado')
142 have['casperjs'] = is_cmd_found('casperjs')
136 have['casperjs'] = is_cmd_found('casperjs')
143 have['phantomjs'] = is_cmd_found('phantomjs')
137 have['phantomjs'] = is_cmd_found('phantomjs')
144 have['slimerjs'] = is_cmd_found('slimerjs')
138 have['slimerjs'] = is_cmd_found('slimerjs')
145
139
146 min_zmq = (13,)
140 min_zmq = (13,)
147
141
148 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
142 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
149
143
150 #-----------------------------------------------------------------------------
144 #-----------------------------------------------------------------------------
151 # Test suite definitions
145 # Test suite definitions
152 #-----------------------------------------------------------------------------
146 #-----------------------------------------------------------------------------
153
147
154 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
148 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
155 'extensions', 'lib', 'terminal', 'testing', 'utils',
149 'extensions', 'lib', 'terminal', 'testing', 'utils',
156 'nbformat', 'qt', 'html', 'nbconvert'
150 'nbformat', 'qt', 'html', 'nbconvert'
157 ]
151 ]
158
152
159 class TestSection(object):
153 class TestSection(object):
160 def __init__(self, name, includes):
154 def __init__(self, name, includes):
161 self.name = name
155 self.name = name
162 self.includes = includes
156 self.includes = includes
163 self.excludes = []
157 self.excludes = []
164 self.dependencies = []
158 self.dependencies = []
165 self.enabled = True
159 self.enabled = True
166
160
167 def exclude(self, module):
161 def exclude(self, module):
168 if not module.startswith('IPython'):
162 if not module.startswith('IPython'):
169 module = self.includes[0] + "." + module
163 module = self.includes[0] + "." + module
170 self.excludes.append(module.replace('.', os.sep))
164 self.excludes.append(module.replace('.', os.sep))
171
165
172 def requires(self, *packages):
166 def requires(self, *packages):
173 self.dependencies.extend(packages)
167 self.dependencies.extend(packages)
174
168
175 @property
169 @property
176 def will_run(self):
170 def will_run(self):
177 return self.enabled and all(have[p] for p in self.dependencies)
171 return self.enabled and all(have[p] for p in self.dependencies)
178
172
179 # Name -> (include, exclude, dependencies_met)
173 # Name -> (include, exclude, dependencies_met)
180 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
174 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
181
175
182 # Exclusions and dependencies
176 # Exclusions and dependencies
183 # ---------------------------
177 # ---------------------------
184
178
185 # core:
179 # core:
186 sec = test_sections['core']
180 sec = test_sections['core']
187 if not have['sqlite3']:
181 if not have['sqlite3']:
188 sec.exclude('tests.test_history')
182 sec.exclude('tests.test_history')
189 sec.exclude('history')
183 sec.exclude('history')
190 if not have['matplotlib']:
184 if not have['matplotlib']:
191 sec.exclude('pylabtools'),
185 sec.exclude('pylabtools'),
192 sec.exclude('tests.test_pylabtools')
186 sec.exclude('tests.test_pylabtools')
193
187
194 # lib:
188 # lib:
195 sec = test_sections['lib']
189 sec = test_sections['lib']
196 if not have['zmq']:
190 if not have['zmq']:
197 sec.exclude('kernel')
191 sec.exclude('kernel')
198 # We do this unconditionally, so that the test suite doesn't import
192 # We do this unconditionally, so that the test suite doesn't import
199 # gtk, changing the default encoding and masking some unicode bugs.
193 # gtk, changing the default encoding and masking some unicode bugs.
200 sec.exclude('inputhookgtk')
194 sec.exclude('inputhookgtk')
201 # We also do this unconditionally, because wx can interfere with Unix signals.
195 # We also do this unconditionally, because wx can interfere with Unix signals.
202 # There are currently no tests for it anyway.
196 # There are currently no tests for it anyway.
203 sec.exclude('inputhookwx')
197 sec.exclude('inputhookwx')
204 # Testing inputhook will need a lot of thought, to figure out
198 # Testing inputhook will need a lot of thought, to figure out
205 # how to have tests that don't lock up with the gui event
199 # how to have tests that don't lock up with the gui event
206 # loops in the picture
200 # loops in the picture
207 sec.exclude('inputhook')
201 sec.exclude('inputhook')
208
202
209 # testing:
203 # testing:
210 sec = test_sections['testing']
204 sec = test_sections['testing']
211 # These have to be skipped on win32 because they use echo, rm, cd, etc.
205 # These have to be skipped on win32 because they use echo, rm, cd, etc.
212 # See ticket https://github.com/ipython/ipython/issues/87
206 # See ticket https://github.com/ipython/ipython/issues/87
213 if sys.platform == 'win32':
207 if sys.platform == 'win32':
214 sec.exclude('plugin.test_exampleip')
208 sec.exclude('plugin.test_exampleip')
215 sec.exclude('plugin.dtexample')
209 sec.exclude('plugin.dtexample')
216
210
217 # terminal:
211 # terminal:
218 if (not have['pexpect']) or (not have['zmq']):
212 if (not have['pexpect']) or (not have['zmq']):
219 test_sections['terminal'].exclude('console')
213 test_sections['terminal'].exclude('console')
220
214
221 # parallel
215 # parallel
222 sec = test_sections['parallel']
216 sec = test_sections['parallel']
223 sec.requires('zmq')
217 sec.requires('zmq')
224 if not have['pymongo']:
218 if not have['pymongo']:
225 sec.exclude('controller.mongodb')
219 sec.exclude('controller.mongodb')
226 sec.exclude('tests.test_mongodb')
220 sec.exclude('tests.test_mongodb')
227
221
228 # kernel:
222 # kernel:
229 sec = test_sections['kernel']
223 sec = test_sections['kernel']
230 sec.requires('zmq')
224 sec.requires('zmq')
231 # The in-process kernel tests are done in a separate section
225 # The in-process kernel tests are done in a separate section
232 sec.exclude('inprocess')
226 sec.exclude('inprocess')
233 # importing gtk sets the default encoding, which we want to avoid
227 # importing gtk sets the default encoding, which we want to avoid
234 sec.exclude('zmq.gui.gtkembed')
228 sec.exclude('zmq.gui.gtkembed')
235 sec.exclude('zmq.gui.gtk3embed')
229 sec.exclude('zmq.gui.gtk3embed')
236 if not have['matplotlib']:
230 if not have['matplotlib']:
237 sec.exclude('zmq.pylab')
231 sec.exclude('zmq.pylab')
238
232
239 # kernel.inprocess:
233 # kernel.inprocess:
240 test_sections['kernel.inprocess'].requires('zmq')
234 test_sections['kernel.inprocess'].requires('zmq')
241
235
242 # extensions:
236 # extensions:
243 sec = test_sections['extensions']
237 sec = test_sections['extensions']
244 # This is deprecated in favour of rpy2
238 # This is deprecated in favour of rpy2
245 sec.exclude('rmagic')
239 sec.exclude('rmagic')
246 # autoreload does some strange stuff, so move it to its own test section
240 # autoreload does some strange stuff, so move it to its own test section
247 sec.exclude('autoreload')
241 sec.exclude('autoreload')
248 sec.exclude('tests.test_autoreload')
242 sec.exclude('tests.test_autoreload')
249 test_sections['autoreload'] = TestSection('autoreload',
243 test_sections['autoreload'] = TestSection('autoreload',
250 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
244 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
251 test_group_names.append('autoreload')
245 test_group_names.append('autoreload')
252
246
253 # qt:
247 # qt:
254 test_sections['qt'].requires('zmq', 'qt', 'pygments')
248 test_sections['qt'].requires('zmq', 'qt', 'pygments')
255
249
256 # html:
250 # html:
257 sec = test_sections['html']
251 sec = test_sections['html']
258 sec.requires('zmq', 'tornado', 'requests', 'sqlite3', 'jsonschema')
252 sec.requires('zmq', 'tornado', 'requests', 'sqlite3', 'jsonschema')
259 # The notebook 'static' directory contains JS, css and other
253 # The notebook 'static' directory contains JS, css and other
260 # files for web serving. Occasionally projects may put a .py
254 # files for web serving. Occasionally projects may put a .py
261 # file in there (MathJax ships a conf.py), so we might as
255 # file in there (MathJax ships a conf.py), so we might as
262 # well play it safe and skip the whole thing.
256 # well play it safe and skip the whole thing.
263 sec.exclude('static')
257 sec.exclude('static')
264 sec.exclude('tasks')
258 sec.exclude('tasks')
265 if not have['jinja2']:
259 if not have['jinja2']:
266 sec.exclude('notebookapp')
260 sec.exclude('notebookapp')
267 if not have['pygments'] or not have['jinja2']:
261 if not have['pygments'] or not have['jinja2']:
268 sec.exclude('nbconvert')
262 sec.exclude('nbconvert')
269 if not have['terminado']:
263 if not have['terminado']:
270 sec.exclude('terminal')
264 sec.exclude('terminal')
271
265
272 # config:
266 # config:
273 # Config files aren't really importable stand-alone
267 # Config files aren't really importable stand-alone
274 test_sections['config'].exclude('profile')
268 test_sections['config'].exclude('profile')
275
269
276 # nbconvert:
270 # nbconvert:
277 sec = test_sections['nbconvert']
271 sec = test_sections['nbconvert']
278 sec.requires('pygments', 'jinja2', 'jsonschema', 'mistune')
272 sec.requires('pygments', 'jinja2', 'jsonschema', 'mistune')
279 # Exclude nbconvert directories containing config files used to test.
273 # Exclude nbconvert directories containing config files used to test.
280 # Executing the config files with iptest would cause an exception.
274 # Executing the config files with iptest would cause an exception.
281 sec.exclude('tests.files')
275 sec.exclude('tests.files')
282 sec.exclude('exporters.tests.files')
276 sec.exclude('exporters.tests.files')
283 if not have['tornado']:
277 if not have['tornado']:
284 sec.exclude('nbconvert.post_processors.serve')
278 sec.exclude('nbconvert.post_processors.serve')
285 sec.exclude('nbconvert.post_processors.tests.test_serve')
279 sec.exclude('nbconvert.post_processors.tests.test_serve')
286
280
287 # nbformat:
281 # nbformat:
288 test_sections['nbformat'].requires('jsonschema')
282 test_sections['nbformat'].requires('jsonschema')
289
283
290 #-----------------------------------------------------------------------------
284 #-----------------------------------------------------------------------------
291 # Functions and classes
285 # Functions and classes
292 #-----------------------------------------------------------------------------
286 #-----------------------------------------------------------------------------
293
287
294 def check_exclusions_exist():
288 def check_exclusions_exist():
295 from IPython.utils.path import get_ipython_package_dir
289 from IPython.utils.path import get_ipython_package_dir
296 from IPython.utils.warn import warn
290 from IPython.utils.warn import warn
297 parent = os.path.dirname(get_ipython_package_dir())
291 parent = os.path.dirname(get_ipython_package_dir())
298 for sec in test_sections:
292 for sec in test_sections:
299 for pattern in sec.exclusions:
293 for pattern in sec.exclusions:
300 fullpath = pjoin(parent, pattern)
294 fullpath = pjoin(parent, pattern)
301 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
295 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
302 warn("Excluding nonexistent file: %r" % pattern)
296 warn("Excluding nonexistent file: %r" % pattern)
303
297
304
298
305 class ExclusionPlugin(Plugin):
299 class ExclusionPlugin(Plugin):
306 """A nose plugin to effect our exclusions of files and directories.
300 """A nose plugin to effect our exclusions of files and directories.
307 """
301 """
308 name = 'exclusions'
302 name = 'exclusions'
309 score = 3000 # Should come before any other plugins
303 score = 3000 # Should come before any other plugins
310
304
311 def __init__(self, exclude_patterns=None):
305 def __init__(self, exclude_patterns=None):
312 """
306 """
313 Parameters
307 Parameters
314 ----------
308 ----------
315
309
316 exclude_patterns : sequence of strings, optional
310 exclude_patterns : sequence of strings, optional
317 Filenames containing these patterns (as raw strings, not as regular
311 Filenames containing these patterns (as raw strings, not as regular
318 expressions) are excluded from the tests.
312 expressions) are excluded from the tests.
319 """
313 """
320 self.exclude_patterns = exclude_patterns or []
314 self.exclude_patterns = exclude_patterns or []
321 super(ExclusionPlugin, self).__init__()
315 super(ExclusionPlugin, self).__init__()
322
316
323 def options(self, parser, env=os.environ):
317 def options(self, parser, env=os.environ):
324 Plugin.options(self, parser, env)
318 Plugin.options(self, parser, env)
325
319
326 def configure(self, options, config):
320 def configure(self, options, config):
327 Plugin.configure(self, options, config)
321 Plugin.configure(self, options, config)
328 # Override nose trying to disable plugin.
322 # Override nose trying to disable plugin.
329 self.enabled = True
323 self.enabled = True
330
324
331 def wantFile(self, filename):
325 def wantFile(self, filename):
332 """Return whether the given filename should be scanned for tests.
326 """Return whether the given filename should be scanned for tests.
333 """
327 """
334 if any(pat in filename for pat in self.exclude_patterns):
328 if any(pat in filename for pat in self.exclude_patterns):
335 return False
329 return False
336 return None
330 return None
337
331
338 def wantDirectory(self, directory):
332 def wantDirectory(self, directory):
339 """Return whether the given directory should be scanned for tests.
333 """Return whether the given directory should be scanned for tests.
340 """
334 """
341 if any(pat in directory for pat in self.exclude_patterns):
335 if any(pat in directory for pat in self.exclude_patterns):
342 return False
336 return False
343 return None
337 return None
344
338
345
339
346 class StreamCapturer(Thread):
340 class StreamCapturer(Thread):
347 daemon = True # Don't hang if main thread crashes
341 daemon = True # Don't hang if main thread crashes
348 started = False
342 started = False
349 def __init__(self, echo=False):
343 def __init__(self, echo=False):
350 super(StreamCapturer, self).__init__()
344 super(StreamCapturer, self).__init__()
351 self.echo = echo
345 self.echo = echo
352 self.streams = []
346 self.streams = []
353 self.buffer = BytesIO()
347 self.buffer = BytesIO()
354 self.readfd, self.writefd = os.pipe()
348 self.readfd, self.writefd = os.pipe()
355 self.buffer_lock = Lock()
349 self.buffer_lock = Lock()
356 self.stop = Event()
350 self.stop = Event()
357
351
358 def run(self):
352 def run(self):
359 self.started = True
353 self.started = True
360
354
361 while not self.stop.is_set():
355 while not self.stop.is_set():
362 chunk = os.read(self.readfd, 1024)
356 chunk = os.read(self.readfd, 1024)
363
357
364 with self.buffer_lock:
358 with self.buffer_lock:
365 self.buffer.write(chunk)
359 self.buffer.write(chunk)
366 if self.echo:
360 if self.echo:
367 sys.stdout.write(bytes_to_str(chunk))
361 sys.stdout.write(bytes_to_str(chunk))
368
362
369 os.close(self.readfd)
363 os.close(self.readfd)
370 os.close(self.writefd)
364 os.close(self.writefd)
371
365
372 def reset_buffer(self):
366 def reset_buffer(self):
373 with self.buffer_lock:
367 with self.buffer_lock:
374 self.buffer.truncate(0)
368 self.buffer.truncate(0)
375 self.buffer.seek(0)
369 self.buffer.seek(0)
376
370
377 def get_buffer(self):
371 def get_buffer(self):
378 with self.buffer_lock:
372 with self.buffer_lock:
379 return self.buffer.getvalue()
373 return self.buffer.getvalue()
380
374
381 def ensure_started(self):
375 def ensure_started(self):
382 if not self.started:
376 if not self.started:
383 self.start()
377 self.start()
384
378
385 def halt(self):
379 def halt(self):
386 """Safely stop the thread."""
380 """Safely stop the thread."""
387 if not self.started:
381 if not self.started:
388 return
382 return
389
383
390 self.stop.set()
384 self.stop.set()
391 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
385 os.write(self.writefd, b'\0') # Ensure we're not locked in a read()
392 self.join()
386 self.join()
393
387
394 class SubprocessStreamCapturePlugin(Plugin):
388 class SubprocessStreamCapturePlugin(Plugin):
395 name='subprocstreams'
389 name='subprocstreams'
396 def __init__(self):
390 def __init__(self):
397 Plugin.__init__(self)
391 Plugin.__init__(self)
398 self.stream_capturer = StreamCapturer()
392 self.stream_capturer = StreamCapturer()
399 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
393 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
400 # This is ugly, but distant parts of the test machinery need to be able
394 # This is ugly, but distant parts of the test machinery need to be able
401 # to redirect streams, so we make the object globally accessible.
395 # to redirect streams, so we make the object globally accessible.
402 nose.iptest_stdstreams_fileno = self.get_write_fileno
396 nose.iptest_stdstreams_fileno = self.get_write_fileno
403
397
404 def get_write_fileno(self):
398 def get_write_fileno(self):
405 if self.destination == 'capture':
399 if self.destination == 'capture':
406 self.stream_capturer.ensure_started()
400 self.stream_capturer.ensure_started()
407 return self.stream_capturer.writefd
401 return self.stream_capturer.writefd
408 elif self.destination == 'discard':
402 elif self.destination == 'discard':
409 return os.open(os.devnull, os.O_WRONLY)
403 return os.open(os.devnull, os.O_WRONLY)
410 else:
404 else:
411 return sys.__stdout__.fileno()
405 return sys.__stdout__.fileno()
412
406
413 def configure(self, options, config):
407 def configure(self, options, config):
414 Plugin.configure(self, options, config)
408 Plugin.configure(self, options, config)
415 # Override nose trying to disable plugin.
409 # Override nose trying to disable plugin.
416 if self.destination == 'capture':
410 if self.destination == 'capture':
417 self.enabled = True
411 self.enabled = True
418
412
419 def startTest(self, test):
413 def startTest(self, test):
420 # Reset log capture
414 # Reset log capture
421 self.stream_capturer.reset_buffer()
415 self.stream_capturer.reset_buffer()
422
416
423 def formatFailure(self, test, err):
417 def formatFailure(self, test, err):
424 # Show output
418 # Show output
425 ec, ev, tb = err
419 ec, ev, tb = err
426 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
420 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
427 if captured.strip():
421 if captured.strip():
428 ev = safe_str(ev)
422 ev = safe_str(ev)
429 out = [ev, '>> begin captured subprocess output <<',
423 out = [ev, '>> begin captured subprocess output <<',
430 captured,
424 captured,
431 '>> end captured subprocess output <<']
425 '>> end captured subprocess output <<']
432 return ec, '\n'.join(out), tb
426 return ec, '\n'.join(out), tb
433
427
434 return err
428 return err
435
429
436 formatError = formatFailure
430 formatError = formatFailure
437
431
438 def finalize(self, result):
432 def finalize(self, result):
439 self.stream_capturer.halt()
433 self.stream_capturer.halt()
440
434
441
435
442 def run_iptest():
436 def run_iptest():
443 """Run the IPython test suite using nose.
437 """Run the IPython test suite using nose.
444
438
445 This function is called when this script is **not** called with the form
439 This function is called when this script is **not** called with the form
446 `iptest all`. It simply calls nose with appropriate command line flags
440 `iptest all`. It simply calls nose with appropriate command line flags
447 and accepts all of the standard nose arguments.
441 and accepts all of the standard nose arguments.
448 """
442 """
449 # Apply our monkeypatch to Xunit
443 # Apply our monkeypatch to Xunit
450 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
444 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
451 monkeypatch_xunit()
445 monkeypatch_xunit()
452
446
453 warnings.filterwarnings('ignore',
447 warnings.filterwarnings('ignore',
454 'This will be removed soon. Use IPython.testing.util instead')
448 'This will be removed soon. Use IPython.testing.util instead')
455
449
456 arg1 = sys.argv[1]
450 arg1 = sys.argv[1]
457 if arg1 in test_sections:
451 if arg1 in test_sections:
458 section = test_sections[arg1]
452 section = test_sections[arg1]
459 sys.argv[1:2] = section.includes
453 sys.argv[1:2] = section.includes
460 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
454 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
461 section = test_sections[arg1[8:]]
455 section = test_sections[arg1[8:]]
462 sys.argv[1:2] = section.includes
456 sys.argv[1:2] = section.includes
463 else:
457 else:
464 section = TestSection(arg1, includes=[arg1])
458 section = TestSection(arg1, includes=[arg1])
465
459
466
460
467 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
461 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
468
462
469 '--with-ipdoctest',
463 '--with-ipdoctest',
470 '--ipdoctest-tests','--ipdoctest-extension=txt',
464 '--ipdoctest-tests','--ipdoctest-extension=txt',
471
465
472 # We add --exe because of setuptools' imbecility (it
466 # We add --exe because of setuptools' imbecility (it
473 # blindly does chmod +x on ALL files). Nose does the
467 # blindly does chmod +x on ALL files). Nose does the
474 # right thing and it tries to avoid executables,
468 # right thing and it tries to avoid executables,
475 # setuptools unfortunately forces our hand here. This
469 # setuptools unfortunately forces our hand here. This
476 # has been discussed on the distutils list and the
470 # has been discussed on the distutils list and the
477 # setuptools devs refuse to fix this problem!
471 # setuptools devs refuse to fix this problem!
478 '--exe',
472 '--exe',
479 ]
473 ]
480 if '-a' not in argv and '-A' not in argv:
474 if '-a' not in argv and '-A' not in argv:
481 argv = argv + ['-a', '!crash']
475 argv = argv + ['-a', '!crash']
482
476
483 if nose.__version__ >= '0.11':
477 if nose.__version__ >= '0.11':
484 # I don't fully understand why we need this one, but depending on what
478 # I don't fully understand why we need this one, but depending on what
485 # directory the test suite is run from, if we don't give it, 0 tests
479 # directory the test suite is run from, if we don't give it, 0 tests
486 # get run. Specifically, if the test suite is run from the source dir
480 # get run. Specifically, if the test suite is run from the source dir
487 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
481 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
488 # even if the same call done in this directory works fine). It appears
482 # even if the same call done in this directory works fine). It appears
489 # that if the requested package is in the current dir, nose bails early
483 # that if the requested package is in the current dir, nose bails early
490 # by default. Since it's otherwise harmless, leave it in by default
484 # by default. Since it's otherwise harmless, leave it in by default
491 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
485 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
492 argv.append('--traverse-namespace')
486 argv.append('--traverse-namespace')
493
487
494 # use our plugin for doctesting. It will remove the standard doctest plugin
488 # use our plugin for doctesting. It will remove the standard doctest plugin
495 # if it finds it enabled
489 # if it finds it enabled
496 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(),
490 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(),
497 SubprocessStreamCapturePlugin() ]
491 SubprocessStreamCapturePlugin() ]
498
492
499 # Use working directory set by parent process (see iptestcontroller)
493 # Use working directory set by parent process (see iptestcontroller)
500 if 'IPTEST_WORKING_DIR' in os.environ:
494 if 'IPTEST_WORKING_DIR' in os.environ:
501 os.chdir(os.environ['IPTEST_WORKING_DIR'])
495 os.chdir(os.environ['IPTEST_WORKING_DIR'])
502
496
503 # We need a global ipython running in this process, but the special
497 # We need a global ipython running in this process, but the special
504 # in-process group spawns its own IPython kernels, so for *that* group we
498 # in-process group spawns its own IPython kernels, so for *that* group we
505 # must avoid also opening the global one (otherwise there's a conflict of
499 # must avoid also opening the global one (otherwise there's a conflict of
506 # singletons). Ultimately the solution to this problem is to refactor our
500 # singletons). Ultimately the solution to this problem is to refactor our
507 # assumptions about what needs to be a singleton and what doesn't (app
501 # assumptions about what needs to be a singleton and what doesn't (app
508 # objects should, individual shells shouldn't). But for now, this
502 # objects should, individual shells shouldn't). But for now, this
509 # workaround allows the test suite for the inprocess module to complete.
503 # workaround allows the test suite for the inprocess module to complete.
510 if 'kernel.inprocess' not in section.name:
504 if 'kernel.inprocess' not in section.name:
511 from IPython.testing import globalipapp
505 from IPython.testing import globalipapp
512 globalipapp.start_ipython()
506 globalipapp.start_ipython()
513
507
514 # Now nose can run
508 # Now nose can run
515 TestProgram(argv=argv, addplugins=plugins)
509 TestProgram(argv=argv, addplugins=plugins)
516
510
517 if __name__ == '__main__':
511 if __name__ == '__main__':
518 run_iptest()
512 run_iptest()
519
513
@@ -1,225 +1,225 b''
1 """Posix-specific implementation of process utilities.
1 """Posix-specific implementation of process utilities.
2
2
3 This file is only meant to be imported by process.py, not by end-users.
3 This file is only meant to be imported by process.py, not by end-users.
4 """
4 """
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (C) 2010-2011 The IPython Development Team
7 # Copyright (C) 2010-2011 The IPython Development Team
8 #
8 #
9 # Distributed under the terms of the BSD License. The full license is in
9 # Distributed under the terms of the BSD License. The full license is in
10 # the file COPYING, distributed as part of this software.
10 # the file COPYING, distributed as part of this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from __future__ import print_function
16 from __future__ import print_function
17
17
18 # Stdlib
18 # Stdlib
19 import errno
19 import errno
20 import os
20 import os
21 import subprocess as sp
21 import subprocess as sp
22 import sys
22 import sys
23
23
24 from IPython.external import pexpect
24 import pexpect
25
25
26 # Our own
26 # Our own
27 from ._process_common import getoutput, arg_split
27 from ._process_common import getoutput, arg_split
28 from IPython.utils import py3compat
28 from IPython.utils import py3compat
29 from IPython.utils.encoding import DEFAULT_ENCODING
29 from IPython.utils.encoding import DEFAULT_ENCODING
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Function definitions
32 # Function definitions
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35 def _find_cmd(cmd):
35 def _find_cmd(cmd):
36 """Find the full path to a command using which."""
36 """Find the full path to a command using which."""
37
37
38 path = sp.Popen(['/usr/bin/env', 'which', cmd],
38 path = sp.Popen(['/usr/bin/env', 'which', cmd],
39 stdout=sp.PIPE, stderr=sp.PIPE).communicate()[0]
39 stdout=sp.PIPE, stderr=sp.PIPE).communicate()[0]
40 return py3compat.bytes_to_str(path)
40 return py3compat.bytes_to_str(path)
41
41
42
42
43 class ProcessHandler(object):
43 class ProcessHandler(object):
44 """Execute subprocesses under the control of pexpect.
44 """Execute subprocesses under the control of pexpect.
45 """
45 """
46 # Timeout in seconds to wait on each reading of the subprocess' output.
46 # Timeout in seconds to wait on each reading of the subprocess' output.
47 # This should not be set too low to avoid cpu overusage from our side,
47 # This should not be set too low to avoid cpu overusage from our side,
48 # since we read in a loop whose period is controlled by this timeout.
48 # since we read in a loop whose period is controlled by this timeout.
49 read_timeout = 0.05
49 read_timeout = 0.05
50
50
51 # Timeout to give a process if we receive SIGINT, between sending the
51 # Timeout to give a process if we receive SIGINT, between sending the
52 # SIGINT to the process and forcefully terminating it.
52 # SIGINT to the process and forcefully terminating it.
53 terminate_timeout = 0.2
53 terminate_timeout = 0.2
54
54
55 # File object where stdout and stderr of the subprocess will be written
55 # File object where stdout and stderr of the subprocess will be written
56 logfile = None
56 logfile = None
57
57
58 # Shell to call for subprocesses to execute
58 # Shell to call for subprocesses to execute
59 _sh = None
59 _sh = None
60
60
61 @property
61 @property
62 def sh(self):
62 def sh(self):
63 if self._sh is None:
63 if self._sh is None:
64 self._sh = pexpect.which('sh')
64 self._sh = pexpect.which('sh')
65 if self._sh is None:
65 if self._sh is None:
66 raise OSError('"sh" shell not found')
66 raise OSError('"sh" shell not found')
67
67
68 return self._sh
68 return self._sh
69
69
70 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
70 def __init__(self, logfile=None, read_timeout=None, terminate_timeout=None):
71 """Arguments are used for pexpect calls."""
71 """Arguments are used for pexpect calls."""
72 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
72 self.read_timeout = (ProcessHandler.read_timeout if read_timeout is
73 None else read_timeout)
73 None else read_timeout)
74 self.terminate_timeout = (ProcessHandler.terminate_timeout if
74 self.terminate_timeout = (ProcessHandler.terminate_timeout if
75 terminate_timeout is None else
75 terminate_timeout is None else
76 terminate_timeout)
76 terminate_timeout)
77 self.logfile = sys.stdout if logfile is None else logfile
77 self.logfile = sys.stdout if logfile is None else logfile
78
78
79 def getoutput(self, cmd):
79 def getoutput(self, cmd):
80 """Run a command and return its stdout/stderr as a string.
80 """Run a command and return its stdout/stderr as a string.
81
81
82 Parameters
82 Parameters
83 ----------
83 ----------
84 cmd : str
84 cmd : str
85 A command to be executed in the system shell.
85 A command to be executed in the system shell.
86
86
87 Returns
87 Returns
88 -------
88 -------
89 output : str
89 output : str
90 A string containing the combination of stdout and stderr from the
90 A string containing the combination of stdout and stderr from the
91 subprocess, in whatever order the subprocess originally wrote to its
91 subprocess, in whatever order the subprocess originally wrote to its
92 file descriptors (so the order of the information in this string is the
92 file descriptors (so the order of the information in this string is the
93 correct order as would be seen if running the command in a terminal).
93 correct order as would be seen if running the command in a terminal).
94 """
94 """
95 try:
95 try:
96 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
96 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
97 except KeyboardInterrupt:
97 except KeyboardInterrupt:
98 print('^C', file=sys.stderr, end='')
98 print('^C', file=sys.stderr, end='')
99
99
100 def getoutput_pexpect(self, cmd):
100 def getoutput_pexpect(self, cmd):
101 """Run a command and return its stdout/stderr as a string.
101 """Run a command and return its stdout/stderr as a string.
102
102
103 Parameters
103 Parameters
104 ----------
104 ----------
105 cmd : str
105 cmd : str
106 A command to be executed in the system shell.
106 A command to be executed in the system shell.
107
107
108 Returns
108 Returns
109 -------
109 -------
110 output : str
110 output : str
111 A string containing the combination of stdout and stderr from the
111 A string containing the combination of stdout and stderr from the
112 subprocess, in whatever order the subprocess originally wrote to its
112 subprocess, in whatever order the subprocess originally wrote to its
113 file descriptors (so the order of the information in this string is the
113 file descriptors (so the order of the information in this string is the
114 correct order as would be seen if running the command in a terminal).
114 correct order as would be seen if running the command in a terminal).
115 """
115 """
116 try:
116 try:
117 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
117 return pexpect.run(self.sh, args=['-c', cmd]).replace('\r\n', '\n')
118 except KeyboardInterrupt:
118 except KeyboardInterrupt:
119 print('^C', file=sys.stderr, end='')
119 print('^C', file=sys.stderr, end='')
120
120
121 def system(self, cmd):
121 def system(self, cmd):
122 """Execute a command in a subshell.
122 """Execute a command in a subshell.
123
123
124 Parameters
124 Parameters
125 ----------
125 ----------
126 cmd : str
126 cmd : str
127 A command to be executed in the system shell.
127 A command to be executed in the system shell.
128
128
129 Returns
129 Returns
130 -------
130 -------
131 int : child's exitstatus
131 int : child's exitstatus
132 """
132 """
133 # Get likely encoding for the output.
133 # Get likely encoding for the output.
134 enc = DEFAULT_ENCODING
134 enc = DEFAULT_ENCODING
135
135
136 # Patterns to match on the output, for pexpect. We read input and
136 # Patterns to match on the output, for pexpect. We read input and
137 # allow either a short timeout or EOF
137 # allow either a short timeout or EOF
138 patterns = [pexpect.TIMEOUT, pexpect.EOF]
138 patterns = [pexpect.TIMEOUT, pexpect.EOF]
139 # the index of the EOF pattern in the list.
139 # the index of the EOF pattern in the list.
140 # even though we know it's 1, this call means we don't have to worry if
140 # even though we know it's 1, this call means we don't have to worry if
141 # we change the above list, and forget to change this value:
141 # we change the above list, and forget to change this value:
142 EOF_index = patterns.index(pexpect.EOF)
142 EOF_index = patterns.index(pexpect.EOF)
143 # The size of the output stored so far in the process output buffer.
143 # The size of the output stored so far in the process output buffer.
144 # Since pexpect only appends to this buffer, each time we print we
144 # Since pexpect only appends to this buffer, each time we print we
145 # record how far we've printed, so that next time we only print *new*
145 # record how far we've printed, so that next time we only print *new*
146 # content from the buffer.
146 # content from the buffer.
147 out_size = 0
147 out_size = 0
148 try:
148 try:
149 # Since we're not really searching the buffer for text patterns, we
149 # Since we're not really searching the buffer for text patterns, we
150 # can set pexpect's search window to be tiny and it won't matter.
150 # can set pexpect's search window to be tiny and it won't matter.
151 # We only search for the 'patterns' timeout or EOF, which aren't in
151 # We only search for the 'patterns' timeout or EOF, which aren't in
152 # the text itself.
152 # the text itself.
153 #child = pexpect.spawn(pcmd, searchwindowsize=1)
153 #child = pexpect.spawn(pcmd, searchwindowsize=1)
154 if hasattr(pexpect, 'spawnb'):
154 if hasattr(pexpect, 'spawnb'):
155 child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U
155 child = pexpect.spawnb(self.sh, args=['-c', cmd]) # Pexpect-U
156 else:
156 else:
157 child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect
157 child = pexpect.spawn(self.sh, args=['-c', cmd]) # Vanilla Pexpect
158 flush = sys.stdout.flush
158 flush = sys.stdout.flush
159 while True:
159 while True:
160 # res is the index of the pattern that caused the match, so we
160 # res is the index of the pattern that caused the match, so we
161 # know whether we've finished (if we matched EOF) or not
161 # know whether we've finished (if we matched EOF) or not
162 res_idx = child.expect_list(patterns, self.read_timeout)
162 res_idx = child.expect_list(patterns, self.read_timeout)
163 print(child.before[out_size:].decode(enc, 'replace'), end='')
163 print(child.before[out_size:].decode(enc, 'replace'), end='')
164 flush()
164 flush()
165 if res_idx==EOF_index:
165 if res_idx==EOF_index:
166 break
166 break
167 # Update the pointer to what we've already printed
167 # Update the pointer to what we've already printed
168 out_size = len(child.before)
168 out_size = len(child.before)
169 except KeyboardInterrupt:
169 except KeyboardInterrupt:
170 # We need to send ^C to the process. The ascii code for '^C' is 3
170 # We need to send ^C to the process. The ascii code for '^C' is 3
171 # (the character is known as ETX for 'End of Text', see
171 # (the character is known as ETX for 'End of Text', see
172 # curses.ascii.ETX).
172 # curses.ascii.ETX).
173 child.sendline(chr(3))
173 child.sendline(chr(3))
174 # Read and print any more output the program might produce on its
174 # Read and print any more output the program might produce on its
175 # way out.
175 # way out.
176 try:
176 try:
177 out_size = len(child.before)
177 out_size = len(child.before)
178 child.expect_list(patterns, self.terminate_timeout)
178 child.expect_list(patterns, self.terminate_timeout)
179 print(child.before[out_size:].decode(enc, 'replace'), end='')
179 print(child.before[out_size:].decode(enc, 'replace'), end='')
180 sys.stdout.flush()
180 sys.stdout.flush()
181 except KeyboardInterrupt:
181 except KeyboardInterrupt:
182 # Impatient users tend to type it multiple times
182 # Impatient users tend to type it multiple times
183 pass
183 pass
184 finally:
184 finally:
185 # Ensure the subprocess really is terminated
185 # Ensure the subprocess really is terminated
186 child.terminate(force=True)
186 child.terminate(force=True)
187 # add isalive check, to ensure exitstatus is set:
187 # add isalive check, to ensure exitstatus is set:
188 child.isalive()
188 child.isalive()
189
189
190 # We follow the subprocess pattern, returning either the exit status
190 # We follow the subprocess pattern, returning either the exit status
191 # as a positive number, or the terminating signal as a negative
191 # as a positive number, or the terminating signal as a negative
192 # number.
192 # number.
193 # on Linux, sh returns 128+n for signals terminating child processes on Linux
193 # on Linux, sh returns 128+n for signals terminating child processes on Linux
194 # on BSD (OS X), the signal code is set instead
194 # on BSD (OS X), the signal code is set instead
195 if child.exitstatus is None:
195 if child.exitstatus is None:
196 # on WIFSIGNALED, pexpect sets signalstatus, leaving exitstatus=None
196 # on WIFSIGNALED, pexpect sets signalstatus, leaving exitstatus=None
197 if child.signalstatus is None:
197 if child.signalstatus is None:
198 # this condition may never occur,
198 # this condition may never occur,
199 # but let's be certain we always return an integer.
199 # but let's be certain we always return an integer.
200 return 0
200 return 0
201 return -child.signalstatus
201 return -child.signalstatus
202 if child.exitstatus > 128:
202 if child.exitstatus > 128:
203 return -(child.exitstatus - 128)
203 return -(child.exitstatus - 128)
204 return child.exitstatus
204 return child.exitstatus
205
205
206
206
207 # Make system() with a functional interface for outside use. Note that we use
207 # Make system() with a functional interface for outside use. Note that we use
208 # getoutput() from the _common utils, which is built on top of popen(). Using
208 # getoutput() from the _common utils, which is built on top of popen(). Using
209 # pexpect to get subprocess output produces difficult to parse output, since
209 # pexpect to get subprocess output produces difficult to parse output, since
210 # programs think they are talking to a tty and produce highly formatted output
210 # programs think they are talking to a tty and produce highly formatted output
211 # (ls is a good example) that makes them hard.
211 # (ls is a good example) that makes them hard.
212 system = ProcessHandler().system
212 system = ProcessHandler().system
213
213
214 def check_pid(pid):
214 def check_pid(pid):
215 try:
215 try:
216 os.kill(pid, 0)
216 os.kill(pid, 0)
217 except OSError as err:
217 except OSError as err:
218 if err.errno == errno.ESRCH:
218 if err.errno == errno.ESRCH:
219 return False
219 return False
220 elif err.errno == errno.EPERM:
220 elif err.errno == errno.EPERM:
221 # Don't have permission to signal the process - probably means it exists
221 # Don't have permission to signal the process - probably means it exists
222 return True
222 return True
223 raise
223 raise
224 else:
224 else:
225 return True
225 return True
@@ -1,346 +1,349 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 platform-specific dependencies
277 if sys.platform == 'darwin':
277 if sys.platform == 'darwin':
278 install_requires.append('appnope')
278 install_requires.append('appnope')
279 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():
280 install_requires.append('gnureadline')
280 install_requires.append('gnureadline')
281 elif sys.platform.startswith('win'):
281
282 if sys.platform.startswith('win'):
282 extras_require['terminal'].append('pyreadline>=2.0')
283 extras_require['terminal'].append('pyreadline>=2.0')
284 else:
285 install_requires.append('pexpect')
283
286
284 everything = set()
287 everything = set()
285 for deps in extras_require.values():
288 for deps in extras_require.values():
286 everything.update(deps)
289 everything.update(deps)
287 extras_require['all'] = everything
290 extras_require['all'] = everything
288
291
289 if 'setuptools' in sys.modules:
292 if 'setuptools' in sys.modules:
290 # setup.py develop should check for submodules
293 # setup.py develop should check for submodules
291 from setuptools.command.develop import develop
294 from setuptools.command.develop import develop
292 setup_args['cmdclass']['develop'] = require_submodules(develop)
295 setup_args['cmdclass']['develop'] = require_submodules(develop)
293 setup_args['cmdclass']['bdist_wheel'] = css_js_prerelease(get_bdist_wheel())
296 setup_args['cmdclass']['bdist_wheel'] = css_js_prerelease(get_bdist_wheel())
294
297
295 setuptools_extra_args['zip_safe'] = False
298 setuptools_extra_args['zip_safe'] = False
296 setuptools_extra_args['entry_points'] = {
299 setuptools_extra_args['entry_points'] = {
297 'console_scripts': find_entry_points(),
300 'console_scripts': find_entry_points(),
298 'pygments.lexers': [
301 'pygments.lexers': [
299 'ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer',
302 'ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer',
300 'ipython = IPython.lib.lexers:IPythonLexer',
303 'ipython = IPython.lib.lexers:IPythonLexer',
301 'ipython3 = IPython.lib.lexers:IPython3Lexer',
304 'ipython3 = IPython.lib.lexers:IPython3Lexer',
302 ],
305 ],
303 }
306 }
304 setup_args['extras_require'] = extras_require
307 setup_args['extras_require'] = extras_require
305 requires = setup_args['install_requires'] = install_requires
308 requires = setup_args['install_requires'] = install_requires
306
309
307 # Script to be run by the windows binary installer after the default setup
310 # Script to be run by the windows binary installer after the default setup
308 # routine, to add shortcuts and similar windows-only things. Windows
311 # routine, to add shortcuts and similar windows-only things. Windows
309 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
312 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
310 # doesn't find them.
313 # doesn't find them.
311 if 'bdist_wininst' in sys.argv:
314 if 'bdist_wininst' in sys.argv:
312 if len(sys.argv) > 2 and \
315 if len(sys.argv) > 2 and \
313 ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
316 ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
314 print("ERROR: bdist_wininst must be run alone. Exiting.", file=sys.stderr)
317 print("ERROR: bdist_wininst must be run alone. Exiting.", file=sys.stderr)
315 sys.exit(1)
318 sys.exit(1)
316 setup_args['data_files'].append(
319 setup_args['data_files'].append(
317 ['Scripts', ('scripts/ipython.ico', 'scripts/ipython_nb.ico')])
320 ['Scripts', ('scripts/ipython.ico', 'scripts/ipython_nb.ico')])
318 setup_args['scripts'] = [pjoin('scripts','ipython_win_post_install.py')]
321 setup_args['scripts'] = [pjoin('scripts','ipython_win_post_install.py')]
319 setup_args['options'] = {"bdist_wininst":
322 setup_args['options'] = {"bdist_wininst":
320 {"install_script":
323 {"install_script":
321 "ipython_win_post_install.py"}}
324 "ipython_win_post_install.py"}}
322
325
323 else:
326 else:
324 # If we are installing without setuptools, call this function which will
327 # If we are installing without setuptools, call this function which will
325 # check for dependencies an inform the user what is needed. This is
328 # check for dependencies an inform the user what is needed. This is
326 # just to make life easy for users.
329 # just to make life easy for users.
327 for install_cmd in ('install', 'symlink'):
330 for install_cmd in ('install', 'symlink'):
328 if install_cmd in sys.argv:
331 if install_cmd in sys.argv:
329 check_for_dependencies()
332 check_for_dependencies()
330 break
333 break
331 # scripts has to be a non-empty list, or install_scripts isn't called
334 # scripts has to be a non-empty list, or install_scripts isn't called
332 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
335 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
333
336
334 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
337 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
335
338
336 #---------------------------------------------------------------------------
339 #---------------------------------------------------------------------------
337 # Do the actual setup now
340 # Do the actual setup now
338 #---------------------------------------------------------------------------
341 #---------------------------------------------------------------------------
339
342
340 setup_args.update(setuptools_extra_args)
343 setup_args.update(setuptools_extra_args)
341
344
342 def main():
345 def main():
343 setup(**setup_args)
346 setup(**setup_args)
344
347
345 if __name__ == '__main__':
348 if __name__ == '__main__':
346 main()
349 main()
@@ -1,769 +1,770 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", "appnope", "terminado"):
673 for pkg in ("gnureadline", "pyreadline", "mock", "terminado", "appnope", "pexpect"):
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 requires.append("appnope; sys.platform == 'darwin'")
682 requires.append("pexpect; sys.platform != 'win32'")
682 for r in requires:
683 for r in requires:
683 pkg_info['Requires-Dist'] = r
684 pkg_info['Requires-Dist'] = r
684 write_pkg_info(metadata_path, pkg_info)
685 write_pkg_info(metadata_path, pkg_info)
685
686
686 return bdist_wheel_tag
687 return bdist_wheel_tag
687
688
688 #---------------------------------------------------------------------------
689 #---------------------------------------------------------------------------
689 # Notebook related
690 # Notebook related
690 #---------------------------------------------------------------------------
691 #---------------------------------------------------------------------------
691
692
692 class CompileCSS(Command):
693 class CompileCSS(Command):
693 """Recompile Notebook CSS
694 """Recompile Notebook CSS
694
695
695 Regenerate the compiled CSS from LESS sources.
696 Regenerate the compiled CSS from LESS sources.
696
697
697 Requires various dev dependencies, such as invoke and lessc.
698 Requires various dev dependencies, such as invoke and lessc.
698 """
699 """
699 description = "Recompile Notebook CSS"
700 description = "Recompile Notebook CSS"
700 user_options = [
701 user_options = [
701 ('minify', 'x', "minify CSS"),
702 ('minify', 'x', "minify CSS"),
702 ('force', 'f', "force recompilation of CSS"),
703 ('force', 'f', "force recompilation of CSS"),
703 ]
704 ]
704
705
705 def initialize_options(self):
706 def initialize_options(self):
706 self.minify = False
707 self.minify = False
707 self.force = False
708 self.force = False
708
709
709 def finalize_options(self):
710 def finalize_options(self):
710 self.minify = bool(self.minify)
711 self.minify = bool(self.minify)
711 self.force = bool(self.force)
712 self.force = bool(self.force)
712
713
713 def run(self):
714 def run(self):
714 cmd = ['invoke', 'css']
715 cmd = ['invoke', 'css']
715 if self.minify:
716 if self.minify:
716 cmd.append('--minify')
717 cmd.append('--minify')
717 if self.force:
718 if self.force:
718 cmd.append('--force')
719 cmd.append('--force')
719 try:
720 try:
720 p = Popen(cmd, cwd=pjoin(repo_root, "IPython", "html"), stderr=PIPE)
721 p = Popen(cmd, cwd=pjoin(repo_root, "IPython", "html"), stderr=PIPE)
721 except OSError:
722 except OSError:
722 raise DistutilsExecError("invoke is required to rebuild css (pip install invoke)")
723 raise DistutilsExecError("invoke is required to rebuild css (pip install invoke)")
723 out, err = p.communicate()
724 out, err = p.communicate()
724 if p.returncode:
725 if p.returncode:
725 if sys.version_info[0] >= 3:
726 if sys.version_info[0] >= 3:
726 err = err.decode('utf8', 'replace')
727 err = err.decode('utf8', 'replace')
727 raise DistutilsExecError(err.strip())
728 raise DistutilsExecError(err.strip())
728
729
729
730
730 class JavascriptVersion(Command):
731 class JavascriptVersion(Command):
731 """write the javascript version to notebook javascript"""
732 """write the javascript version to notebook javascript"""
732 description = "Write IPython version to javascript"
733 description = "Write IPython version to javascript"
733 user_options = []
734 user_options = []
734
735
735 def initialize_options(self):
736 def initialize_options(self):
736 pass
737 pass
737
738
738 def finalize_options(self):
739 def finalize_options(self):
739 pass
740 pass
740
741
741 def run(self):
742 def run(self):
742 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
743 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
743 with open(nsfile) as f:
744 with open(nsfile) as f:
744 lines = f.readlines()
745 lines = f.readlines()
745 with open(nsfile, 'w') as f:
746 with open(nsfile, 'w') as f:
746 found = False
747 found = False
747 for line in lines:
748 for line in lines:
748 if line.strip().startswith("IPython.version"):
749 if line.strip().startswith("IPython.version"):
749 line = ' IPython.version = "{0}";\n'.format(version)
750 line = ' IPython.version = "{0}";\n'.format(version)
750 found = True
751 found = True
751 f.write(line)
752 f.write(line)
752 if not found:
753 if not found:
753 raise RuntimeError("Didn't find IPython.version line in %s" % nsfile)
754 raise RuntimeError("Didn't find IPython.version line in %s" % nsfile)
754
755
755
756
756 def css_js_prerelease(command):
757 def css_js_prerelease(command):
757 """decorator for building js/minified css prior to a release"""
758 """decorator for building js/minified css prior to a release"""
758 class DecoratedCommand(command):
759 class DecoratedCommand(command):
759 def run(self):
760 def run(self):
760 self.distribution.run_command('jsversion')
761 self.distribution.run_command('jsversion')
761 css = self.distribution.get_command_obj('css')
762 css = self.distribution.get_command_obj('css')
762 css.minify = True
763 css.minify = True
763 try:
764 try:
764 self.distribution.run_command('css')
765 self.distribution.run_command('css')
765 except Exception as e:
766 except Exception as e:
766 log.warn("rebuilding css and sourcemaps failed (not a problem)")
767 log.warn("rebuilding css and sourcemaps failed (not a problem)")
767 log.warn(str(e))
768 log.warn(str(e))
768 command.run(self)
769 command.run(self)
769 return DecoratedCommand
770 return DecoratedCommand
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (2123 lines changed) Show them Hide them
General Comments 0
You need to be logged in to leave comments. Login now