##// END OF EJS Templates
Remove Qt console package_data from setup...
Thomas Kluyver -
Show More
@@ -1,756 +1,755 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'],
193 'IPython.nbconvert' : nbconvert_templates +
192 'IPython.nbconvert' : nbconvert_templates +
194 [
193 [
195 'tests/files/*.*',
194 'tests/files/*.*',
196 'exporters/tests/files/*.*',
195 'exporters/tests/files/*.*',
197 'preprocessors/tests/files/*.*',
196 'preprocessors/tests/files/*.*',
198 ],
197 ],
199 'IPython.nbconvert.filters' : ['marked.js'],
198 'IPython.nbconvert.filters' : ['marked.js'],
200 'IPython.nbformat' : [
199 'IPython.nbformat' : [
201 'tests/*.ipynb',
200 'tests/*.ipynb',
202 'v3/nbformat.v3.schema.json',
201 'v3/nbformat.v3.schema.json',
203 'v4/nbformat.v4.schema.json',
202 'v4/nbformat.v4.schema.json',
204 ],
203 ],
205 'IPython.kernel': ['resources/*.*'],
204 'IPython.kernel': ['resources/*.*'],
206 }
205 }
207
206
208 return package_data
207 return package_data
209
208
210
209
211 def check_package_data(package_data):
210 def check_package_data(package_data):
212 """verify that package_data globs make sense"""
211 """verify that package_data globs make sense"""
213 print("checking package data")
212 print("checking package data")
214 for pkg, data in package_data.items():
213 for pkg, data in package_data.items():
215 pkg_root = pjoin(*pkg.split('.'))
214 pkg_root = pjoin(*pkg.split('.'))
216 for d in data:
215 for d in data:
217 path = pjoin(pkg_root, d)
216 path = pjoin(pkg_root, d)
218 if '*' in path:
217 if '*' in path:
219 assert len(glob(path)) > 0, "No files match pattern %s" % path
218 assert len(glob(path)) > 0, "No files match pattern %s" % path
220 else:
219 else:
221 assert os.path.exists(path), "Missing package data: %s" % path
220 assert os.path.exists(path), "Missing package data: %s" % path
222
221
223
222
224 def check_package_data_first(command):
223 def check_package_data_first(command):
225 """decorator for checking package_data before running a given command
224 """decorator for checking package_data before running a given command
226
225
227 Probably only needs to wrap build_py
226 Probably only needs to wrap build_py
228 """
227 """
229 class DecoratedCommand(command):
228 class DecoratedCommand(command):
230 def run(self):
229 def run(self):
231 check_package_data(self.package_data)
230 check_package_data(self.package_data)
232 command.run(self)
231 command.run(self)
233 return DecoratedCommand
232 return DecoratedCommand
234
233
235
234
236 #---------------------------------------------------------------------------
235 #---------------------------------------------------------------------------
237 # Find data files
236 # Find data files
238 #---------------------------------------------------------------------------
237 #---------------------------------------------------------------------------
239
238
240 def make_dir_struct(tag,base,out_base):
239 def make_dir_struct(tag,base,out_base):
241 """Make the directory structure of all files below a starting dir.
240 """Make the directory structure of all files below a starting dir.
242
241
243 This is just a convenience routine to help build a nested directory
242 This is just a convenience routine to help build a nested directory
244 hierarchy because distutils is too stupid to do this by itself.
243 hierarchy because distutils is too stupid to do this by itself.
245
244
246 XXX - this needs a proper docstring!
245 XXX - this needs a proper docstring!
247 """
246 """
248
247
249 # we'll use these a lot below
248 # we'll use these a lot below
250 lbase = len(base)
249 lbase = len(base)
251 pathsep = os.path.sep
250 pathsep = os.path.sep
252 lpathsep = len(pathsep)
251 lpathsep = len(pathsep)
253
252
254 out = []
253 out = []
255 for (dirpath,dirnames,filenames) in os.walk(base):
254 for (dirpath,dirnames,filenames) in os.walk(base):
256 # we need to strip out the dirpath from the base to map it to the
255 # we need to strip out the dirpath from the base to map it to the
257 # output (installation) path. This requires possibly stripping the
256 # output (installation) path. This requires possibly stripping the
258 # path separator, because otherwise pjoin will not work correctly
257 # path separator, because otherwise pjoin will not work correctly
259 # (pjoin('foo/','/bar') returns '/bar').
258 # (pjoin('foo/','/bar') returns '/bar').
260
259
261 dp_eff = dirpath[lbase:]
260 dp_eff = dirpath[lbase:]
262 if dp_eff.startswith(pathsep):
261 if dp_eff.startswith(pathsep):
263 dp_eff = dp_eff[lpathsep:]
262 dp_eff = dp_eff[lpathsep:]
264 # The output path must be anchored at the out_base marker
263 # The output path must be anchored at the out_base marker
265 out_path = pjoin(out_base,dp_eff)
264 out_path = pjoin(out_base,dp_eff)
266 # Now we can generate the final filenames. Since os.walk only produces
265 # 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
266 # filenames, we must join back with the dirpath to get full valid file
268 # paths:
267 # paths:
269 pfiles = [pjoin(dirpath,f) for f in filenames]
268 pfiles = [pjoin(dirpath,f) for f in filenames]
270 # Finally, generate the entry we need, which is a pari of (output
269 # 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.
270 # path, files) for use as a data_files parameter in install_data.
272 out.append((out_path, pfiles))
271 out.append((out_path, pfiles))
273
272
274 return out
273 return out
275
274
276
275
277 def find_data_files():
276 def find_data_files():
278 """
277 """
279 Find IPython's data_files.
278 Find IPython's data_files.
280
279
281 Just man pages at this point.
280 Just man pages at this point.
282 """
281 """
283
282
284 manpagebase = pjoin('share', 'man', 'man1')
283 manpagebase = pjoin('share', 'man', 'man1')
285
284
286 # Simple file lists can be made by hand
285 # Simple file lists can be made by hand
287 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
286 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
288 if not manpages:
287 if not manpages:
289 # When running from a source tree, the manpages aren't gzipped
288 # 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)]
289 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
291
290
292 # And assemble the entire output list
291 # And assemble the entire output list
293 data_files = [ (manpagebase, manpages) ]
292 data_files = [ (manpagebase, manpages) ]
294
293
295 return data_files
294 return data_files
296
295
297
296
298 def make_man_update_target(manpage):
297 def make_man_update_target(manpage):
299 """Return a target_update-compliant tuple for the given manpage.
298 """Return a target_update-compliant tuple for the given manpage.
300
299
301 Parameters
300 Parameters
302 ----------
301 ----------
303 manpage : string
302 manpage : string
304 Name of the manpage, must include the section number (trailing number).
303 Name of the manpage, must include the section number (trailing number).
305
304
306 Example
305 Example
307 -------
306 -------
308
307
309 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
308 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
310 ('docs/man/ipython.1.gz',
309 ('docs/man/ipython.1.gz',
311 ['docs/man/ipython.1'],
310 ['docs/man/ipython.1'],
312 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
311 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
313 """
312 """
314 man_dir = pjoin('docs', 'man')
313 man_dir = pjoin('docs', 'man')
315 manpage_gz = manpage + '.gz'
314 manpage_gz = manpage + '.gz'
316 manpath = pjoin(man_dir, manpage)
315 manpath = pjoin(man_dir, manpage)
317 manpath_gz = pjoin(man_dir, manpage_gz)
316 manpath_gz = pjoin(man_dir, manpage_gz)
318 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
317 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
319 locals() )
318 locals() )
320 return (manpath_gz, [manpath], gz_cmd)
319 return (manpath_gz, [manpath], gz_cmd)
321
320
322 # The two functions below are copied from IPython.utils.path, so we don't need
321 # 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.
322 # to import IPython during setup, which fails on Python 3.
324
323
325 def target_outdated(target,deps):
324 def target_outdated(target,deps):
326 """Determine whether a target is out of date.
325 """Determine whether a target is out of date.
327
326
328 target_outdated(target,deps) -> 1/0
327 target_outdated(target,deps) -> 1/0
329
328
330 deps: list of filenames which MUST exist.
329 deps: list of filenames which MUST exist.
331 target: single filename which may or may not exist.
330 target: single filename which may or may not exist.
332
331
333 If target doesn't exist or is older than any file listed in deps, return
332 If target doesn't exist or is older than any file listed in deps, return
334 true, otherwise return false.
333 true, otherwise return false.
335 """
334 """
336 try:
335 try:
337 target_time = os.path.getmtime(target)
336 target_time = os.path.getmtime(target)
338 except os.error:
337 except os.error:
339 return 1
338 return 1
340 for dep in deps:
339 for dep in deps:
341 dep_time = os.path.getmtime(dep)
340 dep_time = os.path.getmtime(dep)
342 if dep_time > target_time:
341 if dep_time > target_time:
343 #print "For target",target,"Dep failed:",dep # dbg
342 #print "For target",target,"Dep failed:",dep # dbg
344 #print "times (dep,tar):",dep_time,target_time # dbg
343 #print "times (dep,tar):",dep_time,target_time # dbg
345 return 1
344 return 1
346 return 0
345 return 0
347
346
348
347
349 def target_update(target,deps,cmd):
348 def target_update(target,deps,cmd):
350 """Update a target with a given command given a list of dependencies.
349 """Update a target with a given command given a list of dependencies.
351
350
352 target_update(target,deps,cmd) -> runs cmd if target is outdated.
351 target_update(target,deps,cmd) -> runs cmd if target is outdated.
353
352
354 This is just a wrapper around target_outdated() which calls the given
353 This is just a wrapper around target_outdated() which calls the given
355 command if target is outdated."""
354 command if target is outdated."""
356
355
357 if target_outdated(target,deps):
356 if target_outdated(target,deps):
358 os.system(cmd)
357 os.system(cmd)
359
358
360 #---------------------------------------------------------------------------
359 #---------------------------------------------------------------------------
361 # Find scripts
360 # Find scripts
362 #---------------------------------------------------------------------------
361 #---------------------------------------------------------------------------
363
362
364 def find_entry_points():
363 def find_entry_points():
365 """Defines the command line entry points for IPython
364 """Defines the command line entry points for IPython
366
365
367 This always uses setuptools-style entry points. When setuptools is not in
366 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
367 use, our own build_scripts_entrypt class below parses these and builds
369 command line scripts.
368 command line scripts.
370
369
371 Each of our entry points gets both a plain name, e.g. ipython, and one
370 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.
371 suffixed with the Python major version number, e.g. ipython3.
373 """
372 """
374 ep = [
373 ep = [
375 'ipython%s = IPython:start_ipython',
374 'ipython%s = IPython:start_ipython',
376 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
375 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
377 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
376 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
378 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
377 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
379 'iptest%s = IPython.testing.iptestcontroller:main',
378 'iptest%s = IPython.testing.iptestcontroller:main',
380 ]
379 ]
381 suffix = str(sys.version_info[0])
380 suffix = str(sys.version_info[0])
382 return [e % '' for e in ep] + [e % suffix for e in ep]
381 return [e % '' for e in ep] + [e % suffix for e in ep]
383
382
384 script_src = """#!{executable}
383 script_src = """#!{executable}
385 # This script was automatically generated by setup.py
384 # This script was automatically generated by setup.py
386 if __name__ == '__main__':
385 if __name__ == '__main__':
387 from {mod} import {func}
386 from {mod} import {func}
388 {func}()
387 {func}()
389 """
388 """
390
389
391 class build_scripts_entrypt(build_scripts):
390 class build_scripts_entrypt(build_scripts):
392 """Build the command line scripts
391 """Build the command line scripts
393
392
394 Parse setuptools style entry points and write simple scripts to run the
393 Parse setuptools style entry points and write simple scripts to run the
395 target functions.
394 target functions.
396
395
397 On Windows, this also creates .cmd wrappers for the scripts so that you can
396 On Windows, this also creates .cmd wrappers for the scripts so that you can
398 easily launch them from a command line.
397 easily launch them from a command line.
399 """
398 """
400 def run(self):
399 def run(self):
401 self.mkpath(self.build_dir)
400 self.mkpath(self.build_dir)
402 outfiles = []
401 outfiles = []
403 for script in find_entry_points():
402 for script in find_entry_points():
404 name, entrypt = script.split('=')
403 name, entrypt = script.split('=')
405 name = name.strip()
404 name = name.strip()
406 entrypt = entrypt.strip()
405 entrypt = entrypt.strip()
407 outfile = os.path.join(self.build_dir, name)
406 outfile = os.path.join(self.build_dir, name)
408 outfiles.append(outfile)
407 outfiles.append(outfile)
409 print('Writing script to', outfile)
408 print('Writing script to', outfile)
410
409
411 mod, func = entrypt.split(':')
410 mod, func = entrypt.split(':')
412 with open(outfile, 'w') as f:
411 with open(outfile, 'w') as f:
413 f.write(script_src.format(executable=sys.executable,
412 f.write(script_src.format(executable=sys.executable,
414 mod=mod, func=func))
413 mod=mod, func=func))
415
414
416 if sys.platform == 'win32':
415 if sys.platform == 'win32':
417 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
416 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
418 # command line
417 # command line
419 cmd_file = os.path.join(self.build_dir, name + '.cmd')
418 cmd_file = os.path.join(self.build_dir, name + '.cmd')
420 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
419 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
421 python=sys.executable, script=name)
420 python=sys.executable, script=name)
422 log.info("Writing %s wrapper script" % cmd_file)
421 log.info("Writing %s wrapper script" % cmd_file)
423 with open(cmd_file, 'w') as f:
422 with open(cmd_file, 'w') as f:
424 f.write(cmd)
423 f.write(cmd)
425
424
426 return outfiles, outfiles
425 return outfiles, outfiles
427
426
428 class install_lib_symlink(Command):
427 class install_lib_symlink(Command):
429 user_options = [
428 user_options = [
430 ('install-dir=', 'd', "directory to install to"),
429 ('install-dir=', 'd', "directory to install to"),
431 ]
430 ]
432
431
433 def initialize_options(self):
432 def initialize_options(self):
434 self.install_dir = None
433 self.install_dir = None
435
434
436 def finalize_options(self):
435 def finalize_options(self):
437 self.set_undefined_options('symlink',
436 self.set_undefined_options('symlink',
438 ('install_lib', 'install_dir'),
437 ('install_lib', 'install_dir'),
439 )
438 )
440
439
441 def run(self):
440 def run(self):
442 if sys.platform == 'win32':
441 if sys.platform == 'win32':
443 raise Exception("This doesn't work on Windows.")
442 raise Exception("This doesn't work on Windows.")
444 pkg = os.path.join(os.getcwd(), 'IPython')
443 pkg = os.path.join(os.getcwd(), 'IPython')
445 dest = os.path.join(self.install_dir, 'IPython')
444 dest = os.path.join(self.install_dir, 'IPython')
446 if os.path.islink(dest):
445 if os.path.islink(dest):
447 print('removing existing symlink at %s' % dest)
446 print('removing existing symlink at %s' % dest)
448 os.unlink(dest)
447 os.unlink(dest)
449 print('symlinking %s -> %s' % (pkg, dest))
448 print('symlinking %s -> %s' % (pkg, dest))
450 os.symlink(pkg, dest)
449 os.symlink(pkg, dest)
451
450
452 class unsymlink(install):
451 class unsymlink(install):
453 def run(self):
452 def run(self):
454 dest = os.path.join(self.install_lib, 'IPython')
453 dest = os.path.join(self.install_lib, 'IPython')
455 if os.path.islink(dest):
454 if os.path.islink(dest):
456 print('removing symlink at %s' % dest)
455 print('removing symlink at %s' % dest)
457 os.unlink(dest)
456 os.unlink(dest)
458 else:
457 else:
459 print('No symlink exists at %s' % dest)
458 print('No symlink exists at %s' % dest)
460
459
461 class install_symlinked(install):
460 class install_symlinked(install):
462 def run(self):
461 def run(self):
463 if sys.platform == 'win32':
462 if sys.platform == 'win32':
464 raise Exception("This doesn't work on Windows.")
463 raise Exception("This doesn't work on Windows.")
465
464
466 # Run all sub-commands (at least those that need to be run)
465 # Run all sub-commands (at least those that need to be run)
467 for cmd_name in self.get_sub_commands():
466 for cmd_name in self.get_sub_commands():
468 self.run_command(cmd_name)
467 self.run_command(cmd_name)
469
468
470 # 'sub_commands': a list of commands this command might have to run to
469 # 'sub_commands': a list of commands this command might have to run to
471 # get its work done. See cmd.py for more info.
470 # get its work done. See cmd.py for more info.
472 sub_commands = [('install_lib_symlink', lambda self:True),
471 sub_commands = [('install_lib_symlink', lambda self:True),
473 ('install_scripts_sym', lambda self:True),
472 ('install_scripts_sym', lambda self:True),
474 ]
473 ]
475
474
476 class install_scripts_for_symlink(install_scripts):
475 class install_scripts_for_symlink(install_scripts):
477 """Redefined to get options from 'symlink' instead of 'install'.
476 """Redefined to get options from 'symlink' instead of 'install'.
478
477
479 I love distutils almost as much as I love setuptools.
478 I love distutils almost as much as I love setuptools.
480 """
479 """
481 def finalize_options(self):
480 def finalize_options(self):
482 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
481 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
483 self.set_undefined_options('symlink',
482 self.set_undefined_options('symlink',
484 ('install_scripts', 'install_dir'),
483 ('install_scripts', 'install_dir'),
485 ('force', 'force'),
484 ('force', 'force'),
486 ('skip_build', 'skip_build'),
485 ('skip_build', 'skip_build'),
487 )
486 )
488
487
489 #---------------------------------------------------------------------------
488 #---------------------------------------------------------------------------
490 # Verify all dependencies
489 # Verify all dependencies
491 #---------------------------------------------------------------------------
490 #---------------------------------------------------------------------------
492
491
493 def check_for_readline():
492 def check_for_readline():
494 """Check for GNU readline"""
493 """Check for GNU readline"""
495 try:
494 try:
496 import gnureadline as readline
495 import gnureadline as readline
497 except ImportError:
496 except ImportError:
498 pass
497 pass
499 else:
498 else:
500 return True
499 return True
501 try:
500 try:
502 import readline
501 import readline
503 except ImportError:
502 except ImportError:
504 return False
503 return False
505 else:
504 else:
506 if sys.platform == 'darwin' and 'libedit' in readline.__doc__:
505 if sys.platform == 'darwin' and 'libedit' in readline.__doc__:
507 print("Ignoring readline linked to libedit", file=sys.stderr)
506 print("Ignoring readline linked to libedit", file=sys.stderr)
508 return False
507 return False
509 return True
508 return True
510
509
511 #---------------------------------------------------------------------------
510 #---------------------------------------------------------------------------
512 # VCS related
511 # VCS related
513 #---------------------------------------------------------------------------
512 #---------------------------------------------------------------------------
514
513
515 # utils.submodule has checks for submodule status
514 # utils.submodule has checks for submodule status
516 execfile(pjoin('IPython','utils','submodule.py'), globals())
515 execfile(pjoin('IPython','utils','submodule.py'), globals())
517
516
518 class UpdateSubmodules(Command):
517 class UpdateSubmodules(Command):
519 """Update git submodules
518 """Update git submodules
520
519
521 IPython's external javascript dependencies live in a separate repo.
520 IPython's external javascript dependencies live in a separate repo.
522 """
521 """
523 description = "Update git submodules"
522 description = "Update git submodules"
524 user_options = []
523 user_options = []
525
524
526 def initialize_options(self):
525 def initialize_options(self):
527 pass
526 pass
528
527
529 def finalize_options(self):
528 def finalize_options(self):
530 pass
529 pass
531
530
532 def run(self):
531 def run(self):
533 failure = False
532 failure = False
534 try:
533 try:
535 self.spawn('git submodule init'.split())
534 self.spawn('git submodule init'.split())
536 self.spawn('git submodule update --recursive'.split())
535 self.spawn('git submodule update --recursive'.split())
537 except Exception as e:
536 except Exception as e:
538 failure = e
537 failure = e
539 print(e)
538 print(e)
540
539
541 if not check_submodule_status(repo_root) == 'clean':
540 if not check_submodule_status(repo_root) == 'clean':
542 print("submodules could not be checked out")
541 print("submodules could not be checked out")
543 sys.exit(1)
542 sys.exit(1)
544
543
545
544
546 def git_prebuild(pkg_dir, build_cmd=build_py):
545 def git_prebuild(pkg_dir, build_cmd=build_py):
547 """Return extended build or sdist command class for recording commit
546 """Return extended build or sdist command class for recording commit
548
547
549 records git commit in IPython.utils._sysinfo.commit
548 records git commit in IPython.utils._sysinfo.commit
550
549
551 for use in IPython.utils.sysinfo.sys_info() calls after installation.
550 for use in IPython.utils.sysinfo.sys_info() calls after installation.
552
551
553 Also ensures that submodules exist prior to running
552 Also ensures that submodules exist prior to running
554 """
553 """
555
554
556 class MyBuildPy(build_cmd):
555 class MyBuildPy(build_cmd):
557 ''' Subclass to write commit data into installation tree '''
556 ''' Subclass to write commit data into installation tree '''
558 def run(self):
557 def run(self):
559 build_cmd.run(self)
558 build_cmd.run(self)
560 # this one will only fire for build commands
559 # this one will only fire for build commands
561 if hasattr(self, 'build_lib'):
560 if hasattr(self, 'build_lib'):
562 self._record_commit(self.build_lib)
561 self._record_commit(self.build_lib)
563
562
564 def make_release_tree(self, base_dir, files):
563 def make_release_tree(self, base_dir, files):
565 # this one will fire for sdist
564 # this one will fire for sdist
566 build_cmd.make_release_tree(self, base_dir, files)
565 build_cmd.make_release_tree(self, base_dir, files)
567 self._record_commit(base_dir)
566 self._record_commit(base_dir)
568
567
569 def _record_commit(self, base_dir):
568 def _record_commit(self, base_dir):
570 import subprocess
569 import subprocess
571 proc = subprocess.Popen('git rev-parse --short HEAD',
570 proc = subprocess.Popen('git rev-parse --short HEAD',
572 stdout=subprocess.PIPE,
571 stdout=subprocess.PIPE,
573 stderr=subprocess.PIPE,
572 stderr=subprocess.PIPE,
574 shell=True)
573 shell=True)
575 repo_commit, _ = proc.communicate()
574 repo_commit, _ = proc.communicate()
576 repo_commit = repo_commit.strip().decode("ascii")
575 repo_commit = repo_commit.strip().decode("ascii")
577
576
578 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
577 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
579 if os.path.isfile(out_pth) and not repo_commit:
578 if os.path.isfile(out_pth) and not repo_commit:
580 # nothing to write, don't clobber
579 # nothing to write, don't clobber
581 return
580 return
582
581
583 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
582 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
584
583
585 # remove to avoid overwriting original via hard link
584 # remove to avoid overwriting original via hard link
586 try:
585 try:
587 os.remove(out_pth)
586 os.remove(out_pth)
588 except (IOError, OSError):
587 except (IOError, OSError):
589 pass
588 pass
590 with open(out_pth, 'w') as out_file:
589 with open(out_pth, 'w') as out_file:
591 out_file.writelines([
590 out_file.writelines([
592 '# GENERATED BY setup.py\n',
591 '# GENERATED BY setup.py\n',
593 'commit = u"%s"\n' % repo_commit,
592 'commit = u"%s"\n' % repo_commit,
594 ])
593 ])
595 return require_submodules(MyBuildPy)
594 return require_submodules(MyBuildPy)
596
595
597
596
598 def require_submodules(command):
597 def require_submodules(command):
599 """decorator for instructing a command to check for submodules before running"""
598 """decorator for instructing a command to check for submodules before running"""
600 class DecoratedCommand(command):
599 class DecoratedCommand(command):
601 def run(self):
600 def run(self):
602 if not check_submodule_status(repo_root) == 'clean':
601 if not check_submodule_status(repo_root) == 'clean':
603 print("submodules missing! Run `setup.py submodule` and try again")
602 print("submodules missing! Run `setup.py submodule` and try again")
604 sys.exit(1)
603 sys.exit(1)
605 command.run(self)
604 command.run(self)
606 return DecoratedCommand
605 return DecoratedCommand
607
606
608 #---------------------------------------------------------------------------
607 #---------------------------------------------------------------------------
609 # bdist related
608 # bdist related
610 #---------------------------------------------------------------------------
609 #---------------------------------------------------------------------------
611
610
612 def get_bdist_wheel():
611 def get_bdist_wheel():
613 """Construct bdist_wheel command for building wheels
612 """Construct bdist_wheel command for building wheels
614
613
615 Constructs py2-none-any tag, instead of py2.7-none-any
614 Constructs py2-none-any tag, instead of py2.7-none-any
616 """
615 """
617 class RequiresWheel(Command):
616 class RequiresWheel(Command):
618 description = "Dummy command for missing bdist_wheel"
617 description = "Dummy command for missing bdist_wheel"
619 user_options = []
618 user_options = []
620
619
621 def initialize_options(self):
620 def initialize_options(self):
622 pass
621 pass
623
622
624 def finalize_options(self):
623 def finalize_options(self):
625 pass
624 pass
626
625
627 def run(self):
626 def run(self):
628 print("bdist_wheel requires the wheel package")
627 print("bdist_wheel requires the wheel package")
629 sys.exit(1)
628 sys.exit(1)
630
629
631 if 'setuptools' not in sys.modules:
630 if 'setuptools' not in sys.modules:
632 return RequiresWheel
631 return RequiresWheel
633 else:
632 else:
634 try:
633 try:
635 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
634 from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
636 except ImportError:
635 except ImportError:
637 return RequiresWheel
636 return RequiresWheel
638
637
639 class bdist_wheel_tag(bdist_wheel):
638 class bdist_wheel_tag(bdist_wheel):
640
639
641 def add_requirements(self, metadata_path):
640 def add_requirements(self, metadata_path):
642 """transform platform-dependent requirements"""
641 """transform platform-dependent requirements"""
643 pkg_info = read_pkg_info(metadata_path)
642 pkg_info = read_pkg_info(metadata_path)
644 # pkg_info is an email.Message object (?!)
643 # pkg_info is an email.Message object (?!)
645 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
644 # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
646 # and transform them to conditionals
645 # and transform them to conditionals
647 requires = pkg_info.get_all('Requires-Dist')
646 requires = pkg_info.get_all('Requires-Dist')
648 del pkg_info['Requires-Dist']
647 del pkg_info['Requires-Dist']
649 def _remove_startswith(lis, prefix):
648 def _remove_startswith(lis, prefix):
650 """like list.remove, but with startswith instead of =="""
649 """like list.remove, but with startswith instead of =="""
651 found = False
650 found = False
652 for idx, item in enumerate(lis):
651 for idx, item in enumerate(lis):
653 if item.startswith(prefix):
652 if item.startswith(prefix):
654 found = True
653 found = True
655 break
654 break
656 if found:
655 if found:
657 lis.pop(idx)
656 lis.pop(idx)
658
657
659 for pkg in ("gnureadline", "pyreadline", "mock", "terminado", "appnope", "pexpect"):
658 for pkg in ("gnureadline", "pyreadline", "mock", "terminado", "appnope", "pexpect"):
660 _remove_startswith(requires, pkg)
659 _remove_startswith(requires, pkg)
661 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
660 requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'")
662 requires.append("terminado (>=0.3.3); extra == 'notebook' and sys.platform != 'win32'")
661 requires.append("terminado (>=0.3.3); extra == 'notebook' and sys.platform != 'win32'")
663 requires.append("terminado (>=0.3.3); extra == 'all' and sys.platform != 'win32'")
662 requires.append("terminado (>=0.3.3); extra == 'all' and sys.platform != 'win32'")
664 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
663 requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
665 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
664 requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'")
666 requires.append("mock; extra == 'test' and python_version < '3.3'")
665 requires.append("mock; extra == 'test' and python_version < '3.3'")
667 requires.append("appnope; sys.platform == 'darwin'")
666 requires.append("appnope; sys.platform == 'darwin'")
668 requires.append("pexpect; sys.platform != 'win32'")
667 requires.append("pexpect; sys.platform != 'win32'")
669 for r in requires:
668 for r in requires:
670 pkg_info['Requires-Dist'] = r
669 pkg_info['Requires-Dist'] = r
671 write_pkg_info(metadata_path, pkg_info)
670 write_pkg_info(metadata_path, pkg_info)
672
671
673 return bdist_wheel_tag
672 return bdist_wheel_tag
674
673
675 #---------------------------------------------------------------------------
674 #---------------------------------------------------------------------------
676 # Notebook related
675 # Notebook related
677 #---------------------------------------------------------------------------
676 #---------------------------------------------------------------------------
678
677
679 class CompileCSS(Command):
678 class CompileCSS(Command):
680 """Recompile Notebook CSS
679 """Recompile Notebook CSS
681
680
682 Regenerate the compiled CSS from LESS sources.
681 Regenerate the compiled CSS from LESS sources.
683
682
684 Requires various dev dependencies, such as invoke and lessc.
683 Requires various dev dependencies, such as invoke and lessc.
685 """
684 """
686 description = "Recompile Notebook CSS"
685 description = "Recompile Notebook CSS"
687 user_options = [
686 user_options = [
688 ('minify', 'x', "minify CSS"),
687 ('minify', 'x', "minify CSS"),
689 ('force', 'f', "force recompilation of CSS"),
688 ('force', 'f', "force recompilation of CSS"),
690 ]
689 ]
691
690
692 def initialize_options(self):
691 def initialize_options(self):
693 self.minify = False
692 self.minify = False
694 self.force = False
693 self.force = False
695
694
696 def finalize_options(self):
695 def finalize_options(self):
697 self.minify = bool(self.minify)
696 self.minify = bool(self.minify)
698 self.force = bool(self.force)
697 self.force = bool(self.force)
699
698
700 def run(self):
699 def run(self):
701 cmd = ['invoke', 'css']
700 cmd = ['invoke', 'css']
702 if self.minify:
701 if self.minify:
703 cmd.append('--minify')
702 cmd.append('--minify')
704 if self.force:
703 if self.force:
705 cmd.append('--force')
704 cmd.append('--force')
706 try:
705 try:
707 p = Popen(cmd, cwd=pjoin(repo_root, "IPython", "html"), stderr=PIPE)
706 p = Popen(cmd, cwd=pjoin(repo_root, "IPython", "html"), stderr=PIPE)
708 except OSError:
707 except OSError:
709 raise DistutilsExecError("invoke is required to rebuild css (pip install invoke)")
708 raise DistutilsExecError("invoke is required to rebuild css (pip install invoke)")
710 out, err = p.communicate()
709 out, err = p.communicate()
711 if p.returncode:
710 if p.returncode:
712 if sys.version_info[0] >= 3:
711 if sys.version_info[0] >= 3:
713 err = err.decode('utf8', 'replace')
712 err = err.decode('utf8', 'replace')
714 raise DistutilsExecError(err.strip())
713 raise DistutilsExecError(err.strip())
715
714
716
715
717 class JavascriptVersion(Command):
716 class JavascriptVersion(Command):
718 """write the javascript version to notebook javascript"""
717 """write the javascript version to notebook javascript"""
719 description = "Write IPython version to javascript"
718 description = "Write IPython version to javascript"
720 user_options = []
719 user_options = []
721
720
722 def initialize_options(self):
721 def initialize_options(self):
723 pass
722 pass
724
723
725 def finalize_options(self):
724 def finalize_options(self):
726 pass
725 pass
727
726
728 def run(self):
727 def run(self):
729 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
728 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
730 with open(nsfile) as f:
729 with open(nsfile) as f:
731 lines = f.readlines()
730 lines = f.readlines()
732 with open(nsfile, 'w') as f:
731 with open(nsfile, 'w') as f:
733 found = False
732 found = False
734 for line in lines:
733 for line in lines:
735 if line.strip().startswith("IPython.version"):
734 if line.strip().startswith("IPython.version"):
736 line = ' IPython.version = "{0}";\n'.format(version)
735 line = ' IPython.version = "{0}";\n'.format(version)
737 found = True
736 found = True
738 f.write(line)
737 f.write(line)
739 if not found:
738 if not found:
740 raise RuntimeError("Didn't find IPython.version line in %s" % nsfile)
739 raise RuntimeError("Didn't find IPython.version line in %s" % nsfile)
741
740
742
741
743 def css_js_prerelease(command):
742 def css_js_prerelease(command):
744 """decorator for building js/minified css prior to a release"""
743 """decorator for building js/minified css prior to a release"""
745 class DecoratedCommand(command):
744 class DecoratedCommand(command):
746 def run(self):
745 def run(self):
747 self.distribution.run_command('jsversion')
746 self.distribution.run_command('jsversion')
748 css = self.distribution.get_command_obj('css')
747 css = self.distribution.get_command_obj('css')
749 css.minify = True
748 css.minify = True
750 try:
749 try:
751 self.distribution.run_command('css')
750 self.distribution.run_command('css')
752 except Exception as e:
751 except Exception as e:
753 log.warn("rebuilding css and sourcemaps failed (not a problem)")
752 log.warn("rebuilding css and sourcemaps failed (not a problem)")
754 log.warn(str(e))
753 log.warn(str(e))
755 command.run(self)
754 command.run(self)
756 return DecoratedCommand
755 return DecoratedCommand
General Comments 0
You need to be logged in to leave comments. Login now