##// END OF EJS Templates
Don't import IPython to check version number in setup...
Thomas Kluyver -
Show More
@@ -1,470 +1,469 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 re
17 import re
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 glob import glob
27 from glob import glob
28
28
29 from setupext import install_data_ext
29 from setupext import install_data_ext
30
30
31 #-------------------------------------------------------------------------------
31 #-------------------------------------------------------------------------------
32 # Useful globals and utility functions
32 # Useful globals and utility functions
33 #-------------------------------------------------------------------------------
33 #-------------------------------------------------------------------------------
34
34
35 # A few handy globals
35 # A few handy globals
36 isfile = os.path.isfile
36 isfile = os.path.isfile
37 pjoin = os.path.join
37 pjoin = os.path.join
38 repo_root = os.path.dirname(os.path.abspath(__file__))
38 repo_root = os.path.dirname(os.path.abspath(__file__))
39
39
40 def oscmd(s):
40 def oscmd(s):
41 print(">", s)
41 print(">", s)
42 os.system(s)
42 os.system(s)
43
43
44 # Py3 compatibility hacks, without assuming IPython itself is installed with
44 # Py3 compatibility hacks, without assuming IPython itself is installed with
45 # the full py3compat machinery.
45 # the full py3compat machinery.
46
46
47 try:
47 try:
48 execfile
48 execfile
49 except NameError:
49 except NameError:
50 def execfile(fname, globs, locs=None):
50 def execfile(fname, globs, locs=None):
51 locs = locs or globs
51 locs = locs or globs
52 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
52 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
53
53
54 # A little utility we'll need below, since glob() does NOT allow you to do
54 # A little utility we'll need below, since glob() does NOT allow you to do
55 # exclusion on multiple endings!
55 # exclusion on multiple endings!
56 def file_doesnt_endwith(test,endings):
56 def file_doesnt_endwith(test,endings):
57 """Return true if test is a file and its name does NOT end with any
57 """Return true if test is a file and its name does NOT end with any
58 of the strings listed in endings."""
58 of the strings listed in endings."""
59 if not isfile(test):
59 if not isfile(test):
60 return False
60 return False
61 for e in endings:
61 for e in endings:
62 if test.endswith(e):
62 if test.endswith(e):
63 return False
63 return False
64 return True
64 return True
65
65
66 #---------------------------------------------------------------------------
66 #---------------------------------------------------------------------------
67 # Basic project information
67 # Basic project information
68 #---------------------------------------------------------------------------
68 #---------------------------------------------------------------------------
69
69
70 # release.py contains version, authors, license, url, keywords, etc.
70 # release.py contains version, authors, license, url, keywords, etc.
71 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
71 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
72
72
73 # Create a dict with the basic information
73 # Create a dict with the basic information
74 # This dict is eventually passed to setup after additional keys are added.
74 # This dict is eventually passed to setup after additional keys are added.
75 setup_args = dict(
75 setup_args = dict(
76 name = name,
76 name = name,
77 version = version,
77 version = version,
78 description = description,
78 description = description,
79 long_description = long_description,
79 long_description = long_description,
80 author = author,
80 author = author,
81 author_email = author_email,
81 author_email = author_email,
82 url = url,
82 url = url,
83 license = license,
83 license = license,
84 platforms = platforms,
84 platforms = platforms,
85 keywords = keywords,
85 keywords = keywords,
86 classifiers = classifiers,
86 classifiers = classifiers,
87 cmdclass = {'install_data': install_data_ext},
87 cmdclass = {'install_data': install_data_ext},
88 )
88 )
89
89
90
90
91 #---------------------------------------------------------------------------
91 #---------------------------------------------------------------------------
92 # Find packages
92 # Find packages
93 #---------------------------------------------------------------------------
93 #---------------------------------------------------------------------------
94
94
95 def find_packages():
95 def find_packages():
96 """
96 """
97 Find all of IPython's packages.
97 Find all of IPython's packages.
98 """
98 """
99 excludes = ['deathrow', 'quarantine']
99 excludes = ['deathrow', 'quarantine']
100 packages = []
100 packages = []
101 for dir,subdirs,files in os.walk('IPython'):
101 for dir,subdirs,files in os.walk('IPython'):
102 package = dir.replace(os.path.sep, '.')
102 package = dir.replace(os.path.sep, '.')
103 if any(package.startswith('IPython.'+exc) for exc in excludes):
103 if any(package.startswith('IPython.'+exc) for exc in excludes):
104 # package is to be excluded (e.g. deathrow)
104 # package is to be excluded (e.g. deathrow)
105 continue
105 continue
106 if '__init__.py' not in files:
106 if '__init__.py' not in files:
107 # not a package
107 # not a package
108 continue
108 continue
109 packages.append(package)
109 packages.append(package)
110 return packages
110 return packages
111
111
112 #---------------------------------------------------------------------------
112 #---------------------------------------------------------------------------
113 # Find package data
113 # Find package data
114 #---------------------------------------------------------------------------
114 #---------------------------------------------------------------------------
115
115
116 def find_package_data():
116 def find_package_data():
117 """
117 """
118 Find IPython's package_data.
118 Find IPython's package_data.
119 """
119 """
120 # This is not enough for these things to appear in an sdist.
120 # This is not enough for these things to appear in an sdist.
121 # We need to muck with the MANIFEST to get this to work
121 # We need to muck with the MANIFEST to get this to work
122
122
123 package_data = {
123 package_data = {
124 'IPython.core' : ['profile/README*'],
124 'IPython.core' : ['profile/README*'],
125 'IPython.core.tests' : ['*.png', '*.jpg', 'daft_extension/*.py'],
125 'IPython.core.tests' : ['*.png', '*.jpg', 'daft_extension/*.py'],
126 'IPython.lib.tests' : ['*.wav'],
126 'IPython.lib.tests' : ['*.wav'],
127 'IPython.testing.plugin' : ['*.txt'],
127 'IPython.testing.plugin' : ['*.txt'],
128 }
128 }
129
129
130 return package_data
130 return package_data
131
131
132
132
133 def check_package_data(package_data):
133 def check_package_data(package_data):
134 """verify that package_data globs make sense"""
134 """verify that package_data globs make sense"""
135 print("checking package data")
135 print("checking package data")
136 for pkg, data in package_data.items():
136 for pkg, data in package_data.items():
137 pkg_root = pjoin(*pkg.split('.'))
137 pkg_root = pjoin(*pkg.split('.'))
138 for d in data:
138 for d in data:
139 path = pjoin(pkg_root, d)
139 path = pjoin(pkg_root, d)
140 if '*' in path:
140 if '*' in path:
141 assert len(glob(path)) > 0, "No files match pattern %s" % path
141 assert len(glob(path)) > 0, "No files match pattern %s" % path
142 else:
142 else:
143 assert os.path.exists(path), "Missing package data: %s" % path
143 assert os.path.exists(path), "Missing package data: %s" % path
144
144
145
145
146 def check_package_data_first(command):
146 def check_package_data_first(command):
147 """decorator for checking package_data before running a given command
147 """decorator for checking package_data before running a given command
148
148
149 Probably only needs to wrap build_py
149 Probably only needs to wrap build_py
150 """
150 """
151 class DecoratedCommand(command):
151 class DecoratedCommand(command):
152 def run(self):
152 def run(self):
153 check_package_data(self.package_data)
153 check_package_data(self.package_data)
154 command.run(self)
154 command.run(self)
155 return DecoratedCommand
155 return DecoratedCommand
156
156
157
157
158 #---------------------------------------------------------------------------
158 #---------------------------------------------------------------------------
159 # Find data files
159 # Find data files
160 #---------------------------------------------------------------------------
160 #---------------------------------------------------------------------------
161
161
162 def make_dir_struct(tag,base,out_base):
162 def make_dir_struct(tag,base,out_base):
163 """Make the directory structure of all files below a starting dir.
163 """Make the directory structure of all files below a starting dir.
164
164
165 This is just a convenience routine to help build a nested directory
165 This is just a convenience routine to help build a nested directory
166 hierarchy because distutils is too stupid to do this by itself.
166 hierarchy because distutils is too stupid to do this by itself.
167
167
168 XXX - this needs a proper docstring!
168 XXX - this needs a proper docstring!
169 """
169 """
170
170
171 # we'll use these a lot below
171 # we'll use these a lot below
172 lbase = len(base)
172 lbase = len(base)
173 pathsep = os.path.sep
173 pathsep = os.path.sep
174 lpathsep = len(pathsep)
174 lpathsep = len(pathsep)
175
175
176 out = []
176 out = []
177 for (dirpath,dirnames,filenames) in os.walk(base):
177 for (dirpath,dirnames,filenames) in os.walk(base):
178 # we need to strip out the dirpath from the base to map it to the
178 # we need to strip out the dirpath from the base to map it to the
179 # output (installation) path. This requires possibly stripping the
179 # output (installation) path. This requires possibly stripping the
180 # path separator, because otherwise pjoin will not work correctly
180 # path separator, because otherwise pjoin will not work correctly
181 # (pjoin('foo/','/bar') returns '/bar').
181 # (pjoin('foo/','/bar') returns '/bar').
182
182
183 dp_eff = dirpath[lbase:]
183 dp_eff = dirpath[lbase:]
184 if dp_eff.startswith(pathsep):
184 if dp_eff.startswith(pathsep):
185 dp_eff = dp_eff[lpathsep:]
185 dp_eff = dp_eff[lpathsep:]
186 # The output path must be anchored at the out_base marker
186 # The output path must be anchored at the out_base marker
187 out_path = pjoin(out_base,dp_eff)
187 out_path = pjoin(out_base,dp_eff)
188 # Now we can generate the final filenames. Since os.walk only produces
188 # Now we can generate the final filenames. Since os.walk only produces
189 # filenames, we must join back with the dirpath to get full valid file
189 # filenames, we must join back with the dirpath to get full valid file
190 # paths:
190 # paths:
191 pfiles = [pjoin(dirpath,f) for f in filenames]
191 pfiles = [pjoin(dirpath,f) for f in filenames]
192 # Finally, generate the entry we need, which is a pari of (output
192 # Finally, generate the entry we need, which is a pari of (output
193 # path, files) for use as a data_files parameter in install_data.
193 # path, files) for use as a data_files parameter in install_data.
194 out.append((out_path, pfiles))
194 out.append((out_path, pfiles))
195
195
196 return out
196 return out
197
197
198
198
199 def find_data_files():
199 def find_data_files():
200 """
200 """
201 Find IPython's data_files.
201 Find IPython's data_files.
202
202
203 Just man pages at this point.
203 Just man pages at this point.
204 """
204 """
205
205
206 manpagebase = pjoin('share', 'man', 'man1')
206 manpagebase = pjoin('share', 'man', 'man1')
207
207
208 # Simple file lists can be made by hand
208 # Simple file lists can be made by hand
209 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
209 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
210 if not manpages:
210 if not manpages:
211 # When running from a source tree, the manpages aren't gzipped
211 # When running from a source tree, the manpages aren't gzipped
212 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
212 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
213
213
214 # And assemble the entire output list
214 # And assemble the entire output list
215 data_files = [ (manpagebase, manpages) ]
215 data_files = [ (manpagebase, manpages) ]
216
216
217 return data_files
217 return data_files
218
218
219
219
220 def make_man_update_target(manpage):
220 def make_man_update_target(manpage):
221 """Return a target_update-compliant tuple for the given manpage.
221 """Return a target_update-compliant tuple for the given manpage.
222
222
223 Parameters
223 Parameters
224 ----------
224 ----------
225 manpage : string
225 manpage : string
226 Name of the manpage, must include the section number (trailing number).
226 Name of the manpage, must include the section number (trailing number).
227
227
228 Example
228 Example
229 -------
229 -------
230
230
231 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
231 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
232 ('docs/man/ipython.1.gz',
232 ('docs/man/ipython.1.gz',
233 ['docs/man/ipython.1'],
233 ['docs/man/ipython.1'],
234 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
234 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
235 """
235 """
236 man_dir = pjoin('docs', 'man')
236 man_dir = pjoin('docs', 'man')
237 manpage_gz = manpage + '.gz'
237 manpage_gz = manpage + '.gz'
238 manpath = pjoin(man_dir, manpage)
238 manpath = pjoin(man_dir, manpage)
239 manpath_gz = pjoin(man_dir, manpage_gz)
239 manpath_gz = pjoin(man_dir, manpage_gz)
240 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
240 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
241 locals() )
241 locals() )
242 return (manpath_gz, [manpath], gz_cmd)
242 return (manpath_gz, [manpath], gz_cmd)
243
243
244 # The two functions below are copied from IPython.utils.path, so we don't need
244 # The two functions below are copied from IPython.utils.path, so we don't need
245 # to import IPython during setup, which fails on Python 3.
245 # to import IPython during setup, which fails on Python 3.
246
246
247 def target_outdated(target,deps):
247 def target_outdated(target,deps):
248 """Determine whether a target is out of date.
248 """Determine whether a target is out of date.
249
249
250 target_outdated(target,deps) -> 1/0
250 target_outdated(target,deps) -> 1/0
251
251
252 deps: list of filenames which MUST exist.
252 deps: list of filenames which MUST exist.
253 target: single filename which may or may not exist.
253 target: single filename which may or may not exist.
254
254
255 If target doesn't exist or is older than any file listed in deps, return
255 If target doesn't exist or is older than any file listed in deps, return
256 true, otherwise return false.
256 true, otherwise return false.
257 """
257 """
258 try:
258 try:
259 target_time = os.path.getmtime(target)
259 target_time = os.path.getmtime(target)
260 except os.error:
260 except os.error:
261 return 1
261 return 1
262 for dep in deps:
262 for dep in deps:
263 dep_time = os.path.getmtime(dep)
263 dep_time = os.path.getmtime(dep)
264 if dep_time > target_time:
264 if dep_time > target_time:
265 #print "For target",target,"Dep failed:",dep # dbg
265 #print "For target",target,"Dep failed:",dep # dbg
266 #print "times (dep,tar):",dep_time,target_time # dbg
266 #print "times (dep,tar):",dep_time,target_time # dbg
267 return 1
267 return 1
268 return 0
268 return 0
269
269
270
270
271 def target_update(target,deps,cmd):
271 def target_update(target,deps,cmd):
272 """Update a target with a given command given a list of dependencies.
272 """Update a target with a given command given a list of dependencies.
273
273
274 target_update(target,deps,cmd) -> runs cmd if target is outdated.
274 target_update(target,deps,cmd) -> runs cmd if target is outdated.
275
275
276 This is just a wrapper around target_outdated() which calls the given
276 This is just a wrapper around target_outdated() which calls the given
277 command if target is outdated."""
277 command if target is outdated."""
278
278
279 if target_outdated(target,deps):
279 if target_outdated(target,deps):
280 os.system(cmd)
280 os.system(cmd)
281
281
282 #---------------------------------------------------------------------------
282 #---------------------------------------------------------------------------
283 # Find scripts
283 # Find scripts
284 #---------------------------------------------------------------------------
284 #---------------------------------------------------------------------------
285
285
286 def find_entry_points():
286 def find_entry_points():
287 """Defines the command line entry points for IPython
287 """Defines the command line entry points for IPython
288
288
289 This always uses setuptools-style entry points. When setuptools is not in
289 This always uses setuptools-style entry points. When setuptools is not in
290 use, our own build_scripts_entrypt class below parses these and builds
290 use, our own build_scripts_entrypt class below parses these and builds
291 command line scripts.
291 command line scripts.
292
292
293 Each of our entry points gets both a plain name, e.g. ipython, and one
293 Each of our entry points gets both a plain name, e.g. ipython, and one
294 suffixed with the Python major version number, e.g. ipython3.
294 suffixed with the Python major version number, e.g. ipython3.
295 """
295 """
296 ep = [
296 ep = [
297 'ipython%s = IPython:start_ipython',
297 'ipython%s = IPython:start_ipython',
298 'iptest%s = IPython.testing.iptestcontroller:main',
298 'iptest%s = IPython.testing.iptestcontroller:main',
299 ]
299 ]
300 suffix = str(sys.version_info[0])
300 suffix = str(sys.version_info[0])
301 return [e % '' for e in ep] + [e % suffix for e in ep]
301 return [e % '' for e in ep] + [e % suffix for e in ep]
302
302
303 script_src = """#!{executable}
303 script_src = """#!{executable}
304 # This script was automatically generated by setup.py
304 # This script was automatically generated by setup.py
305 if __name__ == '__main__':
305 if __name__ == '__main__':
306 from {mod} import {func}
306 from {mod} import {func}
307 {func}()
307 {func}()
308 """
308 """
309
309
310 class build_scripts_entrypt(build_scripts):
310 class build_scripts_entrypt(build_scripts):
311 """Build the command line scripts
311 """Build the command line scripts
312
312
313 Parse setuptools style entry points and write simple scripts to run the
313 Parse setuptools style entry points and write simple scripts to run the
314 target functions.
314 target functions.
315
315
316 On Windows, this also creates .cmd wrappers for the scripts so that you can
316 On Windows, this also creates .cmd wrappers for the scripts so that you can
317 easily launch them from a command line.
317 easily launch them from a command line.
318 """
318 """
319 def run(self):
319 def run(self):
320 self.mkpath(self.build_dir)
320 self.mkpath(self.build_dir)
321 outfiles = []
321 outfiles = []
322 for script in find_entry_points():
322 for script in find_entry_points():
323 name, entrypt = script.split('=')
323 name, entrypt = script.split('=')
324 name = name.strip()
324 name = name.strip()
325 entrypt = entrypt.strip()
325 entrypt = entrypt.strip()
326 outfile = os.path.join(self.build_dir, name)
326 outfile = os.path.join(self.build_dir, name)
327 outfiles.append(outfile)
327 outfiles.append(outfile)
328 print('Writing script to', outfile)
328 print('Writing script to', outfile)
329
329
330 mod, func = entrypt.split(':')
330 mod, func = entrypt.split(':')
331 with open(outfile, 'w') as f:
331 with open(outfile, 'w') as f:
332 f.write(script_src.format(executable=sys.executable,
332 f.write(script_src.format(executable=sys.executable,
333 mod=mod, func=func))
333 mod=mod, func=func))
334
334
335 if sys.platform == 'win32':
335 if sys.platform == 'win32':
336 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
336 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
337 # command line
337 # command line
338 cmd_file = os.path.join(self.build_dir, name + '.cmd')
338 cmd_file = os.path.join(self.build_dir, name + '.cmd')
339 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
339 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
340 python=sys.executable, script=name)
340 python=sys.executable, script=name)
341 log.info("Writing %s wrapper script" % cmd_file)
341 log.info("Writing %s wrapper script" % cmd_file)
342 with open(cmd_file, 'w') as f:
342 with open(cmd_file, 'w') as f:
343 f.write(cmd)
343 f.write(cmd)
344
344
345 return outfiles, outfiles
345 return outfiles, outfiles
346
346
347 class install_lib_symlink(Command):
347 class install_lib_symlink(Command):
348 user_options = [
348 user_options = [
349 ('install-dir=', 'd', "directory to install to"),
349 ('install-dir=', 'd', "directory to install to"),
350 ]
350 ]
351
351
352 def initialize_options(self):
352 def initialize_options(self):
353 self.install_dir = None
353 self.install_dir = None
354
354
355 def finalize_options(self):
355 def finalize_options(self):
356 self.set_undefined_options('symlink',
356 self.set_undefined_options('symlink',
357 ('install_lib', 'install_dir'),
357 ('install_lib', 'install_dir'),
358 )
358 )
359
359
360 def run(self):
360 def run(self):
361 if sys.platform == 'win32':
361 if sys.platform == 'win32':
362 raise Exception("This doesn't work on Windows.")
362 raise Exception("This doesn't work on Windows.")
363 pkg = os.path.join(os.getcwd(), 'IPython')
363 pkg = os.path.join(os.getcwd(), 'IPython')
364 dest = os.path.join(self.install_dir, 'IPython')
364 dest = os.path.join(self.install_dir, 'IPython')
365 if os.path.islink(dest):
365 if os.path.islink(dest):
366 print('removing existing symlink at %s' % dest)
366 print('removing existing symlink at %s' % dest)
367 os.unlink(dest)
367 os.unlink(dest)
368 print('symlinking %s -> %s' % (pkg, dest))
368 print('symlinking %s -> %s' % (pkg, dest))
369 os.symlink(pkg, dest)
369 os.symlink(pkg, dest)
370
370
371 class unsymlink(install):
371 class unsymlink(install):
372 def run(self):
372 def run(self):
373 dest = os.path.join(self.install_lib, 'IPython')
373 dest = os.path.join(self.install_lib, 'IPython')
374 if os.path.islink(dest):
374 if os.path.islink(dest):
375 print('removing symlink at %s' % dest)
375 print('removing symlink at %s' % dest)
376 os.unlink(dest)
376 os.unlink(dest)
377 else:
377 else:
378 print('No symlink exists at %s' % dest)
378 print('No symlink exists at %s' % dest)
379
379
380 class install_symlinked(install):
380 class install_symlinked(install):
381 def run(self):
381 def run(self):
382 if sys.platform == 'win32':
382 if sys.platform == 'win32':
383 raise Exception("This doesn't work on Windows.")
383 raise Exception("This doesn't work on Windows.")
384
384
385 # Run all sub-commands (at least those that need to be run)
385 # Run all sub-commands (at least those that need to be run)
386 for cmd_name in self.get_sub_commands():
386 for cmd_name in self.get_sub_commands():
387 self.run_command(cmd_name)
387 self.run_command(cmd_name)
388
388
389 # 'sub_commands': a list of commands this command might have to run to
389 # 'sub_commands': a list of commands this command might have to run to
390 # get its work done. See cmd.py for more info.
390 # get its work done. See cmd.py for more info.
391 sub_commands = [('install_lib_symlink', lambda self:True),
391 sub_commands = [('install_lib_symlink', lambda self:True),
392 ('install_scripts_sym', lambda self:True),
392 ('install_scripts_sym', lambda self:True),
393 ]
393 ]
394
394
395 class install_scripts_for_symlink(install_scripts):
395 class install_scripts_for_symlink(install_scripts):
396 """Redefined to get options from 'symlink' instead of 'install'.
396 """Redefined to get options from 'symlink' instead of 'install'.
397
397
398 I love distutils almost as much as I love setuptools.
398 I love distutils almost as much as I love setuptools.
399 """
399 """
400 def finalize_options(self):
400 def finalize_options(self):
401 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
401 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
402 self.set_undefined_options('symlink',
402 self.set_undefined_options('symlink',
403 ('install_scripts', 'install_dir'),
403 ('install_scripts', 'install_dir'),
404 ('force', 'force'),
404 ('force', 'force'),
405 ('skip_build', 'skip_build'),
405 ('skip_build', 'skip_build'),
406 )
406 )
407
407
408
408
409 #---------------------------------------------------------------------------
409 #---------------------------------------------------------------------------
410 # VCS related
410 # VCS related
411 #---------------------------------------------------------------------------
411 #---------------------------------------------------------------------------
412
412
413
413
414 def git_prebuild(pkg_dir, build_cmd=build_py):
414 def git_prebuild(pkg_dir, build_cmd=build_py):
415 """Return extended build or sdist command class for recording commit
415 """Return extended build or sdist command class for recording commit
416
416
417 records git commit in IPython.utils._sysinfo.commit
417 records git commit in IPython.utils._sysinfo.commit
418
418
419 for use in IPython.utils.sysinfo.sys_info() calls after installation.
419 for use in IPython.utils.sysinfo.sys_info() calls after installation.
420 """
420 """
421
421
422 class MyBuildPy(build_cmd):
422 class MyBuildPy(build_cmd):
423 ''' Subclass to write commit data into installation tree '''
423 ''' Subclass to write commit data into installation tree '''
424 def run(self):
424 def run(self):
425 # loose as `.dev` is suppose to be invalid
425 # loose as `.dev` is suppose to be invalid
426 print("check version number")
426 print("check version number")
427 loose_pep440re = re.compile('^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
427 loose_pep440re = re.compile('^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
428 import IPython.core.release as r
428 if not loose_pep440re.match(version):
429 if not loose_pep440re.match(r.version):
429 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % version)
430 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % r.version)
431
430
432
431
433 build_cmd.run(self)
432 build_cmd.run(self)
434 # this one will only fire for build commands
433 # this one will only fire for build commands
435 if hasattr(self, 'build_lib'):
434 if hasattr(self, 'build_lib'):
436 self._record_commit(self.build_lib)
435 self._record_commit(self.build_lib)
437
436
438 def make_release_tree(self, base_dir, files):
437 def make_release_tree(self, base_dir, files):
439 # this one will fire for sdist
438 # this one will fire for sdist
440 build_cmd.make_release_tree(self, base_dir, files)
439 build_cmd.make_release_tree(self, base_dir, files)
441 self._record_commit(base_dir)
440 self._record_commit(base_dir)
442
441
443 def _record_commit(self, base_dir):
442 def _record_commit(self, base_dir):
444 import subprocess
443 import subprocess
445 proc = subprocess.Popen('git rev-parse --short HEAD',
444 proc = subprocess.Popen('git rev-parse --short HEAD',
446 stdout=subprocess.PIPE,
445 stdout=subprocess.PIPE,
447 stderr=subprocess.PIPE,
446 stderr=subprocess.PIPE,
448 shell=True)
447 shell=True)
449 repo_commit, _ = proc.communicate()
448 repo_commit, _ = proc.communicate()
450 repo_commit = repo_commit.strip().decode("ascii")
449 repo_commit = repo_commit.strip().decode("ascii")
451
450
452 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
451 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
453 if os.path.isfile(out_pth) and not repo_commit:
452 if os.path.isfile(out_pth) and not repo_commit:
454 # nothing to write, don't clobber
453 # nothing to write, don't clobber
455 return
454 return
456
455
457 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
456 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
458
457
459 # remove to avoid overwriting original via hard link
458 # remove to avoid overwriting original via hard link
460 try:
459 try:
461 os.remove(out_pth)
460 os.remove(out_pth)
462 except (IOError, OSError):
461 except (IOError, OSError):
463 pass
462 pass
464 with open(out_pth, 'w') as out_file:
463 with open(out_pth, 'w') as out_file:
465 out_file.writelines([
464 out_file.writelines([
466 '# GENERATED BY setup.py\n',
465 '# GENERATED BY setup.py\n',
467 'commit = u"%s"\n' % repo_commit,
466 'commit = u"%s"\n' % repo_commit,
468 ])
467 ])
469 return MyBuildPy
468 return MyBuildPy
470
469
General Comments 0
You need to be logged in to leave comments. Login now