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