##// END OF EJS Templates
friendlier error messages when invoke/lessc are missing...
Min RK -
Show More
@@ -1,83 +1,108 b''
1 """invoke task file to build CSS"""
1 """invoke task file to build CSS"""
2
2
3 from invoke import task, run
3 from __future__ import print_function
4
4 import os
5 import os
6 import sys
5 from distutils.version import LooseVersion as V
7 from distutils.version import LooseVersion as V
6 from subprocess import check_output
8 from subprocess import check_output
7
9
10 from invoke import task, run
11 from invoke.runner import Result
12 from invoke.exceptions import Failure
13
8 pjoin = os.path.join
14 pjoin = os.path.join
9 static_dir = 'static'
15 static_dir = 'static'
10 components_dir = pjoin(static_dir, 'components')
16 components_dir = pjoin(static_dir, 'components')
11 here = os.path.dirname(__file__)
17 here = os.path.dirname(__file__)
12
18
13 min_less_version = '2.0'
19 min_less_version = '2.0'
14 max_less_version = '3.0' # exclusive if string
20 max_less_version = '3.0' # exclusive if string
15
21
22
23 def _fail(msg=''):
24 """Fail a task, logging a message to stderr
25
26 raises a special Failure Exception from invoke.
27 """
28 if msg:
29 print(msg, file=sys.stderr)
30 # raising a Failure allows us to avoid a traceback
31 # we only care about exited, but stdout, stderr, pty are required args
32 raise Failure(Result(stdout='', stderr='', pty=False, exited=1))
33
16 def _need_css_update():
34 def _need_css_update():
17 """Does less need to run?"""
35 """Does less need to run?"""
18
36
19 static_path = pjoin(here, static_dir)
37 static_path = pjoin(here, static_dir)
20 css_targets = [
38 css_targets = [
21 pjoin(static_path, 'style', '%s.min.css' % name)
39 pjoin(static_path, 'style', '%s.min.css' % name)
22 for name in ('style', 'ipython')
40 for name in ('style', 'ipython')
23 ]
41 ]
24 css_maps = [t + '.map' for t in css_targets]
42 css_maps = [t + '.map' for t in css_targets]
25 targets = css_targets + css_maps
43 targets = css_targets + css_maps
26 if not all(os.path.exists(t) for t in targets):
44 if not all(os.path.exists(t) for t in targets):
27 # some generated files don't exist
45 # some generated files don't exist
28 return True
46 return True
29 earliest_target = sorted(os.stat(t).st_mtime for t in targets)[0]
47 earliest_target = sorted(os.stat(t).st_mtime for t in targets)[0]
30
48
31 # check if any .less files are newer than the generated targets
49 # check if any .less files are newer than the generated targets
32 for (dirpath, dirnames, filenames) in os.walk(static_path):
50 for (dirpath, dirnames, filenames) in os.walk(static_path):
33 for f in filenames:
51 for f in filenames:
34 if f.endswith('.less'):
52 if f.endswith('.less'):
35 path = pjoin(static_path, dirpath, f)
53 path = pjoin(static_path, dirpath, f)
36 timestamp = os.stat(path).st_mtime
54 timestamp = os.stat(path).st_mtime
37 if timestamp > earliest_target:
55 if timestamp > earliest_target:
38 return True
56 return True
39
57
40 return False
58 return False
41
59
42 @task
60 @task
43 def css(minify=False, verbose=False, force=False):
61 def css(minify=False, verbose=False, force=False):
44 """generate the css from less files"""
62 """generate the css from less files"""
45 # minify implies force because it's not the default behavior
63 # minify implies force because it's not the default behavior
46 if not force and not minify and not _need_css_update():
64 if not force and not minify and not _need_css_update():
47 print("css up-to-date")
65 print("css up-to-date")
48 return
66 return
49
67
50 for name in ('style', 'ipython'):
68 for name in ('style', 'ipython'):
51 source = pjoin('style', "%s.less" % name)
69 source = pjoin('style', "%s.less" % name)
52 target = pjoin('style', "%s.min.css" % name)
70 target = pjoin('style', "%s.min.css" % name)
53 sourcemap = pjoin('style', "%s.min.css.map" % name)
71 sourcemap = pjoin('style', "%s.min.css.map" % name)
54 _compile_less(source, target, sourcemap, minify, verbose)
72 _compile_less(source, target, sourcemap, minify, verbose)
55
73
74
56 def _compile_less(source, target, sourcemap, minify=True, verbose=False):
75 def _compile_less(source, target, sourcemap, minify=True, verbose=False):
57 """Compile a less file by source and target relative to static_dir"""
76 """Compile a less file by source and target relative to static_dir"""
58 min_flag = '-x' if minify else ''
77 min_flag = '-x' if minify else ''
59 ver_flag = '--verbose' if verbose else ''
78 ver_flag = '--verbose' if verbose else ''
60
79
80 install = "(npm install -g 'less@<{}')".format(max_less_version)
61 # pin less to version number from above
81 # pin less to version number from above
62 try:
82 try:
63 out = check_output(['lessc', '--version'])
83 out = check_output(['lessc', '--version'])
64 except OSError as err:
84 except OSError as err:
65 raise ValueError("Unable to find lessc. Please install lessc >= %s and < %s " \
85 _fail("Unable to find lessc. Rebuilding css requires less >= {0} and < {1} {2}".format(
66 % (min_less_version, max_less_version))
86 min_less_version, max_less_version, install
87 ))
67 out = out.decode('utf8', 'replace')
88 out = out.decode('utf8', 'replace')
68 less_version = out.split()[1]
89 less_version = out.split()[1]
69 if min_less_version and V(less_version) < V(min_less_version):
90 if min_less_version and V(less_version) < V(min_less_version):
70 raise ValueError("lessc too old: %s < %s. Use `$ npm install less@X.Y.Z` to install a specific version of less" % (less_version, min_less_version))
91 _fail("lessc too old: {} < {} {}".format(
92 less_version, min_less_version, install,
93 ))
71 if max_less_version and V(less_version) >= V(max_less_version):
94 if max_less_version and V(less_version) >= V(max_less_version):
72 raise ValueError("lessc too new: %s >= %s. Use `$ npm install less@X.Y.Z` to install a specific version of less" % (less_version, max_less_version))
95 _fail("lessc too new: {} >= {} {}".format(
96 less_version, max_less_version, install,
97 ))
73
98
74 static_path = pjoin(here, static_dir)
99 static_path = pjoin(here, static_dir)
75 cwd = os.getcwd()
100 cwd = os.getcwd()
76 try:
101 try:
77 os.chdir(static_dir)
102 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()),
103 run('lessc {min_flag} {ver_flag} --source-map={sourcemap} --source-map-basepath={static_path} --source-map-rootpath="../" {source} {target}'.format(**locals()),
79 echo=True,
104 echo=True,
80 )
105 )
81 finally:
106 finally:
82 os.chdir(cwd)
107 os.chdir(cwd)
83
108
@@ -1,755 +1,765 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 This module defines the things that are used in setup.py for building IPython
3 This module defines the things that are used in setup.py for building IPython
4
4
5 This includes:
5 This includes:
6
6
7 * The basic arguments to setup
7 * The basic arguments to setup
8 * Functions for finding things like packages, package data, etc.
8 * Functions for finding things like packages, package data, etc.
9 * A function for checking dependencies.
9 * A function for checking dependencies.
10 """
10 """
11
11
12 # Copyright (c) IPython Development Team.
12 # Copyright (c) IPython Development Team.
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14
14
15 from __future__ import print_function
15 from __future__ import print_function
16
16
17 import errno
17 import errno
18 import os
18 import os
19 import sys
19 import sys
20
20
21 from distutils import log
21 from distutils import log
22 from distutils.command.build_py import build_py
22 from distutils.command.build_py import build_py
23 from distutils.command.build_scripts import build_scripts
23 from distutils.command.build_scripts import build_scripts
24 from distutils.command.install import install
24 from distutils.command.install import install
25 from distutils.command.install_scripts import install_scripts
25 from distutils.command.install_scripts import install_scripts
26 from distutils.cmd import Command
26 from distutils.cmd import Command
27 from distutils.errors import DistutilsExecError
27 from fnmatch import fnmatch
28 from fnmatch import fnmatch
28 from glob import glob
29 from glob import glob
29 from subprocess import check_call
30 from subprocess import Popen, PIPE
30
31
31 from setupext import install_data_ext
32 from setupext import install_data_ext
32
33
33 #-------------------------------------------------------------------------------
34 #-------------------------------------------------------------------------------
34 # Useful globals and utility functions
35 # Useful globals and utility functions
35 #-------------------------------------------------------------------------------
36 #-------------------------------------------------------------------------------
36
37
37 # A few handy globals
38 # A few handy globals
38 isfile = os.path.isfile
39 isfile = os.path.isfile
39 pjoin = os.path.join
40 pjoin = os.path.join
40 repo_root = os.path.dirname(os.path.abspath(__file__))
41 repo_root = os.path.dirname(os.path.abspath(__file__))
41
42
42 def oscmd(s):
43 def oscmd(s):
43 print(">", s)
44 print(">", s)
44 os.system(s)
45 os.system(s)
45
46
46 # Py3 compatibility hacks, without assuming IPython itself is installed with
47 # Py3 compatibility hacks, without assuming IPython itself is installed with
47 # the full py3compat machinery.
48 # the full py3compat machinery.
48
49
49 try:
50 try:
50 execfile
51 execfile
51 except NameError:
52 except NameError:
52 def execfile(fname, globs, locs=None):
53 def execfile(fname, globs, locs=None):
53 locs = locs or globs
54 locs = locs or globs
54 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
55 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
55
56
56 # A little utility we'll need below, since glob() does NOT allow you to do
57 # A little utility we'll need below, since glob() does NOT allow you to do
57 # exclusion on multiple endings!
58 # exclusion on multiple endings!
58 def file_doesnt_endwith(test,endings):
59 def file_doesnt_endwith(test,endings):
59 """Return true if test is a file and its name does NOT end with any
60 """Return true if test is a file and its name does NOT end with any
60 of the strings listed in endings."""
61 of the strings listed in endings."""
61 if not isfile(test):
62 if not isfile(test):
62 return False
63 return False
63 for e in endings:
64 for e in endings:
64 if test.endswith(e):
65 if test.endswith(e):
65 return False
66 return False
66 return True
67 return True
67
68
68 #---------------------------------------------------------------------------
69 #---------------------------------------------------------------------------
69 # Basic project information
70 # Basic project information
70 #---------------------------------------------------------------------------
71 #---------------------------------------------------------------------------
71
72
72 # release.py contains version, authors, license, url, keywords, etc.
73 # release.py contains version, authors, license, url, keywords, etc.
73 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
74 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
74
75
75 # Create a dict with the basic information
76 # Create a dict with the basic information
76 # This dict is eventually passed to setup after additional keys are added.
77 # This dict is eventually passed to setup after additional keys are added.
77 setup_args = dict(
78 setup_args = dict(
78 name = name,
79 name = name,
79 version = version,
80 version = version,
80 description = description,
81 description = description,
81 long_description = long_description,
82 long_description = long_description,
82 author = author,
83 author = author,
83 author_email = author_email,
84 author_email = author_email,
84 url = url,
85 url = url,
85 download_url = download_url,
86 download_url = download_url,
86 license = license,
87 license = license,
87 platforms = platforms,
88 platforms = platforms,
88 keywords = keywords,
89 keywords = keywords,
89 classifiers = classifiers,
90 classifiers = classifiers,
90 cmdclass = {'install_data': install_data_ext},
91 cmdclass = {'install_data': install_data_ext},
91 )
92 )
92
93
93
94
94 #---------------------------------------------------------------------------
95 #---------------------------------------------------------------------------
95 # Find packages
96 # Find packages
96 #---------------------------------------------------------------------------
97 #---------------------------------------------------------------------------
97
98
98 def find_packages():
99 def find_packages():
99 """
100 """
100 Find all of IPython's packages.
101 Find all of IPython's packages.
101 """
102 """
102 excludes = ['deathrow', 'quarantine']
103 excludes = ['deathrow', 'quarantine']
103 packages = []
104 packages = []
104 for dir,subdirs,files in os.walk('IPython'):
105 for dir,subdirs,files in os.walk('IPython'):
105 package = dir.replace(os.path.sep, '.')
106 package = dir.replace(os.path.sep, '.')
106 if any(package.startswith('IPython.'+exc) for exc in excludes):
107 if any(package.startswith('IPython.'+exc) for exc in excludes):
107 # package is to be excluded (e.g. deathrow)
108 # package is to be excluded (e.g. deathrow)
108 continue
109 continue
109 if '__init__.py' not in files:
110 if '__init__.py' not in files:
110 # not a package
111 # not a package
111 continue
112 continue
112 packages.append(package)
113 packages.append(package)
113 return packages
114 return packages
114
115
115 #---------------------------------------------------------------------------
116 #---------------------------------------------------------------------------
116 # Find package data
117 # Find package data
117 #---------------------------------------------------------------------------
118 #---------------------------------------------------------------------------
118
119
119 def find_package_data():
120 def find_package_data():
120 """
121 """
121 Find IPython's package_data.
122 Find IPython's package_data.
122 """
123 """
123 # This is not enough for these things to appear in an sdist.
124 # This is not enough for these things to appear in an sdist.
124 # We need to muck with the MANIFEST to get this to work
125 # We need to muck with the MANIFEST to get this to work
125
126
126 # exclude components and less from the walk;
127 # exclude components and less from the walk;
127 # we will build the components separately
128 # we will build the components separately
128 excludes = [
129 excludes = [
129 pjoin('static', 'components'),
130 pjoin('static', 'components'),
130 pjoin('static', '*', 'less'),
131 pjoin('static', '*', 'less'),
131 ]
132 ]
132
133
133 # walk notebook resources:
134 # walk notebook resources:
134 cwd = os.getcwd()
135 cwd = os.getcwd()
135 os.chdir(os.path.join('IPython', 'html'))
136 os.chdir(os.path.join('IPython', 'html'))
136 static_data = []
137 static_data = []
137 for parent, dirs, files in os.walk('static'):
138 for parent, dirs, files in os.walk('static'):
138 if any(fnmatch(parent, pat) for pat in excludes):
139 if any(fnmatch(parent, pat) for pat in excludes):
139 # prevent descending into subdirs
140 # prevent descending into subdirs
140 dirs[:] = []
141 dirs[:] = []
141 continue
142 continue
142 for f in files:
143 for f in files:
143 static_data.append(pjoin(parent, f))
144 static_data.append(pjoin(parent, f))
144
145
145 components = pjoin("static", "components")
146 components = pjoin("static", "components")
146 # select the components we actually need to install
147 # 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)
148 # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
148 static_data.extend([
149 static_data.extend([
149 pjoin(components, "backbone", "backbone-min.js"),
150 pjoin(components, "backbone", "backbone-min.js"),
150 pjoin(components, "bootstrap", "js", "bootstrap.min.js"),
151 pjoin(components, "bootstrap", "js", "bootstrap.min.js"),
151 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
152 pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"),
152 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
153 pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"),
153 pjoin(components, "es6-promise", "*.js"),
154 pjoin(components, "es6-promise", "*.js"),
154 pjoin(components, "font-awesome", "fonts", "*.*"),
155 pjoin(components, "font-awesome", "fonts", "*.*"),
155 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
156 pjoin(components, "google-caja", "html-css-sanitizer-minified.js"),
156 pjoin(components, "jquery", "jquery.min.js"),
157 pjoin(components, "jquery", "jquery.min.js"),
157 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
158 pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
158 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
159 pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
159 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
160 pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"),
160 pjoin(components, "marked", "lib", "marked.js"),
161 pjoin(components, "marked", "lib", "marked.js"),
161 pjoin(components, "requirejs", "require.js"),
162 pjoin(components, "requirejs", "require.js"),
162 pjoin(components, "underscore", "underscore-min.js"),
163 pjoin(components, "underscore", "underscore-min.js"),
163 pjoin(components, "moment", "moment.js"),
164 pjoin(components, "moment", "moment.js"),
164 pjoin(components, "moment", "min", "moment.min.js"),
165 pjoin(components, "moment", "min", "moment.min.js"),
165 pjoin(components, "term.js", "src", "term.js"),
166 pjoin(components, "term.js", "src", "term.js"),
166 pjoin(components, "text-encoding", "lib", "encoding.js"),
167 pjoin(components, "text-encoding", "lib", "encoding.js"),
167 ])
168 ])
168
169
169 # Ship all of Codemirror's CSS and JS
170 # Ship all of Codemirror's CSS and JS
170 for parent, dirs, files in os.walk(pjoin(components, 'codemirror')):
171 for parent, dirs, files in os.walk(pjoin(components, 'codemirror')):
171 for f in files:
172 for f in files:
172 if f.endswith(('.js', '.css')):
173 if f.endswith(('.js', '.css')):
173 static_data.append(pjoin(parent, f))
174 static_data.append(pjoin(parent, f))
174
175
175 os.chdir(os.path.join('tests',))
176 os.chdir(os.path.join('tests',))
176 js_tests = glob('*.js') + glob('*/*.js')
177 js_tests = glob('*.js') + glob('*/*.js')
177
178
178 os.chdir(os.path.join(cwd, 'IPython', 'nbconvert'))
179 os.chdir(os.path.join(cwd, 'IPython', 'nbconvert'))
179 nbconvert_templates = [os.path.join(dirpath, '*.*')
180 nbconvert_templates = [os.path.join(dirpath, '*.*')
180 for dirpath, _, _ in os.walk('templates')]
181 for dirpath, _, _ in os.walk('templates')]
181
182
182 os.chdir(cwd)
183 os.chdir(cwd)
183
184
184 package_data = {
185 package_data = {
185 'IPython.config.profile' : ['README*', '*/*.py'],
186 'IPython.config.profile' : ['README*', '*/*.py'],
186 'IPython.core.tests' : ['*.png', '*.jpg'],
187 'IPython.core.tests' : ['*.png', '*.jpg'],
187 'IPython.lib.tests' : ['*.wav'],
188 'IPython.lib.tests' : ['*.wav'],
188 'IPython.testing.plugin' : ['*.txt'],
189 'IPython.testing.plugin' : ['*.txt'],
189 'IPython.html' : ['templates/*'] + static_data,
190 'IPython.html' : ['templates/*'] + static_data,
190 'IPython.html.tests' : js_tests,
191 'IPython.html.tests' : js_tests,
191 'IPython.qt.console' : ['resources/icon/*.svg'],
192 'IPython.qt.console' : ['resources/icon/*.svg'],
192 'IPython.nbconvert' : nbconvert_templates +
193 'IPython.nbconvert' : nbconvert_templates +
193 [
194 [
194 'tests/files/*.*',
195 'tests/files/*.*',
195 'exporters/tests/files/*.*',
196 'exporters/tests/files/*.*',
196 'preprocessors/tests/files/*.*',
197 'preprocessors/tests/files/*.*',
197 ],
198 ],
198 'IPython.nbconvert.filters' : ['marked.js'],
199 'IPython.nbconvert.filters' : ['marked.js'],
199 'IPython.nbformat' : [
200 'IPython.nbformat' : [
200 'tests/*.ipynb',
201 'tests/*.ipynb',
201 'v3/nbformat.v3.schema.json',
202 'v3/nbformat.v3.schema.json',
202 'v4/nbformat.v4.schema.json',
203 'v4/nbformat.v4.schema.json',
203 ],
204 ],
204 'IPython.kernel': ['resources/*.*'],
205 'IPython.kernel': ['resources/*.*'],
205 }
206 }
206
207
207 return package_data
208 return package_data
208
209
209
210
210 def check_package_data(package_data):
211 def check_package_data(package_data):
211 """verify that package_data globs make sense"""
212 """verify that package_data globs make sense"""
212 print("checking package data")
213 print("checking package data")
213 for pkg, data in package_data.items():
214 for pkg, data in package_data.items():
214 pkg_root = pjoin(*pkg.split('.'))
215 pkg_root = pjoin(*pkg.split('.'))
215 for d in data:
216 for d in data:
216 path = pjoin(pkg_root, d)
217 path = pjoin(pkg_root, d)
217 if '*' in path:
218 if '*' in path:
218 assert len(glob(path)) > 0, "No files match pattern %s" % path
219 assert len(glob(path)) > 0, "No files match pattern %s" % path
219 else:
220 else:
220 assert os.path.exists(path), "Missing package data: %s" % path
221 assert os.path.exists(path), "Missing package data: %s" % path
221
222
222
223
223 def check_package_data_first(command):
224 def check_package_data_first(command):
224 """decorator for checking package_data before running a given command
225 """decorator for checking package_data before running a given command
225
226
226 Probably only needs to wrap build_py
227 Probably only needs to wrap build_py
227 """
228 """
228 class DecoratedCommand(command):
229 class DecoratedCommand(command):
229 def run(self):
230 def run(self):
230 check_package_data(self.package_data)
231 check_package_data(self.package_data)
231 command.run(self)
232 command.run(self)
232 return DecoratedCommand
233 return DecoratedCommand
233
234
234
235
235 #---------------------------------------------------------------------------
236 #---------------------------------------------------------------------------
236 # Find data files
237 # Find data files
237 #---------------------------------------------------------------------------
238 #---------------------------------------------------------------------------
238
239
239 def make_dir_struct(tag,base,out_base):
240 def make_dir_struct(tag,base,out_base):
240 """Make the directory structure of all files below a starting dir.
241 """Make the directory structure of all files below a starting dir.
241
242
242 This is just a convenience routine to help build a nested directory
243 This is just a convenience routine to help build a nested directory
243 hierarchy because distutils is too stupid to do this by itself.
244 hierarchy because distutils is too stupid to do this by itself.
244
245
245 XXX - this needs a proper docstring!
246 XXX - this needs a proper docstring!
246 """
247 """
247
248
248 # we'll use these a lot below
249 # we'll use these a lot below
249 lbase = len(base)
250 lbase = len(base)
250 pathsep = os.path.sep
251 pathsep = os.path.sep
251 lpathsep = len(pathsep)
252 lpathsep = len(pathsep)
252
253
253 out = []
254 out = []
254 for (dirpath,dirnames,filenames) in os.walk(base):
255 for (dirpath,dirnames,filenames) in os.walk(base):
255 # we need to strip out the dirpath from the base to map it to the
256 # we need to strip out the dirpath from the base to map it to the
256 # output (installation) path. This requires possibly stripping the
257 # output (installation) path. This requires possibly stripping the
257 # path separator, because otherwise pjoin will not work correctly
258 # path separator, because otherwise pjoin will not work correctly
258 # (pjoin('foo/','/bar') returns '/bar').
259 # (pjoin('foo/','/bar') returns '/bar').
259
260
260 dp_eff = dirpath[lbase:]
261 dp_eff = dirpath[lbase:]
261 if dp_eff.startswith(pathsep):
262 if dp_eff.startswith(pathsep):
262 dp_eff = dp_eff[lpathsep:]
263 dp_eff = dp_eff[lpathsep:]
263 # The output path must be anchored at the out_base marker
264 # The output path must be anchored at the out_base marker
264 out_path = pjoin(out_base,dp_eff)
265 out_path = pjoin(out_base,dp_eff)
265 # Now we can generate the final filenames. Since os.walk only produces
266 # Now we can generate the final filenames. Since os.walk only produces
266 # filenames, we must join back with the dirpath to get full valid file
267 # filenames, we must join back with the dirpath to get full valid file
267 # paths:
268 # paths:
268 pfiles = [pjoin(dirpath,f) for f in filenames]
269 pfiles = [pjoin(dirpath,f) for f in filenames]
269 # Finally, generate the entry we need, which is a pari of (output
270 # Finally, generate the entry we need, which is a pari of (output
270 # path, files) for use as a data_files parameter in install_data.
271 # path, files) for use as a data_files parameter in install_data.
271 out.append((out_path, pfiles))
272 out.append((out_path, pfiles))
272
273
273 return out
274 return out
274
275
275
276
276 def find_data_files():
277 def find_data_files():
277 """
278 """
278 Find IPython's data_files.
279 Find IPython's data_files.
279
280
280 Just man pages at this point.
281 Just man pages at this point.
281 """
282 """
282
283
283 manpagebase = pjoin('share', 'man', 'man1')
284 manpagebase = pjoin('share', 'man', 'man1')
284
285
285 # Simple file lists can be made by hand
286 # Simple file lists can be made by hand
286 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
287 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
287 if not manpages:
288 if not manpages:
288 # When running from a source tree, the manpages aren't gzipped
289 # When running from a source tree, the manpages aren't gzipped
289 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
290 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
290
291
291 # And assemble the entire output list
292 # And assemble the entire output list
292 data_files = [ (manpagebase, manpages) ]
293 data_files = [ (manpagebase, manpages) ]
293
294
294 return data_files
295 return data_files
295
296
296
297
297 def make_man_update_target(manpage):
298 def make_man_update_target(manpage):
298 """Return a target_update-compliant tuple for the given manpage.
299 """Return a target_update-compliant tuple for the given manpage.
299
300
300 Parameters
301 Parameters
301 ----------
302 ----------
302 manpage : string
303 manpage : string
303 Name of the manpage, must include the section number (trailing number).
304 Name of the manpage, must include the section number (trailing number).
304
305
305 Example
306 Example
306 -------
307 -------
307
308
308 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
309 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
309 ('docs/man/ipython.1.gz',
310 ('docs/man/ipython.1.gz',
310 ['docs/man/ipython.1'],
311 ['docs/man/ipython.1'],
311 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
312 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
312 """
313 """
313 man_dir = pjoin('docs', 'man')
314 man_dir = pjoin('docs', 'man')
314 manpage_gz = manpage + '.gz'
315 manpage_gz = manpage + '.gz'
315 manpath = pjoin(man_dir, manpage)
316 manpath = pjoin(man_dir, manpage)
316 manpath_gz = pjoin(man_dir, manpage_gz)
317 manpath_gz = pjoin(man_dir, manpage_gz)
317 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
318 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
318 locals() )
319 locals() )
319 return (manpath_gz, [manpath], gz_cmd)
320 return (manpath_gz, [manpath], gz_cmd)
320
321
321 # The two functions below are copied from IPython.utils.path, so we don't need
322 # The two functions below are copied from IPython.utils.path, so we don't need
322 # to import IPython during setup, which fails on Python 3.
323 # to import IPython during setup, which fails on Python 3.
323
324
324 def target_outdated(target,deps):
325 def target_outdated(target,deps):
325 """Determine whether a target is out of date.
326 """Determine whether a target is out of date.
326
327
327 target_outdated(target,deps) -> 1/0
328 target_outdated(target,deps) -> 1/0
328
329
329 deps: list of filenames which MUST exist.
330 deps: list of filenames which MUST exist.
330 target: single filename which may or may not exist.
331 target: single filename which may or may not exist.
331
332
332 If target doesn't exist or is older than any file listed in deps, return
333 If target doesn't exist or is older than any file listed in deps, return
333 true, otherwise return false.
334 true, otherwise return false.
334 """
335 """
335 try:
336 try:
336 target_time = os.path.getmtime(target)
337 target_time = os.path.getmtime(target)
337 except os.error:
338 except os.error:
338 return 1
339 return 1
339 for dep in deps:
340 for dep in deps:
340 dep_time = os.path.getmtime(dep)
341 dep_time = os.path.getmtime(dep)
341 if dep_time > target_time:
342 if dep_time > target_time:
342 #print "For target",target,"Dep failed:",dep # dbg
343 #print "For target",target,"Dep failed:",dep # dbg
343 #print "times (dep,tar):",dep_time,target_time # dbg
344 #print "times (dep,tar):",dep_time,target_time # dbg
344 return 1
345 return 1
345 return 0
346 return 0
346
347
347
348
348 def target_update(target,deps,cmd):
349 def target_update(target,deps,cmd):
349 """Update a target with a given command given a list of dependencies.
350 """Update a target with a given command given a list of dependencies.
350
351
351 target_update(target,deps,cmd) -> runs cmd if target is outdated.
352 target_update(target,deps,cmd) -> runs cmd if target is outdated.
352
353
353 This is just a wrapper around target_outdated() which calls the given
354 This is just a wrapper around target_outdated() which calls the given
354 command if target is outdated."""
355 command if target is outdated."""
355
356
356 if target_outdated(target,deps):
357 if target_outdated(target,deps):
357 os.system(cmd)
358 os.system(cmd)
358
359
359 #---------------------------------------------------------------------------
360 #---------------------------------------------------------------------------
360 # Find scripts
361 # Find scripts
361 #---------------------------------------------------------------------------
362 #---------------------------------------------------------------------------
362
363
363 def find_entry_points():
364 def find_entry_points():
364 """Defines the command line entry points for IPython
365 """Defines the command line entry points for IPython
365
366
366 This always uses setuptools-style entry points. When setuptools is not in
367 This always uses setuptools-style entry points. When setuptools is not in
367 use, our own build_scripts_entrypt class below parses these and builds
368 use, our own build_scripts_entrypt class below parses these and builds
368 command line scripts.
369 command line scripts.
369
370
370 Each of our entry points gets both a plain name, e.g. ipython, and one
371 Each of our entry points gets both a plain name, e.g. ipython, and one
371 suffixed with the Python major version number, e.g. ipython3.
372 suffixed with the Python major version number, e.g. ipython3.
372 """
373 """
373 ep = [
374 ep = [
374 'ipython%s = IPython:start_ipython',
375 'ipython%s = IPython:start_ipython',
375 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
376 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
376 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
377 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
377 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
378 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
378 'iptest%s = IPython.testing.iptestcontroller:main',
379 'iptest%s = IPython.testing.iptestcontroller:main',
379 ]
380 ]
380 suffix = str(sys.version_info[0])
381 suffix = str(sys.version_info[0])
381 return [e % '' for e in ep] + [e % suffix for e in ep]
382 return [e % '' for e in ep] + [e % suffix for e in ep]
382
383
383 script_src = """#!{executable}
384 script_src = """#!{executable}
384 # This script was automatically generated by setup.py
385 # This script was automatically generated by setup.py
385 if __name__ == '__main__':
386 if __name__ == '__main__':
386 from {mod} import {func}
387 from {mod} import {func}
387 {func}()
388 {func}()
388 """
389 """
389
390
390 class build_scripts_entrypt(build_scripts):
391 class build_scripts_entrypt(build_scripts):
391 """Build the command line scripts
392 """Build the command line scripts
392
393
393 Parse setuptools style entry points and write simple scripts to run the
394 Parse setuptools style entry points and write simple scripts to run the
394 target functions.
395 target functions.
395
396
396 On Windows, this also creates .cmd wrappers for the scripts so that you can
397 On Windows, this also creates .cmd wrappers for the scripts so that you can
397 easily launch them from a command line.
398 easily launch them from a command line.
398 """
399 """
399 def run(self):
400 def run(self):
400 self.mkpath(self.build_dir)
401 self.mkpath(self.build_dir)
401 outfiles = []
402 outfiles = []
402 for script in find_entry_points():
403 for script in find_entry_points():
403 name, entrypt = script.split('=')
404 name, entrypt = script.split('=')
404 name = name.strip()
405 name = name.strip()
405 entrypt = entrypt.strip()
406 entrypt = entrypt.strip()
406 outfile = os.path.join(self.build_dir, name)
407 outfile = os.path.join(self.build_dir, name)
407 outfiles.append(outfile)
408 outfiles.append(outfile)
408 print('Writing script to', outfile)
409 print('Writing script to', outfile)
409
410
410 mod, func = entrypt.split(':')
411 mod, func = entrypt.split(':')
411 with open(outfile, 'w') as f:
412 with open(outfile, 'w') as f:
412 f.write(script_src.format(executable=sys.executable,
413 f.write(script_src.format(executable=sys.executable,
413 mod=mod, func=func))
414 mod=mod, func=func))
414
415
415 if sys.platform == 'win32':
416 if sys.platform == 'win32':
416 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
417 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
417 # command line
418 # command line
418 cmd_file = os.path.join(self.build_dir, name + '.cmd')
419 cmd_file = os.path.join(self.build_dir, name + '.cmd')
419 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
420 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
420 python=sys.executable, script=name)
421 python=sys.executable, script=name)
421 log.info("Writing %s wrapper script" % cmd_file)
422 log.info("Writing %s wrapper script" % cmd_file)
422 with open(cmd_file, 'w') as f:
423 with open(cmd_file, 'w') as f:
423 f.write(cmd)
424 f.write(cmd)
424
425
425 return outfiles, outfiles
426 return outfiles, outfiles
426
427
427 class install_lib_symlink(Command):
428 class install_lib_symlink(Command):
428 user_options = [
429 user_options = [
429 ('install-dir=', 'd', "directory to install to"),
430 ('install-dir=', 'd', "directory to install to"),
430 ]
431 ]
431
432
432 def initialize_options(self):
433 def initialize_options(self):
433 self.install_dir = None
434 self.install_dir = None
434
435
435 def finalize_options(self):
436 def finalize_options(self):
436 self.set_undefined_options('symlink',
437 self.set_undefined_options('symlink',
437 ('install_lib', 'install_dir'),
438 ('install_lib', 'install_dir'),
438 )
439 )
439
440
440 def run(self):
441 def run(self):
441 if sys.platform == 'win32':
442 if sys.platform == 'win32':
442 raise Exception("This doesn't work on Windows.")
443 raise Exception("This doesn't work on Windows.")
443 pkg = os.path.join(os.getcwd(), 'IPython')
444 pkg = os.path.join(os.getcwd(), 'IPython')
444 dest = os.path.join(self.install_dir, 'IPython')
445 dest = os.path.join(self.install_dir, 'IPython')
445 if os.path.islink(dest):
446 if os.path.islink(dest):
446 print('removing existing symlink at %s' % dest)
447 print('removing existing symlink at %s' % dest)
447 os.unlink(dest)
448 os.unlink(dest)
448 print('symlinking %s -> %s' % (pkg, dest))
449 print('symlinking %s -> %s' % (pkg, dest))
449 os.symlink(pkg, dest)
450 os.symlink(pkg, dest)
450
451
451 class unsymlink(install):
452 class unsymlink(install):
452 def run(self):
453 def run(self):
453 dest = os.path.join(self.install_lib, 'IPython')
454 dest = os.path.join(self.install_lib, 'IPython')
454 if os.path.islink(dest):
455 if os.path.islink(dest):
455 print('removing symlink at %s' % dest)
456 print('removing symlink at %s' % dest)
456 os.unlink(dest)
457 os.unlink(dest)
457 else:
458 else:
458 print('No symlink exists at %s' % dest)
459 print('No symlink exists at %s' % dest)
459
460
460 class install_symlinked(install):
461 class install_symlinked(install):
461 def run(self):
462 def run(self):
462 if sys.platform == 'win32':
463 if sys.platform == 'win32':
463 raise Exception("This doesn't work on Windows.")
464 raise Exception("This doesn't work on Windows.")
464
465
465 # Run all sub-commands (at least those that need to be run)
466 # Run all sub-commands (at least those that need to be run)
466 for cmd_name in self.get_sub_commands():
467 for cmd_name in self.get_sub_commands():
467 self.run_command(cmd_name)
468 self.run_command(cmd_name)
468
469
469 # 'sub_commands': a list of commands this command might have to run to
470 # 'sub_commands': a list of commands this command might have to run to
470 # get its work done. See cmd.py for more info.
471 # get its work done. See cmd.py for more info.
471 sub_commands = [('install_lib_symlink', lambda self:True),
472 sub_commands = [('install_lib_symlink', lambda self:True),
472 ('install_scripts_sym', lambda self:True),
473 ('install_scripts_sym', lambda self:True),
473 ]
474 ]
474
475
475 class install_scripts_for_symlink(install_scripts):
476 class install_scripts_for_symlink(install_scripts):
476 """Redefined to get options from 'symlink' instead of 'install'.
477 """Redefined to get options from 'symlink' instead of 'install'.
477
478
478 I love distutils almost as much as I love setuptools.
479 I love distutils almost as much as I love setuptools.
479 """
480 """
480 def finalize_options(self):
481 def finalize_options(self):
481 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
482 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
482 self.set_undefined_options('symlink',
483 self.set_undefined_options('symlink',
483 ('install_scripts', 'install_dir'),
484 ('install_scripts', 'install_dir'),
484 ('force', 'force'),
485 ('force', 'force'),
485 ('skip_build', 'skip_build'),
486 ('skip_build', 'skip_build'),
486 )
487 )
487
488
488 #---------------------------------------------------------------------------
489 #---------------------------------------------------------------------------
489 # Verify all dependencies
490 # Verify all dependencies
490 #---------------------------------------------------------------------------
491 #---------------------------------------------------------------------------
491
492
492 def check_for_dependencies():
493 def check_for_dependencies():
493 """Check for IPython's dependencies.
494 """Check for IPython's dependencies.
494
495
495 This function should NOT be called if running under setuptools!
496 This function should NOT be called if running under setuptools!
496 """
497 """
497 from setupext.setupext import (
498 from setupext.setupext import (
498 print_line, print_raw, print_status,
499 print_line, print_raw, print_status,
499 check_for_sphinx, check_for_pygments,
500 check_for_sphinx, check_for_pygments,
500 check_for_nose, check_for_pexpect,
501 check_for_nose, check_for_pexpect,
501 check_for_pyzmq, check_for_readline,
502 check_for_pyzmq, check_for_readline,
502 check_for_jinja2, check_for_tornado
503 check_for_jinja2, check_for_tornado
503 )
504 )
504 print_line()
505 print_line()
505 print_raw("BUILDING IPYTHON")
506 print_raw("BUILDING IPYTHON")
506 print_status('python', sys.version)
507 print_status('python', sys.version)
507 print_status('platform', sys.platform)
508 print_status('platform', sys.platform)
508 if sys.platform == 'win32':
509 if sys.platform == 'win32':
509 print_status('Windows version', sys.getwindowsversion())
510 print_status('Windows version', sys.getwindowsversion())
510
511
511 print_raw("")
512 print_raw("")
512 print_raw("OPTIONAL DEPENDENCIES")
513 print_raw("OPTIONAL DEPENDENCIES")
513
514
514 check_for_sphinx()
515 check_for_sphinx()
515 check_for_pygments()
516 check_for_pygments()
516 check_for_nose()
517 check_for_nose()
517 if os.name == 'posix':
518 if os.name == 'posix':
518 check_for_pexpect()
519 check_for_pexpect()
519 check_for_pyzmq()
520 check_for_pyzmq()
520 check_for_tornado()
521 check_for_tornado()
521 check_for_readline()
522 check_for_readline()
522 check_for_jinja2()
523 check_for_jinja2()
523
524
524 #---------------------------------------------------------------------------
525 #---------------------------------------------------------------------------
525 # VCS related
526 # VCS related
526 #---------------------------------------------------------------------------
527 #---------------------------------------------------------------------------
527
528
528 # utils.submodule has checks for submodule status
529 # utils.submodule has checks for submodule status
529 execfile(pjoin('IPython','utils','submodule.py'), globals())
530 execfile(pjoin('IPython','utils','submodule.py'), globals())
530
531
531 class UpdateSubmodules(Command):
532 class UpdateSubmodules(Command):
532 """Update git submodules
533 """Update git submodules
533
534
534 IPython's external javascript dependencies live in a separate repo.
535 IPython's external javascript dependencies live in a separate repo.
535 """
536 """
536 description = "Update git submodules"
537 description = "Update git submodules"
537 user_options = []
538 user_options = []
538
539
539 def initialize_options(self):
540 def initialize_options(self):
540 pass
541 pass
541
542
542 def finalize_options(self):
543 def finalize_options(self):
543 pass
544 pass
544
545
545 def run(self):
546 def run(self):
546 failure = False
547 failure = False
547 try:
548 try:
548 self.spawn('git submodule init'.split())
549 self.spawn('git submodule init'.split())
549 self.spawn('git submodule update --recursive'.split())
550 self.spawn('git submodule update --recursive'.split())
550 except Exception as e:
551 except Exception as e:
551 failure = e
552 failure = e
552 print(e)
553 print(e)
553
554
554 if not check_submodule_status(repo_root) == 'clean':
555 if not check_submodule_status(repo_root) == 'clean':
555 print("submodules could not be checked out")
556 print("submodules could not be checked out")
556 sys.exit(1)
557 sys.exit(1)
557
558
558
559
559 def git_prebuild(pkg_dir, build_cmd=build_py):
560 def git_prebuild(pkg_dir, build_cmd=build_py):
560 """Return extended build or sdist command class for recording commit
561 """Return extended build or sdist command class for recording commit
561
562
562 records git commit in IPython.utils._sysinfo.commit
563 records git commit in IPython.utils._sysinfo.commit
563
564
564 for use in IPython.utils.sysinfo.sys_info() calls after installation.
565 for use in IPython.utils.sysinfo.sys_info() calls after installation.
565
566
566 Also ensures that submodules exist prior to running
567 Also ensures that submodules exist prior to running
567 """
568 """
568
569
569 class MyBuildPy(build_cmd):
570 class MyBuildPy(build_cmd):
570 ''' Subclass to write commit data into installation tree '''
571 ''' Subclass to write commit data into installation tree '''
571 def run(self):
572 def run(self):
572 build_cmd.run(self)
573 build_cmd.run(self)
573 # this one will only fire for build commands
574 # this one will only fire for build commands
574 if hasattr(self, 'build_lib'):
575 if hasattr(self, 'build_lib'):
575 self._record_commit(self.build_lib)
576 self._record_commit(self.build_lib)
576
577
577 def make_release_tree(self, base_dir, files):
578 def make_release_tree(self, base_dir, files):
578 # this one will fire for sdist
579 # this one will fire for sdist
579 build_cmd.make_release_tree(self, base_dir, files)
580 build_cmd.make_release_tree(self, base_dir, files)
580 self._record_commit(base_dir)
581 self._record_commit(base_dir)
581
582
582 def _record_commit(self, base_dir):
583 def _record_commit(self, base_dir):
583 import subprocess
584 import subprocess
584 proc = subprocess.Popen('git rev-parse --short HEAD',
585 proc = subprocess.Popen('git rev-parse --short HEAD',
585 stdout=subprocess.PIPE,
586 stdout=subprocess.PIPE,
586 stderr=subprocess.PIPE,
587 stderr=subprocess.PIPE,
587 shell=True)
588 shell=True)
588 repo_commit, _ = proc.communicate()
589 repo_commit, _ = proc.communicate()
589 repo_commit = repo_commit.strip().decode("ascii")
590 repo_commit = repo_commit.strip().decode("ascii")
590
591
591 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
592 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
592 if os.path.isfile(out_pth) and not repo_commit:
593 if os.path.isfile(out_pth) and not repo_commit:
593 # nothing to write, don't clobber
594 # nothing to write, don't clobber
594 return
595 return
595
596
596 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
597 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
597
598
598 # remove to avoid overwriting original via hard link
599 # remove to avoid overwriting original via hard link
599 try:
600 try:
600 os.remove(out_pth)
601 os.remove(out_pth)
601 except (IOError, OSError):
602 except (IOError, OSError):
602 pass
603 pass
603 with open(out_pth, 'w') as out_file:
604 with open(out_pth, 'w') as out_file:
604 out_file.writelines([
605 out_file.writelines([
605 '# GENERATED BY setup.py\n',
606 '# GENERATED BY setup.py\n',
606 'commit = u"%s"\n' % repo_commit,
607 'commit = u"%s"\n' % repo_commit,
607 ])
608 ])
608 return require_submodules(MyBuildPy)
609 return require_submodules(MyBuildPy)
609
610
610
611
611 def require_submodules(command):
612 def require_submodules(command):
612 """decorator for instructing a command to check for submodules before running"""
613 """decorator for instructing a command to check for submodules before running"""
613 class DecoratedCommand(command):
614 class DecoratedCommand(command):
614 def run(self):
615 def run(self):
615 if not check_submodule_status(repo_root) == 'clean':
616 if not check_submodule_status(repo_root) == 'clean':
616 print("submodules missing! Run `setup.py submodule` and try again")
617 print("submodules missing! Run `setup.py submodule` and try again")
617 sys.exit(1)
618 sys.exit(1)
618 command.run(self)
619 command.run(self)
619 return DecoratedCommand
620 return DecoratedCommand
620
621
621 #---------------------------------------------------------------------------
622 #---------------------------------------------------------------------------
622 # bdist related
623 # bdist related
623 #---------------------------------------------------------------------------
624 #---------------------------------------------------------------------------
624
625
625 def get_bdist_wheel():
626 def get_bdist_wheel():
626 """Construct bdist_wheel command for building wheels
627 """Construct bdist_wheel command for building wheels
627
628
628 Constructs py2-none-any tag, instead of py2.7-none-any
629 Constructs py2-none-any tag, instead of py2.7-none-any
629 """
630 """
630 class RequiresWheel(Command):
631 class RequiresWheel(Command):
631 description = "Dummy command for missing bdist_wheel"
632 description = "Dummy command for missing bdist_wheel"
632 user_options = []
633 user_options = []
633
634
634 def initialize_options(self):
635 def initialize_options(self):
635 pass
636 pass
636
637
637 def finalize_options(self):
638 def finalize_options(self):
638 pass
639 pass
639
640
640 def run(self):
641 def run(self):
641 print("bdist_wheel requires the wheel package")
642 print("bdist_wheel requires the wheel package")
642 sys.exit(1)
643 sys.exit(1)
643
644
644 if 'setuptools' not in sys.modules:
645 if 'setuptools' not in sys.modules:
645 return RequiresWheel
646 return RequiresWheel
646 else:
647 else:
647 try:
648 try:
648 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
649 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
649 except ImportError:
650 except ImportError:
650 return RequiresWheel
651 return RequiresWheel
651
652
652 class bdist_wheel_tag(bdist_wheel):
653 class bdist_wheel_tag(bdist_wheel):
653
654
654 def add_requirements(self, metadata_path):
655 def add_requirements(self, metadata_path):
655 """transform platform-dependent requirements"""
656 """transform platform-dependent requirements"""
656 pkg_info = read_pkg_info(metadata_path)
657 pkg_info = read_pkg_info(metadata_path)
657 # pkg_info is an email.Message object (?!)
658 # pkg_info is an email.Message object (?!)
658 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
659 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
659 # and transform them to conditionals
660 # and transform them to conditionals
660 requires = pkg_info.get_all('Requires-Dist')
661 requires = pkg_info.get_all('Requires-Dist')
661 del pkg_info['Requires-Dist']
662 del pkg_info['Requires-Dist']
662 def _remove_startswith(lis, prefix):
663 def _remove_startswith(lis, prefix):
663 """like list.remove, but with startswith instead of =="""
664 """like list.remove, but with startswith instead of =="""
664 found = False
665 found = False
665 for idx, item in enumerate(lis):
666 for idx, item in enumerate(lis):
666 if item.startswith(prefix):
667 if item.startswith(prefix):
667 found = True
668 found = True
668 break
669 break
669 if found:
670 if found:
670 lis.pop(idx)
671 lis.pop(idx)
671
672
672 for pkg in ("gnureadline", "pyreadline", "mock"):
673 for pkg in ("gnureadline", "pyreadline", "mock"):
673 _remove_startswith(requires, pkg)
674 _remove_startswith(requires, pkg)
674 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
675 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
675 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
676 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
676 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
677 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
677 requires.append("mock; extra == 'test' and python_version < '3.3'")
678 requires.append("mock; extra == 'test' and python_version < '3.3'")
678 for r in requires:
679 for r in requires:
679 pkg_info['Requires-Dist'] = r
680 pkg_info['Requires-Dist'] = r
680 write_pkg_info(metadata_path, pkg_info)
681 write_pkg_info(metadata_path, pkg_info)
681
682
682 return bdist_wheel_tag
683 return bdist_wheel_tag
683
684
684 #---------------------------------------------------------------------------
685 #---------------------------------------------------------------------------
685 # Notebook related
686 # Notebook related
686 #---------------------------------------------------------------------------
687 #---------------------------------------------------------------------------
687
688
688 class CompileCSS(Command):
689 class CompileCSS(Command):
689 """Recompile Notebook CSS
690 """Recompile Notebook CSS
690
691
691 Regenerate the compiled CSS from LESS sources.
692 Regenerate the compiled CSS from LESS sources.
692
693
693 Requires various dev dependencies, such as invoke and lessc.
694 Requires various dev dependencies, such as invoke and lessc.
694 """
695 """
695 description = "Recompile Notebook CSS"
696 description = "Recompile Notebook CSS"
696 user_options = [
697 user_options = [
697 ('minify', 'x', "minify CSS"),
698 ('minify', 'x', "minify CSS"),
698 ('force', 'f', "force recompilation of CSS"),
699 ('force', 'f', "force recompilation of CSS"),
699 ]
700 ]
700
701
701 def initialize_options(self):
702 def initialize_options(self):
702 self.minify = False
703 self.minify = False
703 self.force = False
704 self.force = False
704
705
705 def finalize_options(self):
706 def finalize_options(self):
706 self.minify = bool(self.minify)
707 self.minify = bool(self.minify)
707 self.force = bool(self.force)
708 self.force = bool(self.force)
708
709
709 def run(self):
710 def run(self):
710 cmd = ['invoke', 'css']
711 cmd = ['invoke', 'css']
711 if self.minify:
712 if self.minify:
712 cmd.append('--minify')
713 cmd.append('--minify')
713 if self.force:
714 if self.force:
714 cmd.append('--force')
715 cmd.append('--force')
715 check_call(cmd, cwd=pjoin(repo_root, "IPython", "html"))
716 try:
717 p = Popen(cmd, cwd=pjoin(repo_root, "IPython", "html"), stderr=PIPE)
718 except OSError:
719 raise DistutilsExecError("invoke is required to rebuild css (pip install invoke)")
720 out, err = p.communicate()
721 if p.returncode:
722 if sys.version_info[0] >= 3:
723 err = err.decode('utf8', 'replace')
724 raise DistutilsExecError(err.strip())
716
725
717
726
718 class JavascriptVersion(Command):
727 class JavascriptVersion(Command):
719 """write the javascript version to notebook javascript"""
728 """write the javascript version to notebook javascript"""
720 description = "Write IPython version to javascript"
729 description = "Write IPython version to javascript"
721 user_options = []
730 user_options = []
722
731
723 def initialize_options(self):
732 def initialize_options(self):
724 pass
733 pass
725
734
726 def finalize_options(self):
735 def finalize_options(self):
727 pass
736 pass
728
737
729 def run(self):
738 def run(self):
730 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
739 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
731 with open(nsfile) as f:
740 with open(nsfile) as f:
732 lines = f.readlines()
741 lines = f.readlines()
733 with open(nsfile, 'w') as f:
742 with open(nsfile, 'w') as f:
734 for line in lines:
743 for line in lines:
735 if line.startswith("IPython.version"):
744 if line.startswith("IPython.version"):
736 line = 'IPython.version = "{0}";\n'.format(version)
745 line = 'IPython.version = "{0}";\n'.format(version)
737 f.write(line)
746 f.write(line)
738
747
739
748
740 def css_js_prerelease(command, strict=True):
749 def css_js_prerelease(command, strict=True):
741 """decorator for building js/minified css prior to a release"""
750 """decorator for building js/minified css prior to a release"""
742 class DecoratedCommand(command):
751 class DecoratedCommand(command):
743 def run(self):
752 def run(self):
744 self.distribution.run_command('jsversion')
753 self.distribution.run_command('jsversion')
745 css = self.distribution.get_command_obj('css')
754 css = self.distribution.get_command_obj('css')
746 css.minify = True
755 css.minify = True
747 try:
756 try:
748 self.distribution.run_command('css')
757 self.distribution.run_command('css')
749 except Exception as e:
758 except Exception as e:
750 if strict:
759 if strict:
751 raise
760 raise
752 else:
761 else:
753 log.warn("Failed to build css sourcemaps: %s" % e)
762 log.warn("rebuilding css and sourcemaps failed (not a problem)")
763 log.warn(str(e))
754 command.run(self)
764 command.run(self)
755 return DecoratedCommand
765 return DecoratedCommand
General Comments 0
You need to be logged in to leave comments. Login now