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