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