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