##// END OF EJS Templates
use invoke instead of fabric...
MinRK -
Show More
@@ -1,52 +1,53 b''
1 # Installs IPython from the current branch
1 # Installs IPython from the current branch
2 # Another Docker container should build from this one to get services like the notebook
2 # Another Docker container should build from this one to get services like the notebook
3
3
4 FROM ubuntu:14.04
4 FROM ubuntu:14.04
5
5
6 MAINTAINER IPython Project <ipython-dev@scipy.org>
6 MAINTAINER IPython Project <ipython-dev@scipy.org>
7
7
8 ENV DEBIAN_FRONTEND noninteractive
8 ENV DEBIAN_FRONTEND noninteractive
9
9
10 # Make sure apt is up to date
10 # Make sure apt is up to date
11 RUN apt-get update
11 RUN apt-get update
12 RUN apt-get upgrade -y
12 RUN apt-get upgrade -y
13
13
14 # Not essential, but wise to set the lang
14 # Not essential, but wise to set the lang
15 # Note: Users with other languages should set this in their derivative image
15 # Note: Users with other languages should set this in their derivative image
16 RUN apt-get install -y language-pack-en
16 RUN apt-get install -y language-pack-en
17 ENV LANGUAGE en_US.UTF-8
17 ENV LANGUAGE en_US.UTF-8
18 ENV LANG en_US.UTF-8
18 ENV LANG en_US.UTF-8
19 ENV LC_ALL en_US.UTF-8
19 ENV LC_ALL en_US.UTF-8
20
20
21 RUN locale-gen en_US.UTF-8
21 RUN locale-gen en_US.UTF-8
22 RUN dpkg-reconfigure locales
22 RUN dpkg-reconfigure locales
23
23
24 # Python binary dependencies, developer tools
24 # Python binary dependencies, developer tools
25 RUN apt-get install -y -q build-essential make gcc zlib1g-dev git && \
25 RUN apt-get install -y -q build-essential make gcc zlib1g-dev git && \
26 apt-get install -y -q python python-dev python-pip python3-dev python3-pip && \
26 apt-get install -y -q python python-dev python-pip python3-dev python3-pip && \
27 apt-get install -y -q libzmq3-dev sqlite3 libsqlite3-dev pandoc libcurl4-openssl-dev nodejs nodejs-legacy npm
27 apt-get install -y -q libzmq3-dev sqlite3 libsqlite3-dev pandoc libcurl4-openssl-dev nodejs nodejs-legacy npm
28
28
29 # In order to build from source, need less
29 # In order to build from source, need less
30 RUN npm install -g less
30 RUN npm install -g less
31
31
32 RUN apt-get install -y -q fabric python-sphinx python3-sphinx
32 RUN apt-get install -y -q python-sphinx python3-sphinx
33 RUN pip install invoke
33
34
34 RUN mkdir -p /srv/
35 RUN mkdir -p /srv/
35 WORKDIR /srv/
36 WORKDIR /srv/
36 ADD . /srv/ipython
37 ADD . /srv/ipython
37 WORKDIR /srv/ipython/
38 WORKDIR /srv/ipython/
38 RUN chmod -R +rX /srv/ipython
39 RUN chmod -R +rX /srv/ipython
39
40
40 # .[all] only works with -e, so use file://path#egg
41 # .[all] only works with -e, so use file://path#egg
41 # Can't use -e because ipython2 and ipython3 will clobber each other
42 # Can't use -e because ipython2 and ipython3 will clobber each other
42 RUN pip2 install file:///srv/ipython#egg=ipython[all]
43 RUN pip2 install file:///srv/ipython#egg=ipython[all]
43 RUN pip3 install file:///srv/ipython#egg=ipython[all]
44 RUN pip3 install file:///srv/ipython#egg=ipython[all]
44
45
45 # install kernels
46 # install kernels
46 RUN python2 -m IPython kernelspec install-self --system
47 RUN python2 -m IPython kernelspec install-self --system
47 RUN python3 -m IPython kernelspec install-self --system
48 RUN python3 -m IPython kernelspec install-self --system
48
49
49 WORKDIR /tmp/
50 WORKDIR /tmp/
50
51
51 RUN iptest2
52 RUN iptest2
52 RUN iptest3
53 RUN iptest3
@@ -1,72 +1,70 b''
1 # IPython Notebook development
1 # IPython Notebook development
2
2
3 ## Development dependencies
3 ## Development dependencies
4
4
5 Developers of the IPython Notebook will need to install the following tools:
5 Developers of the IPython Notebook will need to install the following tools:
6
6
7 * fabric
7 * invoke
8 * node.js
8 * node.js
9 * less (`npm install -g less`)
9 * less (`npm install -g less`)
10 * bower (`npm install -g bower`)
11
10
12 ## Components
11 ## Components
13
12
14 We are moving to a model where our JavaScript dependencies are managed using
13 We are moving to a model where our JavaScript dependencies are managed using
15 [bower](http://bower.io/). These packages are installed in `static/components`
14 [bower](http://bower.io/). These packages are installed in `static/components`
16 and committed into a separate git repo [ipython/ipython-components](ipython/ipython-components).
15 and committed into a separate git repo [ipython/ipython-components](ipython/ipython-components).
17 Our dependencies are described in the file
16 Our dependencies are described in the file
18 `static/components/bower.json`. To update our bower packages, run `fab update`
17 `static/components/bower.json`. To update our bower packages, run `bower install`
19 in this directory.
18 in this directory.
20
19
21 ## less
20 ## less
22
21
23 If you edit our `.less` files you will need to run the less compiler to build
22 If you edit our `.less` files you will need to run the less compiler to build
24 our minified css files. This can be done by running `fab css` from this directory,
23 our minified css files. This can be done by running `python setup.py css` from the root of the repository.
25 or `python setup.py css` from the root of the repository.
26 If you are working frequently with `.less` files please consider installing git hooks that
24 If you are working frequently with `.less` files please consider installing git hooks that
27 rebuild the css files and corresponding maps in `${RepoRoot}/git-hooks/install-hooks.sh`.
25 rebuild the css files and corresponding maps in `${RepoRoot}/git-hooks/install-hooks.sh`.
28
26
29 ## JavaScript Documentation
27 ## JavaScript Documentation
30
28
31
29
32 How to Build/ view the doc for JavaScript. JavaScript documentation should follow a
30 How to Build/ view the doc for JavaScript. JavaScript documentation should follow a
33 style close to JSDoc one, so you should be able to build them with your favorite
31 style close to JSDoc one, so you should be able to build them with your favorite
34 documentation builder. Still the documentation comment are mainly written to be read
32 documentation builder. Still the documentation comment are mainly written to be read
35 with YUI doc. You can either build a static version, or start a YUIdoc server that
33 with YUI doc. You can either build a static version, or start a YUIdoc server that
36 will live update the doc at every page request.
34 will live update the doc at every page request.
37
35
38
36
39
37
40 To do so, you will need to install YUIdoc.
38 To do so, you will need to install YUIdoc.
41
39
42 ### Install NodeJS
40 ### Install NodeJS
43
41
44 Node is a browser less javascript interpreter. To install it please refer to
42 Node is a browser less javascript interpreter. To install it please refer to
45 the documentation for your platform. Install also NPM (node package manager) if
43 the documentation for your platform. Install also NPM (node package manager) if
46 it does not come bundled with it.
44 it does not come bundled with it.
47
45
48 ### Get YUIdoc
46 ### Get YUIdoc
49
47
50 npm does by default install package in `./node_modules` instead of doing a
48 npm does by default install package in `./node_modules` instead of doing a
51 system wide install. I'll leave you to yuidoc docs if you want to make a system
49 system wide install. I'll leave you to yuidoc docs if you want to make a system
52 wide install.
50 wide install.
53
51
54 First, cd into js directory :
52 First, cd into js directory :
55 ```bash
53 ```bash
56 cd IPython/html/static/js/
54 cd IPython/html/static/js/
57 # install yuidoc
55 # install yuidoc
58 npm install yuidocjs
56 npm install yuidocjs
59 ```
57 ```
60
58
61
59
62 ### Run YUIdoc server
60 ### Run YUIdoc server
63
61
64 From IPython/html/static/js/
62 From IPython/html/static/js/
65 ```bash
63 ```bash
66 # run yuidoc for install dir
64 # run yuidoc for install dir
67 ./node_modules/yuidocjs/lib/cli.js --server .
65 ./node_modules/yuidocjs/lib/cli.js --server .
68 ```
66 ```
69
67
70 Follow the instruction and the documentation should be available on localhost:3000
68 Follow the instruction and the documentation should be available on localhost:3000
71
69
72 Omitting `--server` will build a static version in the `out` folder by default.
70 Omitting `--server` will build a static version in the `out` folder by default.
@@ -1,85 +1,83 b''
1 """ fabfile to prepare the notebook """
1 """invoke task file to build CSS"""
2
2
3 from fabric.api import local,lcd
3 from invoke import task, run
4 from fabric.utils import abort
5 import os
4 import os
6 from distutils.version import LooseVersion as V
5 from distutils.version import LooseVersion as V
7 from subprocess import check_output
6 from subprocess import check_output
8
7
9 pjoin = os.path.join
8 pjoin = os.path.join
10 static_dir = 'static'
9 static_dir = 'static'
11 components_dir = pjoin(static_dir, 'components')
10 components_dir = pjoin(static_dir, 'components')
12 here = os.path.dirname(__file__)
11 here = os.path.dirname(__file__)
13
12
14 min_less_version = '1.7.0'
13 min_less_version = '1.7.0'
15 max_less_version = '1.7.5' # exclusive
14 max_less_version = '1.7.5' # exclusive
16
15
17 def _need_css_update():
16 def _need_css_update():
18 """Does less need to run?"""
17 """Does less need to run?"""
19
18
20 static_path = pjoin(here, static_dir)
19 static_path = pjoin(here, static_dir)
21 css_targets = [
20 css_targets = [
22 pjoin(static_path, 'style', '%s.min.css' % name)
21 pjoin(static_path, 'style', '%s.min.css' % name)
23 for name in ('style', 'ipython')
22 for name in ('style', 'ipython')
24 ]
23 ]
25 css_maps = [t + '.map' for t in css_targets]
24 css_maps = [t + '.map' for t in css_targets]
26 targets = css_targets + css_maps
25 targets = css_targets + css_maps
27 if not all(os.path.exists(t) for t in targets):
26 if not all(os.path.exists(t) for t in targets):
28 # some generated files don't exist
27 # some generated files don't exist
29 return True
28 return True
30 earliest_target = sorted(os.stat(t).st_mtime for t in targets)[0]
29 earliest_target = sorted(os.stat(t).st_mtime for t in targets)[0]
31
30
32 # check if any .less files are newer than the generated targets
31 # check if any .less files are newer than the generated targets
33 for (dirpath, dirnames, filenames) in os.walk(static_path):
32 for (dirpath, dirnames, filenames) in os.walk(static_path):
34 for f in filenames:
33 for f in filenames:
35 if f.endswith('.less'):
34 if f.endswith('.less'):
36 path = pjoin(static_path, dirpath, f)
35 path = pjoin(static_path, dirpath, f)
37 timestamp = os.stat(path).st_mtime
36 timestamp = os.stat(path).st_mtime
38 if timestamp > earliest_target:
37 if timestamp > earliest_target:
39 return True
38 return True
40
39
41 return False
40 return False
42
41
42 @task
43 def css(minify=False, verbose=False, force=False):
43 def css(minify=False, verbose=False, force=False):
44 """generate the css from less files"""
44 """generate the css from less files"""
45 minify = _to_bool(minify)
46 verbose = _to_bool(verbose)
47 force = _to_bool(force)
48 # minify implies force because it's not the default behavior
45 # minify implies force because it's not the default behavior
49 if not force and not minify and not _need_css_update():
46 if not force and not minify and not _need_css_update():
50 print("css up-to-date")
47 print("css up-to-date")
51 return
48 return
52
49
53 for name in ('style', 'ipython'):
50 for name in ('style', 'ipython'):
54 source = pjoin('style', "%s.less" % name)
51 source = pjoin('style', "%s.less" % name)
55 target = pjoin('style', "%s.min.css" % name)
52 target = pjoin('style', "%s.min.css" % name)
56 sourcemap = pjoin('style', "%s.min.css.map" % name)
53 sourcemap = pjoin('style', "%s.min.css.map" % name)
57 _compile_less(source, target, sourcemap, minify, verbose)
54 _compile_less(source, target, sourcemap, minify, verbose)
58
55
59 def _to_bool(b):
60 if not b in ['True', 'False', True, False]:
61 abort('boolean expected, got: %s' % b)
62 return (b in ['True', True])
63
64 def _compile_less(source, target, sourcemap, minify=True, verbose=False):
56 def _compile_less(source, target, sourcemap, minify=True, verbose=False):
65 """Compile a less file by source and target relative to static_dir"""
57 """Compile a less file by source and target relative to static_dir"""
66 min_flag = '-x' if minify is True else ''
58 min_flag = '-x' if minify else ''
67 ver_flag = '--verbose' if verbose is True else ''
59 ver_flag = '--verbose' if verbose else ''
68
60
69 # pin less to version number from above
61 # pin less to version number from above
70 try:
62 try:
71 out = check_output(['lessc', '--version'])
63 out = check_output(['lessc', '--version'])
72 except OSError as err:
64 except OSError as err:
73 raise ValueError("Unable to find lessc. Please install lessc >= %s and < %s " \
65 raise ValueError("Unable to find lessc. Please install lessc >= %s and < %s " \
74 % (min_less_version, max_less_version))
66 % (min_less_version, max_less_version))
75 out = out.decode('utf8', 'replace')
67 out = out.decode('utf8', 'replace')
76 less_version = out.split()[1]
68 less_version = out.split()[1]
77 if V(less_version) < V(min_less_version):
69 if V(less_version) < V(min_less_version):
78 raise ValueError("lessc too old: %s < %s. Use `$ npm install lesscss@X.Y.Z` to install a specific version of less" % (less_version, min_less_version))
70 raise ValueError("lessc too old: %s < %s. Use `$ npm install lesscss@X.Y.Z` to install a specific version of less" % (less_version, min_less_version))
79 if V(less_version) >= V(max_less_version):
71 if V(less_version) >= V(max_less_version):
80 raise ValueError("lessc too new: %s >= %s. Use `$ npm install lesscss@X.Y.Z` to install a specific version of less" % (less_version, max_less_version))
72 raise ValueError("lessc too new: %s >= %s. Use `$ npm install lesscss@X.Y.Z` to install a specific version of less" % (less_version, max_less_version))
81
73
82 static_path = pjoin(here, static_dir)
74 static_path = pjoin(here, static_dir)
83 with lcd(static_dir):
75 cwd = os.getcwd()
84 local('lessc {min_flag} {ver_flag} --source-map={sourcemap} --source-map-basepath={static_path} --source-map-rootpath="../" {source} {target}'.format(**locals()))
76 try:
77 os.chdir(static_dir)
78 run('lessc {min_flag} {ver_flag} --source-map={sourcemap} --source-map-basepath={static_path} --source-map-rootpath="../" {source} {target}'.format(**locals()),
79 echo=True,
80 )
81 finally:
82 os.chdir(cwd)
85
83
@@ -1,512 +1,512 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.importstring import import_item
38 from IPython.utils.importstring import import_item
39 from IPython.testing.plugin.ipdoctest import IPythonDoctest
39 from IPython.testing.plugin.ipdoctest import IPythonDoctest
40 from IPython.external.decorators import KnownFailure, knownfailureif
40 from IPython.external.decorators import KnownFailure, knownfailureif
41
41
42 pjoin = path.join
42 pjoin = path.join
43
43
44
44
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46 # Globals
46 # Globals
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48
48
49
49
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51 # Warnings control
51 # Warnings control
52 #-----------------------------------------------------------------------------
52 #-----------------------------------------------------------------------------
53
53
54 # Twisted generates annoying warnings with Python 2.6, as will do other code
54 # Twisted generates annoying warnings with Python 2.6, as will do other code
55 # that imports 'sets' as of today
55 # that imports 'sets' as of today
56 warnings.filterwarnings('ignore', 'the sets module is deprecated',
56 warnings.filterwarnings('ignore', 'the sets module is deprecated',
57 DeprecationWarning )
57 DeprecationWarning )
58
58
59 # This one also comes from Twisted
59 # This one also comes from Twisted
60 warnings.filterwarnings('ignore', 'the sha module is deprecated',
60 warnings.filterwarnings('ignore', 'the sha module is deprecated',
61 DeprecationWarning)
61 DeprecationWarning)
62
62
63 # Wx on Fedora11 spits these out
63 # Wx on Fedora11 spits these out
64 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
64 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
65 UserWarning)
65 UserWarning)
66
66
67 # ------------------------------------------------------------------------------
67 # ------------------------------------------------------------------------------
68 # Monkeypatch Xunit to count known failures as skipped.
68 # Monkeypatch Xunit to count known failures as skipped.
69 # ------------------------------------------------------------------------------
69 # ------------------------------------------------------------------------------
70 def monkeypatch_xunit():
70 def monkeypatch_xunit():
71 try:
71 try:
72 knownfailureif(True)(lambda: None)()
72 knownfailureif(True)(lambda: None)()
73 except Exception as e:
73 except Exception as e:
74 KnownFailureTest = type(e)
74 KnownFailureTest = type(e)
75
75
76 def addError(self, test, err, capt=None):
76 def addError(self, test, err, capt=None):
77 if issubclass(err[0], KnownFailureTest):
77 if issubclass(err[0], KnownFailureTest):
78 err = (SkipTest,) + err[1:]
78 err = (SkipTest,) + err[1:]
79 return self.orig_addError(test, err, capt)
79 return self.orig_addError(test, err, capt)
80
80
81 Xunit.orig_addError = Xunit.addError
81 Xunit.orig_addError = Xunit.addError
82 Xunit.addError = addError
82 Xunit.addError = addError
83
83
84 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
85 # Check which dependencies are installed and greater than minimum version.
85 # Check which dependencies are installed and greater than minimum version.
86 #-----------------------------------------------------------------------------
86 #-----------------------------------------------------------------------------
87 def extract_version(mod):
87 def extract_version(mod):
88 return mod.__version__
88 return mod.__version__
89
89
90 def test_for(item, min_version=None, callback=extract_version):
90 def test_for(item, min_version=None, callback=extract_version):
91 """Test to see if item is importable, and optionally check against a minimum
91 """Test to see if item is importable, and optionally check against a minimum
92 version.
92 version.
93
93
94 If min_version is given, the default behavior is to check against the
94 If min_version is given, the default behavior is to check against the
95 `__version__` attribute of the item, but specifying `callback` allows you to
95 `__version__` attribute of the item, but specifying `callback` allows you to
96 extract the value you are interested in. e.g::
96 extract the value you are interested in. e.g::
97
97
98 In [1]: import sys
98 In [1]: import sys
99
99
100 In [2]: from IPython.testing.iptest import test_for
100 In [2]: from IPython.testing.iptest import test_for
101
101
102 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
102 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
103 Out[3]: True
103 Out[3]: True
104
104
105 """
105 """
106 try:
106 try:
107 check = import_item(item)
107 check = import_item(item)
108 except (ImportError, RuntimeError):
108 except (ImportError, RuntimeError):
109 # GTK reports Runtime error if it can't be initialized even if it's
109 # GTK reports Runtime error if it can't be initialized even if it's
110 # importable.
110 # importable.
111 return False
111 return False
112 else:
112 else:
113 if min_version:
113 if min_version:
114 if callback:
114 if callback:
115 # extra processing step to get version to compare
115 # extra processing step to get version to compare
116 check = callback(check)
116 check = callback(check)
117
117
118 return check >= min_version
118 return check >= min_version
119 else:
119 else:
120 return True
120 return True
121
121
122 # Global dict where we can store information on what we have and what we don't
122 # Global dict where we can store information on what we have and what we don't
123 # have available at test run time
123 # have available at test run time
124 have = {}
124 have = {}
125
125
126 have['curses'] = test_for('_curses')
126 have['curses'] = test_for('_curses')
127 have['matplotlib'] = test_for('matplotlib')
127 have['matplotlib'] = test_for('matplotlib')
128 have['numpy'] = test_for('numpy')
128 have['numpy'] = test_for('numpy')
129 have['pexpect'] = test_for('IPython.external.pexpect')
129 have['pexpect'] = test_for('IPython.external.pexpect')
130 have['pymongo'] = test_for('pymongo')
130 have['pymongo'] = test_for('pymongo')
131 have['pygments'] = test_for('pygments')
131 have['pygments'] = test_for('pygments')
132 have['qt'] = test_for('IPython.external.qt')
132 have['qt'] = test_for('IPython.external.qt')
133 have['sqlite3'] = test_for('sqlite3')
133 have['sqlite3'] = test_for('sqlite3')
134 have['tornado'] = test_for('tornado.version_info', (3,1,0), callback=None)
134 have['tornado'] = test_for('tornado.version_info', (3,1,0), callback=None)
135 have['jinja2'] = test_for('jinja2')
135 have['jinja2'] = test_for('jinja2')
136 have['mistune'] = test_for('mistune')
136 have['mistune'] = test_for('mistune')
137 have['requests'] = test_for('requests')
137 have['requests'] = test_for('requests')
138 have['sphinx'] = test_for('sphinx')
138 have['sphinx'] = test_for('sphinx')
139 have['jsonschema'] = test_for('jsonschema')
139 have['jsonschema'] = test_for('jsonschema')
140 have['casperjs'] = is_cmd_found('casperjs')
140 have['casperjs'] = is_cmd_found('casperjs')
141 have['phantomjs'] = is_cmd_found('phantomjs')
141 have['phantomjs'] = is_cmd_found('phantomjs')
142 have['slimerjs'] = is_cmd_found('slimerjs')
142 have['slimerjs'] = is_cmd_found('slimerjs')
143
143
144 min_zmq = (2,1,11)
144 min_zmq = (2,1,11)
145
145
146 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
146 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
147
147
148 #-----------------------------------------------------------------------------
148 #-----------------------------------------------------------------------------
149 # Test suite definitions
149 # Test suite definitions
150 #-----------------------------------------------------------------------------
150 #-----------------------------------------------------------------------------
151
151
152 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
152 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
153 'extensions', 'lib', 'terminal', 'testing', 'utils',
153 'extensions', 'lib', 'terminal', 'testing', 'utils',
154 'nbformat', 'qt', 'html', 'nbconvert'
154 'nbformat', 'qt', 'html', 'nbconvert'
155 ]
155 ]
156
156
157 class TestSection(object):
157 class TestSection(object):
158 def __init__(self, name, includes):
158 def __init__(self, name, includes):
159 self.name = name
159 self.name = name
160 self.includes = includes
160 self.includes = includes
161 self.excludes = []
161 self.excludes = []
162 self.dependencies = []
162 self.dependencies = []
163 self.enabled = True
163 self.enabled = True
164
164
165 def exclude(self, module):
165 def exclude(self, module):
166 if not module.startswith('IPython'):
166 if not module.startswith('IPython'):
167 module = self.includes[0] + "." + module
167 module = self.includes[0] + "." + module
168 self.excludes.append(module.replace('.', os.sep))
168 self.excludes.append(module.replace('.', os.sep))
169
169
170 def requires(self, *packages):
170 def requires(self, *packages):
171 self.dependencies.extend(packages)
171 self.dependencies.extend(packages)
172
172
173 @property
173 @property
174 def will_run(self):
174 def will_run(self):
175 return self.enabled and all(have[p] for p in self.dependencies)
175 return self.enabled and all(have[p] for p in self.dependencies)
176
176
177 # Name -> (include, exclude, dependencies_met)
177 # Name -> (include, exclude, dependencies_met)
178 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
178 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
179
179
180 # Exclusions and dependencies
180 # Exclusions and dependencies
181 # ---------------------------
181 # ---------------------------
182
182
183 # core:
183 # core:
184 sec = test_sections['core']
184 sec = test_sections['core']
185 if not have['sqlite3']:
185 if not have['sqlite3']:
186 sec.exclude('tests.test_history')
186 sec.exclude('tests.test_history')
187 sec.exclude('history')
187 sec.exclude('history')
188 if not have['matplotlib']:
188 if not have['matplotlib']:
189 sec.exclude('pylabtools'),
189 sec.exclude('pylabtools'),
190 sec.exclude('tests.test_pylabtools')
190 sec.exclude('tests.test_pylabtools')
191
191
192 # lib:
192 # lib:
193 sec = test_sections['lib']
193 sec = test_sections['lib']
194 if not have['zmq']:
194 if not have['zmq']:
195 sec.exclude('kernel')
195 sec.exclude('kernel')
196 # We do this unconditionally, so that the test suite doesn't import
196 # We do this unconditionally, so that the test suite doesn't import
197 # gtk, changing the default encoding and masking some unicode bugs.
197 # gtk, changing the default encoding and masking some unicode bugs.
198 sec.exclude('inputhookgtk')
198 sec.exclude('inputhookgtk')
199 # We also do this unconditionally, because wx can interfere with Unix signals.
199 # We also do this unconditionally, because wx can interfere with Unix signals.
200 # There are currently no tests for it anyway.
200 # There are currently no tests for it anyway.
201 sec.exclude('inputhookwx')
201 sec.exclude('inputhookwx')
202 # Testing inputhook will need a lot of thought, to figure out
202 # Testing inputhook will need a lot of thought, to figure out
203 # how to have tests that don't lock up with the gui event
203 # how to have tests that don't lock up with the gui event
204 # loops in the picture
204 # loops in the picture
205 sec.exclude('inputhook')
205 sec.exclude('inputhook')
206
206
207 # testing:
207 # testing:
208 sec = test_sections['testing']
208 sec = test_sections['testing']
209 # These have to be skipped on win32 because they use echo, rm, cd, etc.
209 # These have to be skipped on win32 because they use echo, rm, cd, etc.
210 # See ticket https://github.com/ipython/ipython/issues/87
210 # See ticket https://github.com/ipython/ipython/issues/87
211 if sys.platform == 'win32':
211 if sys.platform == 'win32':
212 sec.exclude('plugin.test_exampleip')
212 sec.exclude('plugin.test_exampleip')
213 sec.exclude('plugin.dtexample')
213 sec.exclude('plugin.dtexample')
214
214
215 # terminal:
215 # terminal:
216 if (not have['pexpect']) or (not have['zmq']):
216 if (not have['pexpect']) or (not have['zmq']):
217 test_sections['terminal'].exclude('console')
217 test_sections['terminal'].exclude('console')
218
218
219 # parallel
219 # parallel
220 sec = test_sections['parallel']
220 sec = test_sections['parallel']
221 sec.requires('zmq')
221 sec.requires('zmq')
222 if not have['pymongo']:
222 if not have['pymongo']:
223 sec.exclude('controller.mongodb')
223 sec.exclude('controller.mongodb')
224 sec.exclude('tests.test_mongodb')
224 sec.exclude('tests.test_mongodb')
225
225
226 # kernel:
226 # kernel:
227 sec = test_sections['kernel']
227 sec = test_sections['kernel']
228 sec.requires('zmq')
228 sec.requires('zmq')
229 # The in-process kernel tests are done in a separate section
229 # The in-process kernel tests are done in a separate section
230 sec.exclude('inprocess')
230 sec.exclude('inprocess')
231 # importing gtk sets the default encoding, which we want to avoid
231 # importing gtk sets the default encoding, which we want to avoid
232 sec.exclude('zmq.gui.gtkembed')
232 sec.exclude('zmq.gui.gtkembed')
233 sec.exclude('zmq.gui.gtk3embed')
233 sec.exclude('zmq.gui.gtk3embed')
234 if not have['matplotlib']:
234 if not have['matplotlib']:
235 sec.exclude('zmq.pylab')
235 sec.exclude('zmq.pylab')
236
236
237 # kernel.inprocess:
237 # kernel.inprocess:
238 test_sections['kernel.inprocess'].requires('zmq')
238 test_sections['kernel.inprocess'].requires('zmq')
239
239
240 # extensions:
240 # extensions:
241 sec = test_sections['extensions']
241 sec = test_sections['extensions']
242 # This is deprecated in favour of rpy2
242 # This is deprecated in favour of rpy2
243 sec.exclude('rmagic')
243 sec.exclude('rmagic')
244 # autoreload does some strange stuff, so move it to its own test section
244 # autoreload does some strange stuff, so move it to its own test section
245 sec.exclude('autoreload')
245 sec.exclude('autoreload')
246 sec.exclude('tests.test_autoreload')
246 sec.exclude('tests.test_autoreload')
247 test_sections['autoreload'] = TestSection('autoreload',
247 test_sections['autoreload'] = TestSection('autoreload',
248 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
248 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
249 test_group_names.append('autoreload')
249 test_group_names.append('autoreload')
250
250
251 # qt:
251 # qt:
252 test_sections['qt'].requires('zmq', 'qt', 'pygments')
252 test_sections['qt'].requires('zmq', 'qt', 'pygments')
253
253
254 # html:
254 # html:
255 sec = test_sections['html']
255 sec = test_sections['html']
256 sec.requires('zmq', 'tornado', 'requests', 'sqlite3', 'jsonschema')
256 sec.requires('zmq', 'tornado', 'requests', 'sqlite3', 'jsonschema')
257 # The notebook 'static' directory contains JS, css and other
257 # The notebook 'static' directory contains JS, css and other
258 # files for web serving. Occasionally projects may put a .py
258 # files for web serving. Occasionally projects may put a .py
259 # file in there (MathJax ships a conf.py), so we might as
259 # file in there (MathJax ships a conf.py), so we might as
260 # well play it safe and skip the whole thing.
260 # well play it safe and skip the whole thing.
261 sec.exclude('static')
261 sec.exclude('static')
262 sec.exclude('fabfile')
262 sec.exclude('tasks')
263 if not have['jinja2']:
263 if not have['jinja2']:
264 sec.exclude('notebookapp')
264 sec.exclude('notebookapp')
265 if not have['pygments'] or not have['jinja2']:
265 if not have['pygments'] or not have['jinja2']:
266 sec.exclude('nbconvert')
266 sec.exclude('nbconvert')
267
267
268 # config:
268 # config:
269 # Config files aren't really importable stand-alone
269 # Config files aren't really importable stand-alone
270 test_sections['config'].exclude('profile')
270 test_sections['config'].exclude('profile')
271
271
272 # nbconvert:
272 # nbconvert:
273 sec = test_sections['nbconvert']
273 sec = test_sections['nbconvert']
274 sec.requires('pygments', 'jinja2', 'jsonschema', 'mistune')
274 sec.requires('pygments', 'jinja2', 'jsonschema', 'mistune')
275 # Exclude nbconvert directories containing config files used to test.
275 # Exclude nbconvert directories containing config files used to test.
276 # Executing the config files with iptest would cause an exception.
276 # Executing the config files with iptest would cause an exception.
277 sec.exclude('tests.files')
277 sec.exclude('tests.files')
278 sec.exclude('exporters.tests.files')
278 sec.exclude('exporters.tests.files')
279 if not have['tornado']:
279 if not have['tornado']:
280 sec.exclude('nbconvert.post_processors.serve')
280 sec.exclude('nbconvert.post_processors.serve')
281 sec.exclude('nbconvert.post_processors.tests.test_serve')
281 sec.exclude('nbconvert.post_processors.tests.test_serve')
282
282
283 # nbformat:
283 # nbformat:
284 test_sections['nbformat'].requires('jsonschema')
284 test_sections['nbformat'].requires('jsonschema')
285
285
286 #-----------------------------------------------------------------------------
286 #-----------------------------------------------------------------------------
287 # Functions and classes
287 # Functions and classes
288 #-----------------------------------------------------------------------------
288 #-----------------------------------------------------------------------------
289
289
290 def check_exclusions_exist():
290 def check_exclusions_exist():
291 from IPython.utils.path import get_ipython_package_dir
291 from IPython.utils.path import get_ipython_package_dir
292 from IPython.utils.warn import warn
292 from IPython.utils.warn import warn
293 parent = os.path.dirname(get_ipython_package_dir())
293 parent = os.path.dirname(get_ipython_package_dir())
294 for sec in test_sections:
294 for sec in test_sections:
295 for pattern in sec.exclusions:
295 for pattern in sec.exclusions:
296 fullpath = pjoin(parent, pattern)
296 fullpath = pjoin(parent, pattern)
297 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
297 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
298 warn("Excluding nonexistent file: %r" % pattern)
298 warn("Excluding nonexistent file: %r" % pattern)
299
299
300
300
301 class ExclusionPlugin(Plugin):
301 class ExclusionPlugin(Plugin):
302 """A nose plugin to effect our exclusions of files and directories.
302 """A nose plugin to effect our exclusions of files and directories.
303 """
303 """
304 name = 'exclusions'
304 name = 'exclusions'
305 score = 3000 # Should come before any other plugins
305 score = 3000 # Should come before any other plugins
306
306
307 def __init__(self, exclude_patterns=None):
307 def __init__(self, exclude_patterns=None):
308 """
308 """
309 Parameters
309 Parameters
310 ----------
310 ----------
311
311
312 exclude_patterns : sequence of strings, optional
312 exclude_patterns : sequence of strings, optional
313 Filenames containing these patterns (as raw strings, not as regular
313 Filenames containing these patterns (as raw strings, not as regular
314 expressions) are excluded from the tests.
314 expressions) are excluded from the tests.
315 """
315 """
316 self.exclude_patterns = exclude_patterns or []
316 self.exclude_patterns = exclude_patterns or []
317 super(ExclusionPlugin, self).__init__()
317 super(ExclusionPlugin, self).__init__()
318
318
319 def options(self, parser, env=os.environ):
319 def options(self, parser, env=os.environ):
320 Plugin.options(self, parser, env)
320 Plugin.options(self, parser, env)
321
321
322 def configure(self, options, config):
322 def configure(self, options, config):
323 Plugin.configure(self, options, config)
323 Plugin.configure(self, options, config)
324 # Override nose trying to disable plugin.
324 # Override nose trying to disable plugin.
325 self.enabled = True
325 self.enabled = True
326
326
327 def wantFile(self, filename):
327 def wantFile(self, filename):
328 """Return whether the given filename should be scanned for tests.
328 """Return whether the given filename should be scanned for tests.
329 """
329 """
330 if any(pat in filename for pat in self.exclude_patterns):
330 if any(pat in filename for pat in self.exclude_patterns):
331 return False
331 return False
332 return None
332 return None
333
333
334 def wantDirectory(self, directory):
334 def wantDirectory(self, directory):
335 """Return whether the given directory should be scanned for tests.
335 """Return whether the given directory should be scanned for tests.
336 """
336 """
337 if any(pat in directory for pat in self.exclude_patterns):
337 if any(pat in directory for pat in self.exclude_patterns):
338 return False
338 return False
339 return None
339 return None
340
340
341
341
342 class StreamCapturer(Thread):
342 class StreamCapturer(Thread):
343 daemon = True # Don't hang if main thread crashes
343 daemon = True # Don't hang if main thread crashes
344 started = False
344 started = False
345 def __init__(self):
345 def __init__(self):
346 super(StreamCapturer, self).__init__()
346 super(StreamCapturer, self).__init__()
347 self.streams = []
347 self.streams = []
348 self.buffer = BytesIO()
348 self.buffer = BytesIO()
349 self.readfd, self.writefd = os.pipe()
349 self.readfd, self.writefd = os.pipe()
350 self.buffer_lock = Lock()
350 self.buffer_lock = Lock()
351 self.stop = Event()
351 self.stop = Event()
352
352
353 def run(self):
353 def run(self):
354 self.started = True
354 self.started = True
355
355
356 while not self.stop.is_set():
356 while not self.stop.is_set():
357 chunk = os.read(self.readfd, 1024)
357 chunk = os.read(self.readfd, 1024)
358
358
359 with self.buffer_lock:
359 with self.buffer_lock:
360 self.buffer.write(chunk)
360 self.buffer.write(chunk)
361
361
362 os.close(self.readfd)
362 os.close(self.readfd)
363 os.close(self.writefd)
363 os.close(self.writefd)
364
364
365 def reset_buffer(self):
365 def reset_buffer(self):
366 with self.buffer_lock:
366 with self.buffer_lock:
367 self.buffer.truncate(0)
367 self.buffer.truncate(0)
368 self.buffer.seek(0)
368 self.buffer.seek(0)
369
369
370 def get_buffer(self):
370 def get_buffer(self):
371 with self.buffer_lock:
371 with self.buffer_lock:
372 return self.buffer.getvalue()
372 return self.buffer.getvalue()
373
373
374 def ensure_started(self):
374 def ensure_started(self):
375 if not self.started:
375 if not self.started:
376 self.start()
376 self.start()
377
377
378 def halt(self):
378 def halt(self):
379 """Safely stop the thread."""
379 """Safely stop the thread."""
380 if not self.started:
380 if not self.started:
381 return
381 return
382
382
383 self.stop.set()
383 self.stop.set()
384 os.write(self.writefd, b'wake up') # Ensure we're not locked in a read()
384 os.write(self.writefd, b'wake up') # Ensure we're not locked in a read()
385 self.join()
385 self.join()
386
386
387 class SubprocessStreamCapturePlugin(Plugin):
387 class SubprocessStreamCapturePlugin(Plugin):
388 name='subprocstreams'
388 name='subprocstreams'
389 def __init__(self):
389 def __init__(self):
390 Plugin.__init__(self)
390 Plugin.__init__(self)
391 self.stream_capturer = StreamCapturer()
391 self.stream_capturer = StreamCapturer()
392 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
392 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
393 # This is ugly, but distant parts of the test machinery need to be able
393 # This is ugly, but distant parts of the test machinery need to be able
394 # to redirect streams, so we make the object globally accessible.
394 # to redirect streams, so we make the object globally accessible.
395 nose.iptest_stdstreams_fileno = self.get_write_fileno
395 nose.iptest_stdstreams_fileno = self.get_write_fileno
396
396
397 def get_write_fileno(self):
397 def get_write_fileno(self):
398 if self.destination == 'capture':
398 if self.destination == 'capture':
399 self.stream_capturer.ensure_started()
399 self.stream_capturer.ensure_started()
400 return self.stream_capturer.writefd
400 return self.stream_capturer.writefd
401 elif self.destination == 'discard':
401 elif self.destination == 'discard':
402 return os.open(os.devnull, os.O_WRONLY)
402 return os.open(os.devnull, os.O_WRONLY)
403 else:
403 else:
404 return sys.__stdout__.fileno()
404 return sys.__stdout__.fileno()
405
405
406 def configure(self, options, config):
406 def configure(self, options, config):
407 Plugin.configure(self, options, config)
407 Plugin.configure(self, options, config)
408 # Override nose trying to disable plugin.
408 # Override nose trying to disable plugin.
409 if self.destination == 'capture':
409 if self.destination == 'capture':
410 self.enabled = True
410 self.enabled = True
411
411
412 def startTest(self, test):
412 def startTest(self, test):
413 # Reset log capture
413 # Reset log capture
414 self.stream_capturer.reset_buffer()
414 self.stream_capturer.reset_buffer()
415
415
416 def formatFailure(self, test, err):
416 def formatFailure(self, test, err):
417 # Show output
417 # Show output
418 ec, ev, tb = err
418 ec, ev, tb = err
419 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
419 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
420 if captured.strip():
420 if captured.strip():
421 ev = safe_str(ev)
421 ev = safe_str(ev)
422 out = [ev, '>> begin captured subprocess output <<',
422 out = [ev, '>> begin captured subprocess output <<',
423 captured,
423 captured,
424 '>> end captured subprocess output <<']
424 '>> end captured subprocess output <<']
425 return ec, '\n'.join(out), tb
425 return ec, '\n'.join(out), tb
426
426
427 return err
427 return err
428
428
429 formatError = formatFailure
429 formatError = formatFailure
430
430
431 def finalize(self, result):
431 def finalize(self, result):
432 self.stream_capturer.halt()
432 self.stream_capturer.halt()
433
433
434
434
435 def run_iptest():
435 def run_iptest():
436 """Run the IPython test suite using nose.
436 """Run the IPython test suite using nose.
437
437
438 This function is called when this script is **not** called with the form
438 This function is called when this script is **not** called with the form
439 `iptest all`. It simply calls nose with appropriate command line flags
439 `iptest all`. It simply calls nose with appropriate command line flags
440 and accepts all of the standard nose arguments.
440 and accepts all of the standard nose arguments.
441 """
441 """
442 # Apply our monkeypatch to Xunit
442 # Apply our monkeypatch to Xunit
443 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
443 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
444 monkeypatch_xunit()
444 monkeypatch_xunit()
445
445
446 warnings.filterwarnings('ignore',
446 warnings.filterwarnings('ignore',
447 'This will be removed soon. Use IPython.testing.util instead')
447 'This will be removed soon. Use IPython.testing.util instead')
448
448
449 arg1 = sys.argv[1]
449 arg1 = sys.argv[1]
450 if arg1 in test_sections:
450 if arg1 in test_sections:
451 section = test_sections[arg1]
451 section = test_sections[arg1]
452 sys.argv[1:2] = section.includes
452 sys.argv[1:2] = section.includes
453 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
453 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
454 section = test_sections[arg1[8:]]
454 section = test_sections[arg1[8:]]
455 sys.argv[1:2] = section.includes
455 sys.argv[1:2] = section.includes
456 else:
456 else:
457 section = TestSection(arg1, includes=[arg1])
457 section = TestSection(arg1, includes=[arg1])
458
458
459
459
460 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
460 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
461
461
462 '--with-ipdoctest',
462 '--with-ipdoctest',
463 '--ipdoctest-tests','--ipdoctest-extension=txt',
463 '--ipdoctest-tests','--ipdoctest-extension=txt',
464
464
465 # We add --exe because of setuptools' imbecility (it
465 # We add --exe because of setuptools' imbecility (it
466 # blindly does chmod +x on ALL files). Nose does the
466 # blindly does chmod +x on ALL files). Nose does the
467 # right thing and it tries to avoid executables,
467 # right thing and it tries to avoid executables,
468 # setuptools unfortunately forces our hand here. This
468 # setuptools unfortunately forces our hand here. This
469 # has been discussed on the distutils list and the
469 # has been discussed on the distutils list and the
470 # setuptools devs refuse to fix this problem!
470 # setuptools devs refuse to fix this problem!
471 '--exe',
471 '--exe',
472 ]
472 ]
473 if '-a' not in argv and '-A' not in argv:
473 if '-a' not in argv and '-A' not in argv:
474 argv = argv + ['-a', '!crash']
474 argv = argv + ['-a', '!crash']
475
475
476 if nose.__version__ >= '0.11':
476 if nose.__version__ >= '0.11':
477 # I don't fully understand why we need this one, but depending on what
477 # I don't fully understand why we need this one, but depending on what
478 # directory the test suite is run from, if we don't give it, 0 tests
478 # directory the test suite is run from, if we don't give it, 0 tests
479 # get run. Specifically, if the test suite is run from the source dir
479 # get run. Specifically, if the test suite is run from the source dir
480 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
480 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
481 # even if the same call done in this directory works fine). It appears
481 # even if the same call done in this directory works fine). It appears
482 # that if the requested package is in the current dir, nose bails early
482 # that if the requested package is in the current dir, nose bails early
483 # by default. Since it's otherwise harmless, leave it in by default
483 # by default. Since it's otherwise harmless, leave it in by default
484 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
484 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
485 argv.append('--traverse-namespace')
485 argv.append('--traverse-namespace')
486
486
487 # use our plugin for doctesting. It will remove the standard doctest plugin
487 # use our plugin for doctesting. It will remove the standard doctest plugin
488 # if it finds it enabled
488 # if it finds it enabled
489 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(),
489 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(),
490 SubprocessStreamCapturePlugin() ]
490 SubprocessStreamCapturePlugin() ]
491
491
492 # Use working directory set by parent process (see iptestcontroller)
492 # Use working directory set by parent process (see iptestcontroller)
493 if 'IPTEST_WORKING_DIR' in os.environ:
493 if 'IPTEST_WORKING_DIR' in os.environ:
494 os.chdir(os.environ['IPTEST_WORKING_DIR'])
494 os.chdir(os.environ['IPTEST_WORKING_DIR'])
495
495
496 # We need a global ipython running in this process, but the special
496 # We need a global ipython running in this process, but the special
497 # in-process group spawns its own IPython kernels, so for *that* group we
497 # in-process group spawns its own IPython kernels, so for *that* group we
498 # must avoid also opening the global one (otherwise there's a conflict of
498 # must avoid also opening the global one (otherwise there's a conflict of
499 # singletons). Ultimately the solution to this problem is to refactor our
499 # singletons). Ultimately the solution to this problem is to refactor our
500 # assumptions about what needs to be a singleton and what doesn't (app
500 # assumptions about what needs to be a singleton and what doesn't (app
501 # objects should, individual shells shouldn't). But for now, this
501 # objects should, individual shells shouldn't). But for now, this
502 # workaround allows the test suite for the inprocess module to complete.
502 # workaround allows the test suite for the inprocess module to complete.
503 if 'kernel.inprocess' not in section.name:
503 if 'kernel.inprocess' not in section.name:
504 from IPython.testing import globalipapp
504 from IPython.testing import globalipapp
505 globalipapp.start_ipython()
505 globalipapp.start_ipython()
506
506
507 # Now nose can run
507 # Now nose can run
508 TestProgram(argv=argv, addplugins=plugins)
508 TestProgram(argv=argv, addplugins=plugins)
509
509
510 if __name__ == '__main__':
510 if __name__ == '__main__':
511 run_iptest()
511 run_iptest()
512
512
@@ -1,68 +1,68 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 """Script to auto-generate our API docs.
2 """Script to auto-generate our API docs.
3 """
3 """
4 # stdlib imports
4 # stdlib imports
5 import os
5 import os
6 import sys
6 import sys
7
7
8 # local imports
8 # local imports
9 sys.path.append(os.path.abspath('sphinxext'))
9 sys.path.append(os.path.abspath('sphinxext'))
10 from apigen import ApiDocWriter
10 from apigen import ApiDocWriter
11
11
12 #*****************************************************************************
12 #*****************************************************************************
13 if __name__ == '__main__':
13 if __name__ == '__main__':
14 pjoin = os.path.join
14 pjoin = os.path.join
15 package = 'IPython'
15 package = 'IPython'
16 outdir = pjoin('source','api','generated')
16 outdir = pjoin('source','api','generated')
17 docwriter = ApiDocWriter(package,rst_extension='.rst')
17 docwriter = ApiDocWriter(package,rst_extension='.rst')
18 # You have to escape the . here because . is a special char for regexps.
18 # You have to escape the . here because . is a special char for regexps.
19 # You must do make clean if you change this!
19 # You must do make clean if you change this!
20 docwriter.package_skip_patterns += [r'\.external$',
20 docwriter.package_skip_patterns += [r'\.external$',
21 # Extensions are documented elsewhere.
21 # Extensions are documented elsewhere.
22 r'\.extensions',
22 r'\.extensions',
23 r'\.config\.profile',
23 r'\.config\.profile',
24 # These should be accessed via nbformat.current
24 # These should be accessed via nbformat.current
25 r'\.nbformat\.v\d+',
25 r'\.nbformat\.v\d+',
26 # Public API for this is in kernel.zmq.eventloops
26 # Public API for this is in kernel.zmq.eventloops
27 r'\.kernel\.zmq\.gui',
27 r'\.kernel\.zmq\.gui',
28 ]
28 ]
29
29
30 # The inputhook* modules often cause problems on import, such as trying to
30 # The inputhook* modules often cause problems on import, such as trying to
31 # load incompatible Qt bindings. It's easiest to leave them all out. The
31 # load incompatible Qt bindings. It's easiest to leave them all out. The
32 # main API is in the inputhook module, which is documented.
32 # main API is in the inputhook module, which is documented.
33 docwriter.module_skip_patterns += [ r'\.lib\.inputhook.+',
33 docwriter.module_skip_patterns += [ r'\.lib\.inputhook.+',
34 r'\.ipdoctest',
34 r'\.ipdoctest',
35 r'\.testing\.plugin',
35 r'\.testing\.plugin',
36 # This just prints a deprecation msg:
36 # This just prints a deprecation msg:
37 r'\.frontend$',
37 r'\.frontend$',
38 # Deprecated:
38 # Deprecated:
39 r'\.core\.magics\.deprecated',
39 r'\.core\.magics\.deprecated',
40 # We document this manually.
40 # We document this manually.
41 r'\.utils\.py3compat',
41 r'\.utils\.py3compat',
42 # These are exposed by nbformat.current
42 # These are exposed by nbformat.current
43 r'\.nbformat\.convert',
43 r'\.nbformat\.convert',
44 r'\.nbformat\.validator',
44 r'\.nbformat\.validator',
45 # These are exposed in display
45 # These are exposed in display
46 r'\.core\.display',
46 r'\.core\.display',
47 r'\.lib\.display',
47 r'\.lib\.display',
48 # This isn't actually a module
48 # This isn't actually a module
49 r'\.html\.fabfile',
49 r'\.html\.tasks',
50 ]
50 ]
51
51
52 # These modules import functions and classes from other places to expose
52 # These modules import functions and classes from other places to expose
53 # them as part of the public API. They must have __all__ defined. The
53 # them as part of the public API. They must have __all__ defined. The
54 # non-API modules they import from should be excluded by the skip patterns
54 # non-API modules they import from should be excluded by the skip patterns
55 # above.
55 # above.
56 docwriter.names_from__all__.update({
56 docwriter.names_from__all__.update({
57 'IPython.nbformat.current',
57 'IPython.nbformat.current',
58 'IPython.display',
58 'IPython.display',
59 })
59 })
60
60
61 # Now, generate the outputs
61 # Now, generate the outputs
62 docwriter.write_api_docs(outdir)
62 docwriter.write_api_docs(outdir)
63 # Write index with .txt extension - we can include it, but Sphinx won't try
63 # Write index with .txt extension - we can include it, but Sphinx won't try
64 # to compile it
64 # to compile it
65 docwriter.write_index(outdir, 'gen.txt',
65 docwriter.write_index(outdir, 'gen.txt',
66 relative_to = pjoin('source','api')
66 relative_to = pjoin('source','api')
67 )
67 )
68 print ('%d files written' % len(docwriter.written_modules))
68 print ('%d files written' % len(docwriter.written_modules))
@@ -1,22 +1,22 b''
1 #!/bin/bash
1 #!/bin/bash
2
2
3 git submodule init
3 git submodule init
4 git submodule update
4 git submodule update
5
5
6 if [[ "$(basename $0)" == "post-merge" ]]; then
6 if [[ "$(basename $0)" == "post-merge" ]]; then
7 PREVIOUS_HEAD=ORIG_HEAD
7 PREVIOUS_HEAD=ORIG_HEAD
8 else
8 else
9 PREVIOUS_HEAD=$1
9 PREVIOUS_HEAD=$1
10 fi
10 fi
11
11
12 # if style changed (and less/fabric available), rebuild sourcemaps
12 # if style changed (and less/invoke available), rebuild sourcemaps
13 if [[
13 if [[
14 ! -z "$(git diff $PREVIOUS_HEAD IPython/html/static/style/ipython.min.css)"
14 ! -z "$(git diff $PREVIOUS_HEAD IPython/html/static/style/ipython.min.css)"
15 && ! -z "$(git diff $PREVIOUS_HEAD IPython/html/static/style/style.min.css)"
15 && ! -z "$(git diff $PREVIOUS_HEAD IPython/html/static/style/style.min.css)"
16 && ! -z $(which 2>/dev/null lessc)
16 && ! -z $(which 2>/dev/null lessc)
17 && ! -z $(which 2>/dev/null fab)
17 && ! -z $(which 2>/dev/null invoke)
18 ]]; then
18 ]]; then
19 echo "rebuilding sourcemaps"
19 echo "rebuilding sourcemaps"
20 cd IPython/html
20 cd IPython/html
21 fab css || echo "failed to compile css"
21 fab css || echo "failed to compile css"
22 fi
22 fi
@@ -1,733 +1,734 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 fnmatch import fnmatch
27 from fnmatch import fnmatch
28 from glob import glob
28 from glob import glob
29 from subprocess import check_call
29 from subprocess import check_call
30
30
31 from setupext import install_data_ext
31 from setupext import install_data_ext
32
32
33 #-------------------------------------------------------------------------------
33 #-------------------------------------------------------------------------------
34 # Useful globals and utility functions
34 # Useful globals and utility functions
35 #-------------------------------------------------------------------------------
35 #-------------------------------------------------------------------------------
36
36
37 # A few handy globals
37 # A few handy globals
38 isfile = os.path.isfile
38 isfile = os.path.isfile
39 pjoin = os.path.join
39 pjoin = os.path.join
40 repo_root = os.path.dirname(os.path.abspath(__file__))
40 repo_root = os.path.dirname(os.path.abspath(__file__))
41
41
42 def oscmd(s):
42 def oscmd(s):
43 print(">", s)
43 print(">", s)
44 os.system(s)
44 os.system(s)
45
45
46 # Py3 compatibility hacks, without assuming IPython itself is installed with
46 # Py3 compatibility hacks, without assuming IPython itself is installed with
47 # the full py3compat machinery.
47 # the full py3compat machinery.
48
48
49 try:
49 try:
50 execfile
50 execfile
51 except NameError:
51 except NameError:
52 def execfile(fname, globs, locs=None):
52 def execfile(fname, globs, locs=None):
53 locs = locs or globs
53 locs = locs or globs
54 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
54 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
55
55
56 # A little utility we'll need below, since glob() does NOT allow you to do
56 # A little utility we'll need below, since glob() does NOT allow you to do
57 # exclusion on multiple endings!
57 # exclusion on multiple endings!
58 def file_doesnt_endwith(test,endings):
58 def file_doesnt_endwith(test,endings):
59 """Return true if test is a file and its name does NOT end with any
59 """Return true if test is a file and its name does NOT end with any
60 of the strings listed in endings."""
60 of the strings listed in endings."""
61 if not isfile(test):
61 if not isfile(test):
62 return False
62 return False
63 for e in endings:
63 for e in endings:
64 if test.endswith(e):
64 if test.endswith(e):
65 return False
65 return False
66 return True
66 return True
67
67
68 #---------------------------------------------------------------------------
68 #---------------------------------------------------------------------------
69 # Basic project information
69 # Basic project information
70 #---------------------------------------------------------------------------
70 #---------------------------------------------------------------------------
71
71
72 # release.py contains version, authors, license, url, keywords, etc.
72 # release.py contains version, authors, license, url, keywords, etc.
73 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
73 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
74
74
75 # Create a dict with the basic information
75 # Create a dict with the basic information
76 # This dict is eventually passed to setup after additional keys are added.
76 # This dict is eventually passed to setup after additional keys are added.
77 setup_args = dict(
77 setup_args = dict(
78 name = name,
78 name = name,
79 version = version,
79 version = version,
80 description = description,
80 description = description,
81 long_description = long_description,
81 long_description = long_description,
82 author = author,
82 author = author,
83 author_email = author_email,
83 author_email = author_email,
84 url = url,
84 url = url,
85 download_url = download_url,
85 download_url = download_url,
86 license = license,
86 license = license,
87 platforms = platforms,
87 platforms = platforms,
88 keywords = keywords,
88 keywords = keywords,
89 classifiers = classifiers,
89 classifiers = classifiers,
90 cmdclass = {'install_data': install_data_ext},
90 cmdclass = {'install_data': install_data_ext},
91 )
91 )
92
92
93
93
94 #---------------------------------------------------------------------------
94 #---------------------------------------------------------------------------
95 # Find packages
95 # Find packages
96 #---------------------------------------------------------------------------
96 #---------------------------------------------------------------------------
97
97
98 def find_packages():
98 def find_packages():
99 """
99 """
100 Find all of IPython's packages.
100 Find all of IPython's packages.
101 """
101 """
102 excludes = ['deathrow', 'quarantine']
102 excludes = ['deathrow', 'quarantine']
103 packages = []
103 packages = []
104 for dir,subdirs,files in os.walk('IPython'):
104 for dir,subdirs,files in os.walk('IPython'):
105 package = dir.replace(os.path.sep, '.')
105 package = dir.replace(os.path.sep, '.')
106 if any(package.startswith('IPython.'+exc) for exc in excludes):
106 if any(package.startswith('IPython.'+exc) for exc in excludes):
107 # package is to be excluded (e.g. deathrow)
107 # package is to be excluded (e.g. deathrow)
108 continue
108 continue
109 if '__init__.py' not in files:
109 if '__init__.py' not in files:
110 # not a package
110 # not a package
111 continue
111 continue
112 packages.append(package)
112 packages.append(package)
113 return packages
113 return packages
114
114
115 #---------------------------------------------------------------------------
115 #---------------------------------------------------------------------------
116 # Find package data
116 # Find package data
117 #---------------------------------------------------------------------------
117 #---------------------------------------------------------------------------
118
118
119 def find_package_data():
119 def find_package_data():
120 """
120 """
121 Find IPython's package_data.
121 Find IPython's package_data.
122 """
122 """
123 # This is not enough for these things to appear in an sdist.
123 # This is not enough for these things to appear in an sdist.
124 # We need to muck with the MANIFEST to get this to work
124 # We need to muck with the MANIFEST to get this to work
125
125
126 # exclude components and less from the walk;
126 # exclude components and less from the walk;
127 # we will build the components separately
127 # we will build the components separately
128 excludes = [
128 excludes = [
129 pjoin('static', 'components'),
129 pjoin('static', 'components'),
130 pjoin('static', '*', 'less'),
130 pjoin('static', '*', 'less'),
131 ]
131 ]
132
132
133 # walk notebook resources:
133 # walk notebook resources:
134 cwd = os.getcwd()
134 cwd = os.getcwd()
135 os.chdir(os.path.join('IPython', 'html'))
135 os.chdir(os.path.join('IPython', 'html'))
136 static_data = []
136 static_data = []
137 for parent, dirs, files in os.walk('static'):
137 for parent, dirs, files in os.walk('static'):
138 if any(fnmatch(parent, pat) for pat in excludes):
138 if any(fnmatch(parent, pat) for pat in excludes):
139 # prevent descending into subdirs
139 # prevent descending into subdirs
140 dirs[:] = []
140 dirs[:] = []
141 continue
141 continue
142 for f in files:
142 for f in files:
143 static_data.append(pjoin(parent, f))
143 static_data.append(pjoin(parent, f))
144
144
145 components = pjoin("static", "components")
145 components = pjoin("static", "components")
146 # select the components we actually need to install
146 # select the components we actually need to install
147 # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
147 # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
148 static_data.extend([
148 static_data.extend([
149 pjoin(components, "backbone", "backbone-min.js"),
149 pjoin(components, "backbone", "backbone-min.js"),
150 pjoin(components, "bootstrap", "js", "bootstrap.min.js"),
150 pjoin(components, "bootstrap", "js", "bootstrap.min.js"),
151 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
151 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
152 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
152 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
153 pjoin(components, "font-awesome", "fonts", "*.*"),
153 pjoin(components, "font-awesome", "fonts", "*.*"),
154 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
154 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
155 pjoin(components, "highlight.js", "build", "highlight.pack.js"),
155 pjoin(components, "highlight.js", "build", "highlight.pack.js"),
156 pjoin(components, "jquery", "jquery.min.js"),
156 pjoin(components, "jquery", "jquery.min.js"),
157 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
157 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
158 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
158 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
159 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
159 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
160 pjoin(components, "marked", "lib", "marked.js"),
160 pjoin(components, "marked", "lib", "marked.js"),
161 pjoin(components, "requirejs", "require.js"),
161 pjoin(components, "requirejs", "require.js"),
162 pjoin(components, "underscore", "underscore-min.js"),
162 pjoin(components, "underscore", "underscore-min.js"),
163 pjoin(components, "moment", "moment.js"),
163 pjoin(components, "moment", "moment.js"),
164 pjoin(components, "moment", "min","moment.min.js"),
164 pjoin(components, "moment", "min","moment.min.js"),
165 ])
165 ])
166
166
167 # Ship all of Codemirror's CSS and JS
167 # Ship all of Codemirror's CSS and JS
168 for parent, dirs, files in os.walk(pjoin(components, 'codemirror')):
168 for parent, dirs, files in os.walk(pjoin(components, 'codemirror')):
169 for f in files:
169 for f in files:
170 if f.endswith(('.js', '.css')):
170 if f.endswith(('.js', '.css')):
171 static_data.append(pjoin(parent, f))
171 static_data.append(pjoin(parent, f))
172
172
173 os.chdir(os.path.join('tests',))
173 os.chdir(os.path.join('tests',))
174 js_tests = glob('*.js') + glob('*/*.js')
174 js_tests = glob('*.js') + glob('*/*.js')
175
175
176 os.chdir(os.path.join(cwd, 'IPython', 'nbconvert'))
176 os.chdir(os.path.join(cwd, 'IPython', 'nbconvert'))
177 nbconvert_templates = [os.path.join(dirpath, '*.*')
177 nbconvert_templates = [os.path.join(dirpath, '*.*')
178 for dirpath, _, _ in os.walk('templates')]
178 for dirpath, _, _ in os.walk('templates')]
179
179
180 os.chdir(cwd)
180 os.chdir(cwd)
181
181
182 package_data = {
182 package_data = {
183 'IPython.config.profile' : ['README*', '*/*.py'],
183 'IPython.config.profile' : ['README*', '*/*.py'],
184 'IPython.core.tests' : ['*.png', '*.jpg'],
184 'IPython.core.tests' : ['*.png', '*.jpg'],
185 'IPython.lib.tests' : ['*.wav'],
185 'IPython.lib.tests' : ['*.wav'],
186 'IPython.testing.plugin' : ['*.txt'],
186 'IPython.testing.plugin' : ['*.txt'],
187 'IPython.html' : ['templates/*'] + static_data,
187 'IPython.html' : ['templates/*'] + static_data,
188 'IPython.html.tests' : js_tests,
188 'IPython.html.tests' : js_tests,
189 'IPython.qt.console' : ['resources/icon/*.svg'],
189 'IPython.qt.console' : ['resources/icon/*.svg'],
190 'IPython.nbconvert' : nbconvert_templates +
190 'IPython.nbconvert' : nbconvert_templates +
191 [
191 [
192 'tests/files/*.*',
192 'tests/files/*.*',
193 'exporters/tests/files/*.*',
193 'exporters/tests/files/*.*',
194 'preprocessors/tests/files/*.*',
194 'preprocessors/tests/files/*.*',
195 ],
195 ],
196 'IPython.nbconvert.filters' : ['marked.js'],
196 'IPython.nbconvert.filters' : ['marked.js'],
197 'IPython.nbformat' : [
197 'IPython.nbformat' : [
198 'tests/*.ipynb',
198 'tests/*.ipynb',
199 'v3/nbformat.v3.schema.json',
199 'v3/nbformat.v3.schema.json',
200 ]
200 ]
201 }
201 }
202
202
203 return package_data
203 return package_data
204
204
205
205
206 def check_package_data(package_data):
206 def check_package_data(package_data):
207 """verify that package_data globs make sense"""
207 """verify that package_data globs make sense"""
208 print("checking package data")
208 print("checking package data")
209 for pkg, data in package_data.items():
209 for pkg, data in package_data.items():
210 pkg_root = pjoin(*pkg.split('.'))
210 pkg_root = pjoin(*pkg.split('.'))
211 for d in data:
211 for d in data:
212 path = pjoin(pkg_root, d)
212 path = pjoin(pkg_root, d)
213 if '*' in path:
213 if '*' in path:
214 assert len(glob(path)) > 0, "No files match pattern %s" % path
214 assert len(glob(path)) > 0, "No files match pattern %s" % path
215 else:
215 else:
216 assert os.path.exists(path), "Missing package data: %s" % path
216 assert os.path.exists(path), "Missing package data: %s" % path
217
217
218
218
219 def check_package_data_first(command):
219 def check_package_data_first(command):
220 """decorator for checking package_data before running a given command
220 """decorator for checking package_data before running a given command
221
221
222 Probably only needs to wrap build_py
222 Probably only needs to wrap build_py
223 """
223 """
224 class DecoratedCommand(command):
224 class DecoratedCommand(command):
225 def run(self):
225 def run(self):
226 check_package_data(self.package_data)
226 check_package_data(self.package_data)
227 command.run(self)
227 command.run(self)
228 return DecoratedCommand
228 return DecoratedCommand
229
229
230
230
231 #---------------------------------------------------------------------------
231 #---------------------------------------------------------------------------
232 # Find data files
232 # Find data files
233 #---------------------------------------------------------------------------
233 #---------------------------------------------------------------------------
234
234
235 def make_dir_struct(tag,base,out_base):
235 def make_dir_struct(tag,base,out_base):
236 """Make the directory structure of all files below a starting dir.
236 """Make the directory structure of all files below a starting dir.
237
237
238 This is just a convenience routine to help build a nested directory
238 This is just a convenience routine to help build a nested directory
239 hierarchy because distutils is too stupid to do this by itself.
239 hierarchy because distutils is too stupid to do this by itself.
240
240
241 XXX - this needs a proper docstring!
241 XXX - this needs a proper docstring!
242 """
242 """
243
243
244 # we'll use these a lot below
244 # we'll use these a lot below
245 lbase = len(base)
245 lbase = len(base)
246 pathsep = os.path.sep
246 pathsep = os.path.sep
247 lpathsep = len(pathsep)
247 lpathsep = len(pathsep)
248
248
249 out = []
249 out = []
250 for (dirpath,dirnames,filenames) in os.walk(base):
250 for (dirpath,dirnames,filenames) in os.walk(base):
251 # we need to strip out the dirpath from the base to map it to the
251 # we need to strip out the dirpath from the base to map it to the
252 # output (installation) path. This requires possibly stripping the
252 # output (installation) path. This requires possibly stripping the
253 # path separator, because otherwise pjoin will not work correctly
253 # path separator, because otherwise pjoin will not work correctly
254 # (pjoin('foo/','/bar') returns '/bar').
254 # (pjoin('foo/','/bar') returns '/bar').
255
255
256 dp_eff = dirpath[lbase:]
256 dp_eff = dirpath[lbase:]
257 if dp_eff.startswith(pathsep):
257 if dp_eff.startswith(pathsep):
258 dp_eff = dp_eff[lpathsep:]
258 dp_eff = dp_eff[lpathsep:]
259 # The output path must be anchored at the out_base marker
259 # The output path must be anchored at the out_base marker
260 out_path = pjoin(out_base,dp_eff)
260 out_path = pjoin(out_base,dp_eff)
261 # Now we can generate the final filenames. Since os.walk only produces
261 # Now we can generate the final filenames. Since os.walk only produces
262 # filenames, we must join back with the dirpath to get full valid file
262 # filenames, we must join back with the dirpath to get full valid file
263 # paths:
263 # paths:
264 pfiles = [pjoin(dirpath,f) for f in filenames]
264 pfiles = [pjoin(dirpath,f) for f in filenames]
265 # Finally, generate the entry we need, which is a pari of (output
265 # Finally, generate the entry we need, which is a pari of (output
266 # path, files) for use as a data_files parameter in install_data.
266 # path, files) for use as a data_files parameter in install_data.
267 out.append((out_path, pfiles))
267 out.append((out_path, pfiles))
268
268
269 return out
269 return out
270
270
271
271
272 def find_data_files():
272 def find_data_files():
273 """
273 """
274 Find IPython's data_files.
274 Find IPython's data_files.
275
275
276 Just man pages at this point.
276 Just man pages at this point.
277 """
277 """
278
278
279 manpagebase = pjoin('share', 'man', 'man1')
279 manpagebase = pjoin('share', 'man', 'man1')
280
280
281 # Simple file lists can be made by hand
281 # Simple file lists can be made by hand
282 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
282 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
283 if not manpages:
283 if not manpages:
284 # When running from a source tree, the manpages aren't gzipped
284 # When running from a source tree, the manpages aren't gzipped
285 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
285 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
286
286
287 # And assemble the entire output list
287 # And assemble the entire output list
288 data_files = [ (manpagebase, manpages) ]
288 data_files = [ (manpagebase, manpages) ]
289
289
290 return data_files
290 return data_files
291
291
292
292
293 def make_man_update_target(manpage):
293 def make_man_update_target(manpage):
294 """Return a target_update-compliant tuple for the given manpage.
294 """Return a target_update-compliant tuple for the given manpage.
295
295
296 Parameters
296 Parameters
297 ----------
297 ----------
298 manpage : string
298 manpage : string
299 Name of the manpage, must include the section number (trailing number).
299 Name of the manpage, must include the section number (trailing number).
300
300
301 Example
301 Example
302 -------
302 -------
303
303
304 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
304 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
305 ('docs/man/ipython.1.gz',
305 ('docs/man/ipython.1.gz',
306 ['docs/man/ipython.1'],
306 ['docs/man/ipython.1'],
307 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
307 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
308 """
308 """
309 man_dir = pjoin('docs', 'man')
309 man_dir = pjoin('docs', 'man')
310 manpage_gz = manpage + '.gz'
310 manpage_gz = manpage + '.gz'
311 manpath = pjoin(man_dir, manpage)
311 manpath = pjoin(man_dir, manpage)
312 manpath_gz = pjoin(man_dir, manpage_gz)
312 manpath_gz = pjoin(man_dir, manpage_gz)
313 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
313 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
314 locals() )
314 locals() )
315 return (manpath_gz, [manpath], gz_cmd)
315 return (manpath_gz, [manpath], gz_cmd)
316
316
317 # The two functions below are copied from IPython.utils.path, so we don't need
317 # The two functions below are copied from IPython.utils.path, so we don't need
318 # to import IPython during setup, which fails on Python 3.
318 # to import IPython during setup, which fails on Python 3.
319
319
320 def target_outdated(target,deps):
320 def target_outdated(target,deps):
321 """Determine whether a target is out of date.
321 """Determine whether a target is out of date.
322
322
323 target_outdated(target,deps) -> 1/0
323 target_outdated(target,deps) -> 1/0
324
324
325 deps: list of filenames which MUST exist.
325 deps: list of filenames which MUST exist.
326 target: single filename which may or may not exist.
326 target: single filename which may or may not exist.
327
327
328 If target doesn't exist or is older than any file listed in deps, return
328 If target doesn't exist or is older than any file listed in deps, return
329 true, otherwise return false.
329 true, otherwise return false.
330 """
330 """
331 try:
331 try:
332 target_time = os.path.getmtime(target)
332 target_time = os.path.getmtime(target)
333 except os.error:
333 except os.error:
334 return 1
334 return 1
335 for dep in deps:
335 for dep in deps:
336 dep_time = os.path.getmtime(dep)
336 dep_time = os.path.getmtime(dep)
337 if dep_time > target_time:
337 if dep_time > target_time:
338 #print "For target",target,"Dep failed:",dep # dbg
338 #print "For target",target,"Dep failed:",dep # dbg
339 #print "times (dep,tar):",dep_time,target_time # dbg
339 #print "times (dep,tar):",dep_time,target_time # dbg
340 return 1
340 return 1
341 return 0
341 return 0
342
342
343
343
344 def target_update(target,deps,cmd):
344 def target_update(target,deps,cmd):
345 """Update a target with a given command given a list of dependencies.
345 """Update a target with a given command given a list of dependencies.
346
346
347 target_update(target,deps,cmd) -> runs cmd if target is outdated.
347 target_update(target,deps,cmd) -> runs cmd if target is outdated.
348
348
349 This is just a wrapper around target_outdated() which calls the given
349 This is just a wrapper around target_outdated() which calls the given
350 command if target is outdated."""
350 command if target is outdated."""
351
351
352 if target_outdated(target,deps):
352 if target_outdated(target,deps):
353 os.system(cmd)
353 os.system(cmd)
354
354
355 #---------------------------------------------------------------------------
355 #---------------------------------------------------------------------------
356 # Find scripts
356 # Find scripts
357 #---------------------------------------------------------------------------
357 #---------------------------------------------------------------------------
358
358
359 def find_entry_points():
359 def find_entry_points():
360 """Find IPython's scripts.
360 """Find IPython's scripts.
361
361
362 if entry_points is True:
362 if entry_points is True:
363 return setuptools entry_point-style definitions
363 return setuptools entry_point-style definitions
364 else:
364 else:
365 return file paths of plain scripts [default]
365 return file paths of plain scripts [default]
366
366
367 suffix is appended to script names if entry_points is True, so that the
367 suffix is appended to script names if entry_points is True, so that the
368 Python 3 scripts get named "ipython3" etc.
368 Python 3 scripts get named "ipython3" etc.
369 """
369 """
370 ep = [
370 ep = [
371 'ipython%s = IPython:start_ipython',
371 'ipython%s = IPython:start_ipython',
372 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
372 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
373 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
373 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
374 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
374 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
375 'iptest%s = IPython.testing.iptestcontroller:main',
375 'iptest%s = IPython.testing.iptestcontroller:main',
376 ]
376 ]
377 suffix = str(sys.version_info[0])
377 suffix = str(sys.version_info[0])
378 return [e % '' for e in ep] + [e % suffix for e in ep]
378 return [e % '' for e in ep] + [e % suffix for e in ep]
379
379
380 script_src = """#!{executable}
380 script_src = """#!{executable}
381 # This script was automatically generated by setup.py
381 # This script was automatically generated by setup.py
382 if __name__ == '__main__':
382 if __name__ == '__main__':
383 from {mod} import {func}
383 from {mod} import {func}
384 {func}()
384 {func}()
385 """
385 """
386
386
387 class build_scripts_entrypt(build_scripts):
387 class build_scripts_entrypt(build_scripts):
388 def run(self):
388 def run(self):
389 self.mkpath(self.build_dir)
389 self.mkpath(self.build_dir)
390 outfiles = []
390 outfiles = []
391 for script in find_entry_points():
391 for script in find_entry_points():
392 name, entrypt = script.split('=')
392 name, entrypt = script.split('=')
393 name = name.strip()
393 name = name.strip()
394 entrypt = entrypt.strip()
394 entrypt = entrypt.strip()
395 outfile = os.path.join(self.build_dir, name)
395 outfile = os.path.join(self.build_dir, name)
396 outfiles.append(outfile)
396 outfiles.append(outfile)
397 print('Writing script to', outfile)
397 print('Writing script to', outfile)
398
398
399 mod, func = entrypt.split(':')
399 mod, func = entrypt.split(':')
400 with open(outfile, 'w') as f:
400 with open(outfile, 'w') as f:
401 f.write(script_src.format(executable=sys.executable,
401 f.write(script_src.format(executable=sys.executable,
402 mod=mod, func=func))
402 mod=mod, func=func))
403
403
404 return outfiles, outfiles
404 return outfiles, outfiles
405
405
406 class install_lib_symlink(Command):
406 class install_lib_symlink(Command):
407 user_options = [
407 user_options = [
408 ('install-dir=', 'd', "directory to install to"),
408 ('install-dir=', 'd', "directory to install to"),
409 ]
409 ]
410
410
411 def initialize_options(self):
411 def initialize_options(self):
412 self.install_dir = None
412 self.install_dir = None
413
413
414 def finalize_options(self):
414 def finalize_options(self):
415 self.set_undefined_options('symlink',
415 self.set_undefined_options('symlink',
416 ('install_lib', 'install_dir'),
416 ('install_lib', 'install_dir'),
417 )
417 )
418
418
419 def run(self):
419 def run(self):
420 if sys.platform == 'win32':
420 if sys.platform == 'win32':
421 raise Exception("This doesn't work on Windows.")
421 raise Exception("This doesn't work on Windows.")
422 pkg = os.path.join(os.getcwd(), 'IPython')
422 pkg = os.path.join(os.getcwd(), 'IPython')
423 dest = os.path.join(self.install_dir, 'IPython')
423 dest = os.path.join(self.install_dir, 'IPython')
424 if os.path.islink(dest):
424 if os.path.islink(dest):
425 print('removing existing symlink at %s' % dest)
425 print('removing existing symlink at %s' % dest)
426 os.unlink(dest)
426 os.unlink(dest)
427 print('symlinking %s -> %s' % (pkg, dest))
427 print('symlinking %s -> %s' % (pkg, dest))
428 os.symlink(pkg, dest)
428 os.symlink(pkg, dest)
429
429
430 class unsymlink(install):
430 class unsymlink(install):
431 def run(self):
431 def run(self):
432 dest = os.path.join(self.install_lib, 'IPython')
432 dest = os.path.join(self.install_lib, 'IPython')
433 if os.path.islink(dest):
433 if os.path.islink(dest):
434 print('removing symlink at %s' % dest)
434 print('removing symlink at %s' % dest)
435 os.unlink(dest)
435 os.unlink(dest)
436 else:
436 else:
437 print('No symlink exists at %s' % dest)
437 print('No symlink exists at %s' % dest)
438
438
439 class install_symlinked(install):
439 class install_symlinked(install):
440 def run(self):
440 def run(self):
441 if sys.platform == 'win32':
441 if sys.platform == 'win32':
442 raise Exception("This doesn't work on Windows.")
442 raise Exception("This doesn't work on Windows.")
443
443
444 # Run all sub-commands (at least those that need to be run)
444 # Run all sub-commands (at least those that need to be run)
445 for cmd_name in self.get_sub_commands():
445 for cmd_name in self.get_sub_commands():
446 self.run_command(cmd_name)
446 self.run_command(cmd_name)
447
447
448 # 'sub_commands': a list of commands this command might have to run to
448 # 'sub_commands': a list of commands this command might have to run to
449 # get its work done. See cmd.py for more info.
449 # get its work done. See cmd.py for more info.
450 sub_commands = [('install_lib_symlink', lambda self:True),
450 sub_commands = [('install_lib_symlink', lambda self:True),
451 ('install_scripts_sym', lambda self:True),
451 ('install_scripts_sym', lambda self:True),
452 ]
452 ]
453
453
454 class install_scripts_for_symlink(install_scripts):
454 class install_scripts_for_symlink(install_scripts):
455 """Redefined to get options from 'symlink' instead of 'install'.
455 """Redefined to get options from 'symlink' instead of 'install'.
456
456
457 I love distutils almost as much as I love setuptools.
457 I love distutils almost as much as I love setuptools.
458 """
458 """
459 def finalize_options(self):
459 def finalize_options(self):
460 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
460 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
461 self.set_undefined_options('symlink',
461 self.set_undefined_options('symlink',
462 ('install_scripts', 'install_dir'),
462 ('install_scripts', 'install_dir'),
463 ('force', 'force'),
463 ('force', 'force'),
464 ('skip_build', 'skip_build'),
464 ('skip_build', 'skip_build'),
465 )
465 )
466
466
467 #---------------------------------------------------------------------------
467 #---------------------------------------------------------------------------
468 # Verify all dependencies
468 # Verify all dependencies
469 #---------------------------------------------------------------------------
469 #---------------------------------------------------------------------------
470
470
471 def check_for_dependencies():
471 def check_for_dependencies():
472 """Check for IPython's dependencies.
472 """Check for IPython's dependencies.
473
473
474 This function should NOT be called if running under setuptools!
474 This function should NOT be called if running under setuptools!
475 """
475 """
476 from setupext.setupext import (
476 from setupext.setupext import (
477 print_line, print_raw, print_status,
477 print_line, print_raw, print_status,
478 check_for_sphinx, check_for_pygments,
478 check_for_sphinx, check_for_pygments,
479 check_for_nose, check_for_pexpect,
479 check_for_nose, check_for_pexpect,
480 check_for_pyzmq, check_for_readline,
480 check_for_pyzmq, check_for_readline,
481 check_for_jinja2, check_for_tornado
481 check_for_jinja2, check_for_tornado
482 )
482 )
483 print_line()
483 print_line()
484 print_raw("BUILDING IPYTHON")
484 print_raw("BUILDING IPYTHON")
485 print_status('python', sys.version)
485 print_status('python', sys.version)
486 print_status('platform', sys.platform)
486 print_status('platform', sys.platform)
487 if sys.platform == 'win32':
487 if sys.platform == 'win32':
488 print_status('Windows version', sys.getwindowsversion())
488 print_status('Windows version', sys.getwindowsversion())
489
489
490 print_raw("")
490 print_raw("")
491 print_raw("OPTIONAL DEPENDENCIES")
491 print_raw("OPTIONAL DEPENDENCIES")
492
492
493 check_for_sphinx()
493 check_for_sphinx()
494 check_for_pygments()
494 check_for_pygments()
495 check_for_nose()
495 check_for_nose()
496 if os.name == 'posix':
496 if os.name == 'posix':
497 check_for_pexpect()
497 check_for_pexpect()
498 check_for_pyzmq()
498 check_for_pyzmq()
499 check_for_tornado()
499 check_for_tornado()
500 check_for_readline()
500 check_for_readline()
501 check_for_jinja2()
501 check_for_jinja2()
502
502
503 #---------------------------------------------------------------------------
503 #---------------------------------------------------------------------------
504 # VCS related
504 # VCS related
505 #---------------------------------------------------------------------------
505 #---------------------------------------------------------------------------
506
506
507 # utils.submodule has checks for submodule status
507 # utils.submodule has checks for submodule status
508 execfile(pjoin('IPython','utils','submodule.py'), globals())
508 execfile(pjoin('IPython','utils','submodule.py'), globals())
509
509
510 class UpdateSubmodules(Command):
510 class UpdateSubmodules(Command):
511 """Update git submodules
511 """Update git submodules
512
512
513 IPython's external javascript dependencies live in a separate repo.
513 IPython's external javascript dependencies live in a separate repo.
514 """
514 """
515 description = "Update git submodules"
515 description = "Update git submodules"
516 user_options = []
516 user_options = []
517
517
518 def initialize_options(self):
518 def initialize_options(self):
519 pass
519 pass
520
520
521 def finalize_options(self):
521 def finalize_options(self):
522 pass
522 pass
523
523
524 def run(self):
524 def run(self):
525 failure = False
525 failure = False
526 try:
526 try:
527 self.spawn('git submodule init'.split())
527 self.spawn('git submodule init'.split())
528 self.spawn('git submodule update --recursive'.split())
528 self.spawn('git submodule update --recursive'.split())
529 except Exception as e:
529 except Exception as e:
530 failure = e
530 failure = e
531 print(e)
531 print(e)
532
532
533 if not check_submodule_status(repo_root) == 'clean':
533 if not check_submodule_status(repo_root) == 'clean':
534 print("submodules could not be checked out")
534 print("submodules could not be checked out")
535 sys.exit(1)
535 sys.exit(1)
536
536
537
537
538 def git_prebuild(pkg_dir, build_cmd=build_py):
538 def git_prebuild(pkg_dir, build_cmd=build_py):
539 """Return extended build or sdist command class for recording commit
539 """Return extended build or sdist command class for recording commit
540
540
541 records git commit in IPython.utils._sysinfo.commit
541 records git commit in IPython.utils._sysinfo.commit
542
542
543 for use in IPython.utils.sysinfo.sys_info() calls after installation.
543 for use in IPython.utils.sysinfo.sys_info() calls after installation.
544
544
545 Also ensures that submodules exist prior to running
545 Also ensures that submodules exist prior to running
546 """
546 """
547
547
548 class MyBuildPy(build_cmd):
548 class MyBuildPy(build_cmd):
549 ''' Subclass to write commit data into installation tree '''
549 ''' Subclass to write commit data into installation tree '''
550 def run(self):
550 def run(self):
551 build_cmd.run(self)
551 build_cmd.run(self)
552 # this one will only fire for build commands
552 # this one will only fire for build commands
553 if hasattr(self, 'build_lib'):
553 if hasattr(self, 'build_lib'):
554 self._record_commit(self.build_lib)
554 self._record_commit(self.build_lib)
555
555
556 def make_release_tree(self, base_dir, files):
556 def make_release_tree(self, base_dir, files):
557 # this one will fire for sdist
557 # this one will fire for sdist
558 build_cmd.make_release_tree(self, base_dir, files)
558 build_cmd.make_release_tree(self, base_dir, files)
559 self._record_commit(base_dir)
559 self._record_commit(base_dir)
560
560
561 def _record_commit(self, base_dir):
561 def _record_commit(self, base_dir):
562 import subprocess
562 import subprocess
563 proc = subprocess.Popen('git rev-parse --short HEAD',
563 proc = subprocess.Popen('git rev-parse --short HEAD',
564 stdout=subprocess.PIPE,
564 stdout=subprocess.PIPE,
565 stderr=subprocess.PIPE,
565 stderr=subprocess.PIPE,
566 shell=True)
566 shell=True)
567 repo_commit, _ = proc.communicate()
567 repo_commit, _ = proc.communicate()
568 repo_commit = repo_commit.strip().decode("ascii")
568 repo_commit = repo_commit.strip().decode("ascii")
569
569
570 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
570 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
571 if os.path.isfile(out_pth) and not repo_commit:
571 if os.path.isfile(out_pth) and not repo_commit:
572 # nothing to write, don't clobber
572 # nothing to write, don't clobber
573 return
573 return
574
574
575 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
575 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
576
576
577 # remove to avoid overwriting original via hard link
577 # remove to avoid overwriting original via hard link
578 try:
578 try:
579 os.remove(out_pth)
579 os.remove(out_pth)
580 except (IOError, OSError):
580 except (IOError, OSError):
581 pass
581 pass
582 with open(out_pth, 'w') as out_file:
582 with open(out_pth, 'w') as out_file:
583 out_file.writelines([
583 out_file.writelines([
584 '# GENERATED BY setup.py\n',
584 '# GENERATED BY setup.py\n',
585 'commit = u"%s"\n' % repo_commit,
585 'commit = u"%s"\n' % repo_commit,
586 ])
586 ])
587 return require_submodules(MyBuildPy)
587 return require_submodules(MyBuildPy)
588
588
589
589
590 def require_submodules(command):
590 def require_submodules(command):
591 """decorator for instructing a command to check for submodules before running"""
591 """decorator for instructing a command to check for submodules before running"""
592 class DecoratedCommand(command):
592 class DecoratedCommand(command):
593 def run(self):
593 def run(self):
594 if not check_submodule_status(repo_root) == 'clean':
594 if not check_submodule_status(repo_root) == 'clean':
595 print("submodules missing! Run `setup.py submodule` and try again")
595 print("submodules missing! Run `setup.py submodule` and try again")
596 sys.exit(1)
596 sys.exit(1)
597 command.run(self)
597 command.run(self)
598 return DecoratedCommand
598 return DecoratedCommand
599
599
600 #---------------------------------------------------------------------------
600 #---------------------------------------------------------------------------
601 # bdist related
601 # bdist related
602 #---------------------------------------------------------------------------
602 #---------------------------------------------------------------------------
603
603
604 def get_bdist_wheel():
604 def get_bdist_wheel():
605 """Construct bdist_wheel command for building wheels
605 """Construct bdist_wheel command for building wheels
606
606
607 Constructs py2-none-any tag, instead of py2.7-none-any
607 Constructs py2-none-any tag, instead of py2.7-none-any
608 """
608 """
609 class RequiresWheel(Command):
609 class RequiresWheel(Command):
610 description = "Dummy command for missing bdist_wheel"
610 description = "Dummy command for missing bdist_wheel"
611 user_options = []
611 user_options = []
612
612
613 def initialize_options(self):
613 def initialize_options(self):
614 pass
614 pass
615
615
616 def finalize_options(self):
616 def finalize_options(self):
617 pass
617 pass
618
618
619 def run(self):
619 def run(self):
620 print("bdist_wheel requires the wheel package")
620 print("bdist_wheel requires the wheel package")
621 sys.exit(1)
621 sys.exit(1)
622
622
623 if 'setuptools' not in sys.modules:
623 if 'setuptools' not in sys.modules:
624 return RequiresWheel
624 return RequiresWheel
625 else:
625 else:
626 try:
626 try:
627 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
627 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
628 except ImportError:
628 except ImportError:
629 return RequiresWheel
629 return RequiresWheel
630
630
631 class bdist_wheel_tag(bdist_wheel):
631 class bdist_wheel_tag(bdist_wheel):
632
632
633 def add_requirements(self, metadata_path):
633 def add_requirements(self, metadata_path):
634 """transform platform-dependent requirements"""
634 """transform platform-dependent requirements"""
635 pkg_info = read_pkg_info(metadata_path)
635 pkg_info = read_pkg_info(metadata_path)
636 # pkg_info is an email.Message object (?!)
636 # pkg_info is an email.Message object (?!)
637 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
637 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
638 # and transform them to conditionals
638 # and transform them to conditionals
639 requires = pkg_info.get_all('Requires-Dist')
639 requires = pkg_info.get_all('Requires-Dist')
640 del pkg_info['Requires-Dist']
640 del pkg_info['Requires-Dist']
641 def _remove_startswith(lis, prefix):
641 def _remove_startswith(lis, prefix):
642 """like list.remove, but with startswith instead of =="""
642 """like list.remove, but with startswith instead of =="""
643 found = False
643 found = False
644 for idx, item in enumerate(lis):
644 for idx, item in enumerate(lis):
645 if item.startswith(prefix):
645 if item.startswith(prefix):
646 found = True
646 found = True
647 break
647 break
648 if found:
648 if found:
649 lis.pop(idx)
649 lis.pop(idx)
650
650
651 for pkg in ("gnureadline", "pyreadline", "mock"):
651 for pkg in ("gnureadline", "pyreadline", "mock"):
652 _remove_startswith(requires, pkg)
652 _remove_startswith(requires, pkg)
653 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
653 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
654 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
654 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
655 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
655 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
656 requires.append("mock; extra == 'test' and python_version < '3.3'")
656 requires.append("mock; extra == 'test' and python_version < '3.3'")
657 for r in requires:
657 for r in requires:
658 pkg_info['Requires-Dist'] = r
658 pkg_info['Requires-Dist'] = r
659 write_pkg_info(metadata_path, pkg_info)
659 write_pkg_info(metadata_path, pkg_info)
660
660
661 return bdist_wheel_tag
661 return bdist_wheel_tag
662
662
663 #---------------------------------------------------------------------------
663 #---------------------------------------------------------------------------
664 # Notebook related
664 # Notebook related
665 #---------------------------------------------------------------------------
665 #---------------------------------------------------------------------------
666
666
667 class CompileCSS(Command):
667 class CompileCSS(Command):
668 """Recompile Notebook CSS
668 """Recompile Notebook CSS
669
669
670 Regenerate the compiled CSS from LESS sources.
670 Regenerate the compiled CSS from LESS sources.
671
671
672 Requires various dev dependencies, such as fabric and lessc.
672 Requires various dev dependencies, such as invoke and lessc.
673 """
673 """
674 description = "Recompile Notebook CSS"
674 description = "Recompile Notebook CSS"
675 user_options = [
675 user_options = [
676 ('minify', 'x', "minify CSS"),
676 ('minify', 'x', "minify CSS"),
677 ('force', 'f', "force recompilation of CSS"),
677 ('force', 'f', "force recompilation of CSS"),
678 ]
678 ]
679
679
680 def initialize_options(self):
680 def initialize_options(self):
681 self.minify = False
681 self.minify = False
682 self.force = False
682 self.force = False
683
683
684 def finalize_options(self):
684 def finalize_options(self):
685 self.minify = bool(self.minify)
685 self.minify = bool(self.minify)
686 self.force = bool(self.force)
686 self.force = bool(self.force)
687
687
688 def run(self):
688 def run(self):
689 check_call([
689 cmd = ['invoke', 'css']
690 "fab",
690 if self.minify:
691 "css:minify=%s,force=%s" % (self.minify, self.force),
691 cmd.append('--minify')
692 ], cwd=pjoin(repo_root, "IPython", "html"),
692 if self.force:
693 )
693 cmd.append('--force')
694 check_call(cmd, cwd=pjoin(repo_root, "IPython", "html"))
694
695
695
696
696 class JavascriptVersion(Command):
697 class JavascriptVersion(Command):
697 """write the javascript version to notebook javascript"""
698 """write the javascript version to notebook javascript"""
698 description = "Write IPython version to javascript"
699 description = "Write IPython version to javascript"
699 user_options = []
700 user_options = []
700
701
701 def initialize_options(self):
702 def initialize_options(self):
702 pass
703 pass
703
704
704 def finalize_options(self):
705 def finalize_options(self):
705 pass
706 pass
706
707
707 def run(self):
708 def run(self):
708 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
709 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
709 with open(nsfile) as f:
710 with open(nsfile) as f:
710 lines = f.readlines()
711 lines = f.readlines()
711 with open(nsfile, 'w') as f:
712 with open(nsfile, 'w') as f:
712 for line in lines:
713 for line in lines:
713 if line.startswith("IPython.version"):
714 if line.startswith("IPython.version"):
714 line = 'IPython.version = "{0}";\n'.format(version)
715 line = 'IPython.version = "{0}";\n'.format(version)
715 f.write(line)
716 f.write(line)
716
717
717
718
718 def css_js_prerelease(command, strict=True):
719 def css_js_prerelease(command, strict=True):
719 """decorator for building js/minified css prior to a release"""
720 """decorator for building js/minified css prior to a release"""
720 class DecoratedCommand(command):
721 class DecoratedCommand(command):
721 def run(self):
722 def run(self):
722 self.distribution.run_command('jsversion')
723 self.distribution.run_command('jsversion')
723 css = self.distribution.get_command_obj('css')
724 css = self.distribution.get_command_obj('css')
724 css.minify = True
725 css.minify = True
725 try:
726 try:
726 self.distribution.run_command('css')
727 self.distribution.run_command('css')
727 except Exception as e:
728 except Exception as e:
728 if strict:
729 if strict:
729 raise
730 raise
730 else:
731 else:
731 log.warn("Failed to build css sourcemaps: %s" % e)
732 log.warn("Failed to build css sourcemaps: %s" % e)
732 command.run(self)
733 command.run(self)
733 return DecoratedCommand
734 return DecoratedCommand
@@ -1,46 +1,44 b''
1 # Tox (http://tox.testrun.org/) is a tool for running tests
1 # Tox (http://tox.testrun.org/) is a tool for running tests
2 # in multiple virtualenvs. This configuration file will run the
2 # in multiple virtualenvs. This configuration file will run the
3 # test suite on all supported python versions. To use it, "pip install tox"
3 # test suite on all supported python versions. To use it, "pip install tox"
4 # and then run "tox" from this directory.
4 # and then run "tox" from this directory.
5
5
6 # Building the source distribution requires both fabric's fab binary
6 # Building the source distribution requires `invoke` and `lessc` to be on your PATH.
7 # (http://www.fabfile.org/) and the lessc binary of the css preprocessor
7 # "pip install invoke" will install invoke. Less can be installed by
8 # less (http://lesscss.org/) in the PATH.
9 # "pip install fabric" will install fabric. Less can be installed by
10 # node.js' (http://nodejs.org/) package manager npm:
8 # node.js' (http://nodejs.org/) package manager npm:
11 # "npm install -g less".
9 # "npm install -g less".
12
10
13 # Javascript tests need additional dependencies that can be installed
11 # Javascript tests need additional dependencies that can be installed
14 # using node.js' package manager npm:
12 # using node.js' package manager npm:
15 # [*] casperjs: "npm install -g casperjs"
13 # [*] casperjs: "npm install -g casperjs"
16 # [*] slimerjs: "npm install -g slimerjs"
14 # [*] slimerjs: "npm install -g slimerjs"
17 # [*] phantomjs: "npm install -g phantomjs"
15 # [*] phantomjs: "npm install -g phantomjs"
18
16
19 # Note: qt4 versions break some tests with tornado versions >=4.0.
17 # Note: qt4 versions break some tests with tornado versions >=4.0.
20
18
21 [tox]
19 [tox]
22 envlist = py27, py33, py34
20 envlist = py27, py33, py34
23
21
24 [testenv]
22 [testenv]
25 deps =
23 deps =
26 pyzmq
24 pyzmq
27 nose
25 nose
28 tornado<4.0
26 tornado<4.0
29 jinja2
27 jinja2
30 sphinx
28 sphinx
31 pygments
29 pygments
32 jsonpointer
30 jsonpointer
33 jsonschema
31 jsonschema
34 mistune
32 mistune
35
33
36 # To avoid loading IPython module in the current directory, change
34 # To avoid loading IPython module in the current directory, change
37 # current directory to ".tox/py*/tmp" before running test.
35 # current directory to ".tox/py*/tmp" before running test.
38 changedir = {envtmpdir}
36 changedir = {envtmpdir}
39
37
40 commands =
38 commands =
41 iptest --all
39 iptest --all
42
40
43 [testenv:py27]
41 [testenv:py27]
44 deps=
42 deps=
45 mock
43 mock
46 {[testenv]deps}
44 {[testenv]deps}
General Comments 0
You need to be logged in to leave comments. Login now