##// END OF EJS Templates
Check verison number only at build time or pip install -e fails
Matthias Bussonnier -
Show More
@@ -1,21 +1,21 b''
1 # http://travis-ci.org/#!/ipython/ipython
1 # http://travis-ci.org/#!/ipython/ipython
2 language: python
2 language: python
3 python:
3 python:
4 - 3.5
4 - 3.5
5 - 3.4
5 - 3.4
6 - 3.3
6 - 3.3
7 - 2.7
7 - 2.7
8 sudo: false
8 sudo: false
9 before_install:
9 before_install:
10 - git clone --quiet --depth 1 https://github.com/minrk/travis-wheels travis-wheels
10 - git clone --quiet --depth 1 https://github.com/minrk/travis-wheels travis-wheels
11 - 'if [[ $GROUP != js* ]]; then COVERAGE=""; fi'
11 - 'if [[ $GROUP != js* ]]; then COVERAGE=""; fi'
12 install:
12 install:
13 - pip install "setuptools>=18.5"
13 - pip install "setuptools>=18.5"
14 - pip install -f travis-wheels/wheelhouse -e file://$PWD#egg=ipython[test] traitlets
14 - pip install -f travis-wheels/wheelhouse -e file://$PWD#egg=ipython[test]
15 - pip install codecov
15 - pip install codecov
16 script:
16 script:
17 - cd /tmp && iptest --coverage xml && cd -
17 - cd /tmp && iptest --coverage xml && cd -
18 after_success:
18 after_success:
19 - cp /tmp/ipy_coverage.xml ./
19 - cp /tmp/ipy_coverage.xml ./
20 - cp /tmp/.coverage ./
20 - cp /tmp/.coverage ./
21 - codecov
21 - codecov
@@ -1,305 +1,309 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
2 # -*- coding: utf-8 -*-
3 """Setup script for IPython.
3 """Setup script for IPython.
4
4
5 Under Posix environments it works like a typical setup.py script.
5 Under Posix environments it works like a typical setup.py script.
6 Under Windows, the command sdist is not supported, since IPython
6 Under Windows, the command sdist is not supported, since IPython
7 requires utilities which are not available under Windows."""
7 requires utilities which are not available under Windows."""
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (c) 2008-2011, IPython Development Team.
10 # Copyright (c) 2008-2011, IPython Development Team.
11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
11 # Copyright (c) 2001-2007, Fernando Perez <fernando.perez@colorado.edu>
12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
12 # Copyright (c) 2001, Janko Hauser <jhauser@zscout.de>
13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
13 # Copyright (c) 2001, Nathaniel Gray <n8gray@caltech.edu>
14 #
14 #
15 # Distributed under the terms of the Modified BSD License.
15 # Distributed under the terms of the Modified BSD License.
16 #
16 #
17 # The full license is in the file COPYING.rst, distributed with this software.
17 # The full license is in the file COPYING.rst, distributed with this software.
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Minimal Python version sanity check
21 # Minimal Python version sanity check
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 from __future__ import print_function
23 from __future__ import print_function
24
24
25 import sys
25 import sys
26 import re
26 import re
27
27
28 # This check is also made in IPython/__init__, don't forget to update both when
28 # This check is also made in IPython/__init__, don't forget to update both when
29 # changing Python version requirements.
29 # changing Python version requirements.
30 v = sys.version_info
30 v = sys.version_info
31 if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)):
31 if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)):
32 error = "ERROR: IPython requires Python version 2.7 or 3.3 or above."
32 error = "ERROR: IPython requires Python version 2.7 or 3.3 or above."
33 print(error, file=sys.stderr)
33 print(error, file=sys.stderr)
34 sys.exit(1)
34 sys.exit(1)
35
35
36 PY3 = (sys.version_info[0] >= 3)
36 PY3 = (sys.version_info[0] >= 3)
37
37
38 # At least we're on the python version we need, move on.
38 # At least we're on the python version we need, move on.
39
39
40 #-------------------------------------------------------------------------------
40 #-------------------------------------------------------------------------------
41 # Imports
41 # Imports
42 #-------------------------------------------------------------------------------
42 #-------------------------------------------------------------------------------
43
43
44 # Stdlib imports
44 # Stdlib imports
45 import os
45 import os
46
46
47 from glob import glob
47 from glob import glob
48
48
49 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
49 # BEFORE importing distutils, remove MANIFEST. distutils doesn't properly
50 # update it when the contents of directories change.
50 # update it when the contents of directories change.
51 if os.path.exists('MANIFEST'): os.remove('MANIFEST')
51 if os.path.exists('MANIFEST'): os.remove('MANIFEST')
52
52
53 from distutils.core import setup
53 from distutils.core import setup
54
54
55 # Our own imports
55 # Our own imports
56 from setupbase import target_update
56 from setupbase import target_update
57
57
58 from setupbase import (
58 from setupbase import (
59 setup_args,
59 setup_args,
60 find_packages,
60 find_packages,
61 find_package_data,
61 find_package_data,
62 check_package_data_first,
62 check_package_data_first,
63 find_entry_points,
63 find_entry_points,
64 build_scripts_entrypt,
64 build_scripts_entrypt,
65 find_data_files,
65 find_data_files,
66 git_prebuild,
66 git_prebuild,
67 install_symlinked,
67 install_symlinked,
68 install_lib_symlink,
68 install_lib_symlink,
69 install_scripts_for_symlink,
69 install_scripts_for_symlink,
70 unsymlink,
70 unsymlink,
71 )
71 )
72
72
73 isfile = os.path.isfile
73 isfile = os.path.isfile
74 pjoin = os.path.join
74 pjoin = os.path.join
75
75
76 #-------------------------------------------------------------------------------
76 #-------------------------------------------------------------------------------
77 # Handle OS specific things
77 # Handle OS specific things
78 #-------------------------------------------------------------------------------
78 #-------------------------------------------------------------------------------
79
79
80 if os.name in ('nt','dos'):
80 if os.name in ('nt','dos'):
81 os_name = 'windows'
81 os_name = 'windows'
82 else:
82 else:
83 os_name = os.name
83 os_name = os.name
84
84
85 # Under Windows, 'sdist' has not been supported. Now that the docs build with
85 # Under Windows, 'sdist' has not been supported. Now that the docs build with
86 # Sphinx it might work, but let's not turn it on until someone confirms that it
86 # Sphinx it might work, but let's not turn it on until someone confirms that it
87 # actually works.
87 # actually works.
88 if os_name == 'windows' and 'sdist' in sys.argv:
88 if os_name == 'windows' and 'sdist' in sys.argv:
89 print('The sdist command is not available under Windows. Exiting.')
89 print('The sdist command is not available under Windows. Exiting.')
90 sys.exit(1)
90 sys.exit(1)
91
91
92
92
93 #-------------------------------------------------------------------------------
93 #-------------------------------------------------------------------------------
94 # Things related to the IPython documentation
94 # Things related to the IPython documentation
95 #-------------------------------------------------------------------------------
95 #-------------------------------------------------------------------------------
96
96
97 # update the manuals when building a source dist
97 # update the manuals when building a source dist
98 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
98 if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'):
99
99
100 # List of things to be updated. Each entry is a triplet of args for
100 # List of things to be updated. Each entry is a triplet of args for
101 # target_update()
101 # target_update()
102 to_update = [
102 to_update = [
103 ('docs/man/ipython.1.gz',
103 ('docs/man/ipython.1.gz',
104 ['docs/man/ipython.1'],
104 ['docs/man/ipython.1'],
105 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
105 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz'),
106 ]
106 ]
107
107
108
108
109 [ target_update(*t) for t in to_update ]
109 [ target_update(*t) for t in to_update ]
110
110
111 #---------------------------------------------------------------------------
111 #---------------------------------------------------------------------------
112 # Find all the packages, package data, and data_files
112 # Find all the packages, package data, and data_files
113 #---------------------------------------------------------------------------
113 #---------------------------------------------------------------------------
114
114
115 packages = find_packages()
115 packages = find_packages()
116 package_data = find_package_data()
116 package_data = find_package_data()
117
117
118 data_files = find_data_files()
118 data_files = find_data_files()
119
119
120 setup_args['packages'] = packages
120 setup_args['packages'] = packages
121 setup_args['package_data'] = package_data
121 setup_args['package_data'] = package_data
122 setup_args['data_files'] = data_files
122 setup_args['data_files'] = data_files
123
123
124 #---------------------------------------------------------------------------
124 #---------------------------------------------------------------------------
125 # custom distutils commands
125 # custom distutils commands
126 #---------------------------------------------------------------------------
126 #---------------------------------------------------------------------------
127 # imports here, so they are after setuptools import if there was one
127 # imports here, so they are after setuptools import if there was one
128 from distutils.command.sdist import sdist
128 from distutils.command.sdist import sdist
129 from distutils.command.upload import upload
129 from distutils.command.upload import upload
130
130
131 class UploadWindowsInstallers(upload):
131 class UploadWindowsInstallers(upload):
132
132
133 description = "Upload Windows installers to PyPI (only used from tools/release_windows.py)"
133 description = "Upload Windows installers to PyPI (only used from tools/release_windows.py)"
134 user_options = upload.user_options + [
134 user_options = upload.user_options + [
135 ('files=', 'f', 'exe file (or glob) to upload')
135 ('files=', 'f', 'exe file (or glob) to upload')
136 ]
136 ]
137 def initialize_options(self):
137 def initialize_options(self):
138 upload.initialize_options(self)
138 upload.initialize_options(self)
139 meta = self.distribution.metadata
139 meta = self.distribution.metadata
140 base = '{name}-{version}'.format(
140 base = '{name}-{version}'.format(
141 name=meta.get_name(),
141 name=meta.get_name(),
142 version=meta.get_version()
142 version=meta.get_version()
143 )
143 )
144 self.files = os.path.join('dist', '%s.*.exe' % base)
144 self.files = os.path.join('dist', '%s.*.exe' % base)
145
145
146 def run(self):
146 def run(self):
147 for dist_file in glob(self.files):
147 for dist_file in glob(self.files):
148 self.upload_file('bdist_wininst', 'any', dist_file)
148 self.upload_file('bdist_wininst', 'any', dist_file)
149
149
150 setup_args['cmdclass'] = {
150 setup_args['cmdclass'] = {
151 'build_py': \
151 'build_py': \
152 check_package_data_first(git_prebuild('IPython')),
152 check_package_data_first(git_prebuild('IPython')),
153 'sdist' : git_prebuild('IPython', sdist),
153 'sdist' : git_prebuild('IPython', sdist),
154 'upload_wininst' : UploadWindowsInstallers,
154 'upload_wininst' : UploadWindowsInstallers,
155 'symlink': install_symlinked,
155 'symlink': install_symlinked,
156 'install_lib_symlink': install_lib_symlink,
156 'install_lib_symlink': install_lib_symlink,
157 'install_scripts_sym': install_scripts_for_symlink,
157 'install_scripts_sym': install_scripts_for_symlink,
158 'unsymlink': unsymlink,
158 'unsymlink': unsymlink,
159 }
159 }
160
160
161
161
162 #---------------------------------------------------------------------------
162 #---------------------------------------------------------------------------
163 # Handle scripts, dependencies, and setuptools specific things
163 # Handle scripts, dependencies, and setuptools specific things
164 #---------------------------------------------------------------------------
164 #---------------------------------------------------------------------------
165
165
166 # For some commands, use setuptools. Note that we do NOT list install here!
166 # For some commands, use setuptools. Note that we do NOT list install here!
167 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
167 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
168 needs_setuptools = set(('develop', 'release', 'bdist_egg', 'bdist_rpm',
168 needs_setuptools = set(('develop', 'release', 'bdist_egg', 'bdist_rpm',
169 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
169 'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
170 'egg_info', 'easy_install', 'upload', 'install_egg_info',
170 'egg_info', 'easy_install', 'upload', 'install_egg_info',
171 ))
171 ))
172
172
173 if len(needs_setuptools.intersection(sys.argv)) > 0:
173 if len(needs_setuptools.intersection(sys.argv)) > 0:
174 import setuptools
174 import setuptools
175 v = tuple(int(x) for x in setuptools.__version__.split('.'))
175 v = tuple(int(x) for x in setuptools.__version__.split('.'))
176 if v < (18,5):
176 if v < (18,5):
177 raise ValueError('Setuptools version >=18.5 is required, found: %s'%setuptools.__version__)
177 raise ValueError('Setuptools version >=18.5 is required, found: %s'%setuptools.__version__)
178
178
179 # This dict is used for passing extra arguments that are setuptools
179 # This dict is used for passing extra arguments that are setuptools
180 # specific to setup
180 # specific to setup
181 setuptools_extra_args = {}
181 setuptools_extra_args = {}
182
182
183 # setuptools requirements
183 # setuptools requirements
184
184
185 extras_require = dict(
185 extras_require = dict(
186 parallel = ['ipyparallel'],
186 parallel = ['ipyparallel'],
187 qtconsole = ['qtconsole'],
187 qtconsole = ['qtconsole'],
188 doc = ['Sphinx>=1.3'],
188 doc = ['Sphinx>=1.3'],
189 test = ['nose>=0.10.1', 'requests', 'testpath'],
189 test = ['nose>=0.10.1', 'requests', 'testpath'],
190 terminal = [],
190 terminal = [],
191 kernel = ['ipykernel'],
191 kernel = ['ipykernel'],
192 nbformat = ['nbformat'],
192 nbformat = ['nbformat'],
193 notebook = ['notebook', 'ipywidgets'],
193 notebook = ['notebook', 'ipywidgets'],
194 nbconvert = ['nbconvert'],
194 nbconvert = ['nbconvert'],
195 )
195 )
196 install_requires = [
196 install_requires = [
197 'setuptools>=18.5'
197 'setuptools>=18.5'
198 'decorator',
198 'decorator',
199 'pickleshare',
199 'pickleshare',
200 'simplegeneric>0.8',
200 'simplegeneric>0.8',
201 'traitlets',
201 'traitlets',
202 ]
202 ]
203
203
204 # Platform-specific dependencies:
204 # Platform-specific dependencies:
205 # This is the correct way to specify these,
205 # This is the correct way to specify these,
206 # but requires pip >= 6. pip < 6 ignores these.
206 # but requires pip >= 6. pip < 6 ignores these.
207
207
208 extras_require.update({
208 extras_require.update({
209 ':sys_platform != "win32"': ['pexpect'],
209 ':sys_platform != "win32"': ['pexpect'],
210 ':sys_platform == "darwin"': ['appnope'],
210 ':sys_platform == "darwin"': ['appnope'],
211 ':sys_platform == "darwin" and platform_python_implementation == "CPython"': ['gnureadline'],
211 ':sys_platform == "darwin" and platform_python_implementation == "CPython"': ['gnureadline'],
212 'terminal:sys_platform == "win32"': ['pyreadline>=2'],
212 'terminal:sys_platform == "win32"': ['pyreadline>=2'],
213 'test:python_version == "2.7"': ['mock'],
213 'test:python_version == "2.7"': ['mock'],
214 })
214 })
215 # FIXME: re-specify above platform dependencies for pip < 6
215 # FIXME: re-specify above platform dependencies for pip < 6
216 # These would result in non-portable bdists.
216 # These would result in non-portable bdists.
217 if not any(arg.startswith('bdist') for arg in sys.argv):
217 if not any(arg.startswith('bdist') for arg in sys.argv):
218 if sys.version_info < (3, 3):
218 if sys.version_info < (3, 3):
219 extras_require['test'].append('mock')
219 extras_require['test'].append('mock')
220
220
221 if sys.platform == 'darwin':
221 if sys.platform == 'darwin':
222 install_requires.extend(['appnope'])
222 install_requires.extend(['appnope'])
223 have_readline = False
223 have_readline = False
224 try:
224 try:
225 import readline
225 import readline
226 except ImportError:
226 except ImportError:
227 pass
227 pass
228 else:
228 else:
229 if 'libedit' not in readline.__doc__:
229 if 'libedit' not in readline.__doc__:
230 have_readline = True
230 have_readline = True
231 if not have_readline:
231 if not have_readline:
232 install_requires.extend(['gnureadline'])
232 install_requires.extend(['gnureadline'])
233
233
234 if sys.platform.startswith('win'):
234 if sys.platform.startswith('win'):
235 extras_require['terminal'].append('pyreadline>=2.0')
235 extras_require['terminal'].append('pyreadline>=2.0')
236 else:
236 else:
237 install_requires.append('pexpect')
237 install_requires.append('pexpect')
238
238
239 # workaround pypa/setuptools#147, where setuptools misspells
239 # workaround pypa/setuptools#147, where setuptools misspells
240 # platform_python_implementation as python_implementation
240 # platform_python_implementation as python_implementation
241 if 'setuptools' in sys.modules:
241 if 'setuptools' in sys.modules:
242 for key in list(extras_require):
242 for key in list(extras_require):
243 if 'platform_python_implementation' in key:
243 if 'platform_python_implementation' in key:
244 new_key = key.replace('platform_python_implementation', 'python_implementation')
244 new_key = key.replace('platform_python_implementation', 'python_implementation')
245 extras_require[new_key] = extras_require.pop(key)
245 extras_require[new_key] = extras_require.pop(key)
246
246
247 everything = set()
247 everything = set()
248 for key, deps in extras_require.items():
248 for key, deps in extras_require.items():
249 if ':' not in key:
249 if ':' not in key:
250 everything.update(deps)
250 everything.update(deps)
251 extras_require['all'] = everything
251 extras_require['all'] = everything
252
252
253 if 'setuptools' in sys.modules:
253 if 'setuptools' in sys.modules:
254 setuptools_extra_args['zip_safe'] = False
254 setuptools_extra_args['zip_safe'] = False
255 setuptools_extra_args['entry_points'] = {
255 setuptools_extra_args['entry_points'] = {
256 'console_scripts': find_entry_points(),
256 'console_scripts': find_entry_points(),
257 'pygments.lexers': [
257 'pygments.lexers': [
258 'ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer',
258 'ipythonconsole = IPython.lib.lexers:IPythonConsoleLexer',
259 'ipython = IPython.lib.lexers:IPythonLexer',
259 'ipython = IPython.lib.lexers:IPythonLexer',
260 'ipython3 = IPython.lib.lexers:IPython3Lexer',
260 'ipython3 = IPython.lib.lexers:IPython3Lexer',
261 ],
261 ],
262 }
262 }
263 setup_args['extras_require'] = extras_require
263 setup_args['extras_require'] = extras_require
264 requires = setup_args['install_requires'] = install_requires
264 requires = setup_args['install_requires'] = install_requires
265
265
266 # Script to be run by the windows binary installer after the default setup
266 # Script to be run by the windows binary installer after the default setup
267 # routine, to add shortcuts and similar windows-only things. Windows
267 # routine, to add shortcuts and similar windows-only things. Windows
268 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
268 # post-install scripts MUST reside in the scripts/ dir, otherwise distutils
269 # doesn't find them.
269 # doesn't find them.
270 if 'bdist_wininst' in sys.argv:
270 if 'bdist_wininst' in sys.argv:
271 if len(sys.argv) > 2 and \
271 if len(sys.argv) > 2 and \
272 ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
272 ('sdist' in sys.argv or 'bdist_rpm' in sys.argv):
273 print("ERROR: bdist_wininst must be run alone. Exiting.", file=sys.stderr)
273 print("ERROR: bdist_wininst must be run alone. Exiting.", file=sys.stderr)
274 sys.exit(1)
274 sys.exit(1)
275 setup_args['data_files'].append(
275 setup_args['data_files'].append(
276 ['Scripts', ('scripts/ipython.ico', 'scripts/ipython_nb.ico')])
276 ['Scripts', ('scripts/ipython.ico', 'scripts/ipython_nb.ico')])
277 setup_args['scripts'] = [pjoin('scripts','ipython_win_post_install.py')]
277 setup_args['scripts'] = [pjoin('scripts','ipython_win_post_install.py')]
278 setup_args['options'] = {"bdist_wininst":
278 setup_args['options'] = {"bdist_wininst":
279 {"install_script":
279 {"install_script":
280 "ipython_win_post_install.py"}}
280 "ipython_win_post_install.py"}}
281
281
282 else:
282 else:
283 # scripts has to be a non-empty list, or install_scripts isn't called
283 # scripts has to be a non-empty list, or install_scripts isn't called
284 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
284 setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
285
285
286 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
286 setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt
287
287
288 #---------------------------------------------------------------------------
288 #---------------------------------------------------------------------------
289 # Do the actual setup now
289 # Do the actual setup now
290 #---------------------------------------------------------------------------
290 #---------------------------------------------------------------------------
291
291
292 setup_args.update(setuptools_extra_args)
292 setup_args.update(setuptools_extra_args)
293
293
294
294
295 # loose as `.dev` is suppose to be invalid
296 loose_pep440re = re.compile('^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
297
295
298 def main():
296 def main():
299 import IPython.core.release as r
297 try:
300 if not loose_pep440re.match(r.version):
298 # loose as `.dev` is suppose to be invalid
301 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % r.version)
299 print("check version number")
300 loose_pep440re = re.compile('^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
301 import IPython.core.release as r
302 if not loose_pep440re.match(r.version):
303 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % r.version)
304 except:
305 pass
302 setup(**setup_args)
306 setup(**setup_args)
303
307
304 if __name__ == '__main__':
308 if __name__ == '__main__':
305 main()
309 main()
@@ -1,466 +1,471 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 This module defines the things that are used in setup.py for building IPython
3 This module defines the things that are used in setup.py for building IPython
4
4
5 This includes:
5 This includes:
6
6
7 * The basic arguments to setup
7 * The basic arguments to setup
8 * Functions for finding things like packages, package data, etc.
8 * Functions for finding things like packages, package data, etc.
9 * A function for checking dependencies.
9 * A function for checking dependencies.
10 """
10 """
11
11
12 # Copyright (c) IPython Development Team.
12 # Copyright (c) IPython Development Team.
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14
14
15 from __future__ import print_function
15 from __future__ import print_function
16
16
17 import errno
17 import 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 distutils.errors import DistutilsExecError
28 from fnmatch import fnmatch
29 from glob import glob
27 from glob import glob
30 from subprocess import Popen, PIPE
31
28
32 from setupext import install_data_ext
29 from setupext import install_data_ext
33
30
34 #-------------------------------------------------------------------------------
31 #-------------------------------------------------------------------------------
35 # Useful globals and utility functions
32 # Useful globals and utility functions
36 #-------------------------------------------------------------------------------
33 #-------------------------------------------------------------------------------
37
34
38 # A few handy globals
35 # A few handy globals
39 isfile = os.path.isfile
36 isfile = os.path.isfile
40 pjoin = os.path.join
37 pjoin = os.path.join
41 repo_root = os.path.dirname(os.path.abspath(__file__))
38 repo_root = os.path.dirname(os.path.abspath(__file__))
42
39
43 def oscmd(s):
40 def oscmd(s):
44 print(">", s)
41 print(">", s)
45 os.system(s)
42 os.system(s)
46
43
47 # Py3 compatibility hacks, without assuming IPython itself is installed with
44 # Py3 compatibility hacks, without assuming IPython itself is installed with
48 # the full py3compat machinery.
45 # the full py3compat machinery.
49
46
50 try:
47 try:
51 execfile
48 execfile
52 except NameError:
49 except NameError:
53 def execfile(fname, globs, locs=None):
50 def execfile(fname, globs, locs=None):
54 locs = locs or globs
51 locs = locs or globs
55 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
52 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
56
53
57 # 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
58 # exclusion on multiple endings!
55 # exclusion on multiple endings!
59 def file_doesnt_endwith(test,endings):
56 def file_doesnt_endwith(test,endings):
60 """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
61 of the strings listed in endings."""
58 of the strings listed in endings."""
62 if not isfile(test):
59 if not isfile(test):
63 return False
60 return False
64 for e in endings:
61 for e in endings:
65 if test.endswith(e):
62 if test.endswith(e):
66 return False
63 return False
67 return True
64 return True
68
65
69 #---------------------------------------------------------------------------
66 #---------------------------------------------------------------------------
70 # Basic project information
67 # Basic project information
71 #---------------------------------------------------------------------------
68 #---------------------------------------------------------------------------
72
69
73 # release.py contains version, authors, license, url, keywords, etc.
70 # release.py contains version, authors, license, url, keywords, etc.
74 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
71 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
75
72
76 # Create a dict with the basic information
73 # Create a dict with the basic information
77 # 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.
78 setup_args = dict(
75 setup_args = dict(
79 name = name,
76 name = name,
80 version = version,
77 version = version,
81 description = description,
78 description = description,
82 long_description = long_description,
79 long_description = long_description,
83 author = author,
80 author = author,
84 author_email = author_email,
81 author_email = author_email,
85 url = url,
82 url = url,
86 download_url = download_url,
83 download_url = download_url,
87 license = license,
84 license = license,
88 platforms = platforms,
85 platforms = platforms,
89 keywords = keywords,
86 keywords = keywords,
90 classifiers = classifiers,
87 classifiers = classifiers,
91 cmdclass = {'install_data': install_data_ext},
88 cmdclass = {'install_data': install_data_ext},
92 )
89 )
93
90
94
91
95 #---------------------------------------------------------------------------
92 #---------------------------------------------------------------------------
96 # Find packages
93 # Find packages
97 #---------------------------------------------------------------------------
94 #---------------------------------------------------------------------------
98
95
99 def find_packages():
96 def find_packages():
100 """
97 """
101 Find all of IPython's packages.
98 Find all of IPython's packages.
102 """
99 """
103 excludes = ['deathrow', 'quarantine']
100 excludes = ['deathrow', 'quarantine']
104 packages = []
101 packages = []
105 for dir,subdirs,files in os.walk('IPython'):
102 for dir,subdirs,files in os.walk('IPython'):
106 package = dir.replace(os.path.sep, '.')
103 package = dir.replace(os.path.sep, '.')
107 if any(package.startswith('IPython.'+exc) for exc in excludes):
104 if any(package.startswith('IPython.'+exc) for exc in excludes):
108 # package is to be excluded (e.g. deathrow)
105 # package is to be excluded (e.g. deathrow)
109 continue
106 continue
110 if '__init__.py' not in files:
107 if '__init__.py' not in files:
111 # not a package
108 # not a package
112 continue
109 continue
113 packages.append(package)
110 packages.append(package)
114 return packages
111 return packages
115
112
116 #---------------------------------------------------------------------------
113 #---------------------------------------------------------------------------
117 # Find package data
114 # Find package data
118 #---------------------------------------------------------------------------
115 #---------------------------------------------------------------------------
119
116
120 def find_package_data():
117 def find_package_data():
121 """
118 """
122 Find IPython's package_data.
119 Find IPython's package_data.
123 """
120 """
124 # This is not enough for these things to appear in an sdist.
121 # This is not enough for these things to appear in an sdist.
125 # We need to muck with the MANIFEST to get this to work
122 # We need to muck with the MANIFEST to get this to work
126
123
127 package_data = {
124 package_data = {
128 'IPython.core' : ['profile/README*'],
125 'IPython.core' : ['profile/README*'],
129 'IPython.core.tests' : ['*.png', '*.jpg', 'daft_extension/*.py'],
126 'IPython.core.tests' : ['*.png', '*.jpg', 'daft_extension/*.py'],
130 'IPython.lib.tests' : ['*.wav'],
127 'IPython.lib.tests' : ['*.wav'],
131 'IPython.testing.plugin' : ['*.txt'],
128 'IPython.testing.plugin' : ['*.txt'],
132 }
129 }
133
130
134 return package_data
131 return package_data
135
132
136
133
137 def check_package_data(package_data):
134 def check_package_data(package_data):
138 """verify that package_data globs make sense"""
135 """verify that package_data globs make sense"""
139 print("checking package data")
136 print("checking package data")
140 for pkg, data in package_data.items():
137 for pkg, data in package_data.items():
141 pkg_root = pjoin(*pkg.split('.'))
138 pkg_root = pjoin(*pkg.split('.'))
142 for d in data:
139 for d in data:
143 path = pjoin(pkg_root, d)
140 path = pjoin(pkg_root, d)
144 if '*' in path:
141 if '*' in path:
145 assert len(glob(path)) > 0, "No files match pattern %s" % path
142 assert len(glob(path)) > 0, "No files match pattern %s" % path
146 else:
143 else:
147 assert os.path.exists(path), "Missing package data: %s" % path
144 assert os.path.exists(path), "Missing package data: %s" % path
148
145
149
146
150 def check_package_data_first(command):
147 def check_package_data_first(command):
151 """decorator for checking package_data before running a given command
148 """decorator for checking package_data before running a given command
152
149
153 Probably only needs to wrap build_py
150 Probably only needs to wrap build_py
154 """
151 """
155 class DecoratedCommand(command):
152 class DecoratedCommand(command):
156 def run(self):
153 def run(self):
157 check_package_data(self.package_data)
154 check_package_data(self.package_data)
158 command.run(self)
155 command.run(self)
159 return DecoratedCommand
156 return DecoratedCommand
160
157
161
158
162 #---------------------------------------------------------------------------
159 #---------------------------------------------------------------------------
163 # Find data files
160 # Find data files
164 #---------------------------------------------------------------------------
161 #---------------------------------------------------------------------------
165
162
166 def make_dir_struct(tag,base,out_base):
163 def make_dir_struct(tag,base,out_base):
167 """Make the directory structure of all files below a starting dir.
164 """Make the directory structure of all files below a starting dir.
168
165
169 This is just a convenience routine to help build a nested directory
166 This is just a convenience routine to help build a nested directory
170 hierarchy because distutils is too stupid to do this by itself.
167 hierarchy because distutils is too stupid to do this by itself.
171
168
172 XXX - this needs a proper docstring!
169 XXX - this needs a proper docstring!
173 """
170 """
174
171
175 # we'll use these a lot below
172 # we'll use these a lot below
176 lbase = len(base)
173 lbase = len(base)
177 pathsep = os.path.sep
174 pathsep = os.path.sep
178 lpathsep = len(pathsep)
175 lpathsep = len(pathsep)
179
176
180 out = []
177 out = []
181 for (dirpath,dirnames,filenames) in os.walk(base):
178 for (dirpath,dirnames,filenames) in os.walk(base):
182 # we need to strip out the dirpath from the base to map it to the
179 # we need to strip out the dirpath from the base to map it to the
183 # output (installation) path. This requires possibly stripping the
180 # output (installation) path. This requires possibly stripping the
184 # path separator, because otherwise pjoin will not work correctly
181 # path separator, because otherwise pjoin will not work correctly
185 # (pjoin('foo/','/bar') returns '/bar').
182 # (pjoin('foo/','/bar') returns '/bar').
186
183
187 dp_eff = dirpath[lbase:]
184 dp_eff = dirpath[lbase:]
188 if dp_eff.startswith(pathsep):
185 if dp_eff.startswith(pathsep):
189 dp_eff = dp_eff[lpathsep:]
186 dp_eff = dp_eff[lpathsep:]
190 # The output path must be anchored at the out_base marker
187 # The output path must be anchored at the out_base marker
191 out_path = pjoin(out_base,dp_eff)
188 out_path = pjoin(out_base,dp_eff)
192 # Now we can generate the final filenames. Since os.walk only produces
189 # Now we can generate the final filenames. Since os.walk only produces
193 # filenames, we must join back with the dirpath to get full valid file
190 # filenames, we must join back with the dirpath to get full valid file
194 # paths:
191 # paths:
195 pfiles = [pjoin(dirpath,f) for f in filenames]
192 pfiles = [pjoin(dirpath,f) for f in filenames]
196 # Finally, generate the entry we need, which is a pari of (output
193 # Finally, generate the entry we need, which is a pari of (output
197 # path, files) for use as a data_files parameter in install_data.
194 # path, files) for use as a data_files parameter in install_data.
198 out.append((out_path, pfiles))
195 out.append((out_path, pfiles))
199
196
200 return out
197 return out
201
198
202
199
203 def find_data_files():
200 def find_data_files():
204 """
201 """
205 Find IPython's data_files.
202 Find IPython's data_files.
206
203
207 Just man pages at this point.
204 Just man pages at this point.
208 """
205 """
209
206
210 manpagebase = pjoin('share', 'man', 'man1')
207 manpagebase = pjoin('share', 'man', 'man1')
211
208
212 # Simple file lists can be made by hand
209 # Simple file lists can be made by hand
213 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
210 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
214 if not manpages:
211 if not manpages:
215 # When running from a source tree, the manpages aren't gzipped
212 # When running from a source tree, the manpages aren't gzipped
216 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
213 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
217
214
218 # And assemble the entire output list
215 # And assemble the entire output list
219 data_files = [ (manpagebase, manpages) ]
216 data_files = [ (manpagebase, manpages) ]
220
217
221 return data_files
218 return data_files
222
219
223
220
224 def make_man_update_target(manpage):
221 def make_man_update_target(manpage):
225 """Return a target_update-compliant tuple for the given manpage.
222 """Return a target_update-compliant tuple for the given manpage.
226
223
227 Parameters
224 Parameters
228 ----------
225 ----------
229 manpage : string
226 manpage : string
230 Name of the manpage, must include the section number (trailing number).
227 Name of the manpage, must include the section number (trailing number).
231
228
232 Example
229 Example
233 -------
230 -------
234
231
235 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
232 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
236 ('docs/man/ipython.1.gz',
233 ('docs/man/ipython.1.gz',
237 ['docs/man/ipython.1'],
234 ['docs/man/ipython.1'],
238 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
235 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
239 """
236 """
240 man_dir = pjoin('docs', 'man')
237 man_dir = pjoin('docs', 'man')
241 manpage_gz = manpage + '.gz'
238 manpage_gz = manpage + '.gz'
242 manpath = pjoin(man_dir, manpage)
239 manpath = pjoin(man_dir, manpage)
243 manpath_gz = pjoin(man_dir, manpage_gz)
240 manpath_gz = pjoin(man_dir, manpage_gz)
244 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
241 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
245 locals() )
242 locals() )
246 return (manpath_gz, [manpath], gz_cmd)
243 return (manpath_gz, [manpath], gz_cmd)
247
244
248 # The two functions below are copied from IPython.utils.path, so we don't need
245 # The two functions below are copied from IPython.utils.path, so we don't need
249 # to import IPython during setup, which fails on Python 3.
246 # to import IPython during setup, which fails on Python 3.
250
247
251 def target_outdated(target,deps):
248 def target_outdated(target,deps):
252 """Determine whether a target is out of date.
249 """Determine whether a target is out of date.
253
250
254 target_outdated(target,deps) -> 1/0
251 target_outdated(target,deps) -> 1/0
255
252
256 deps: list of filenames which MUST exist.
253 deps: list of filenames which MUST exist.
257 target: single filename which may or may not exist.
254 target: single filename which may or may not exist.
258
255
259 If target doesn't exist or is older than any file listed in deps, return
256 If target doesn't exist or is older than any file listed in deps, return
260 true, otherwise return false.
257 true, otherwise return false.
261 """
258 """
262 try:
259 try:
263 target_time = os.path.getmtime(target)
260 target_time = os.path.getmtime(target)
264 except os.error:
261 except os.error:
265 return 1
262 return 1
266 for dep in deps:
263 for dep in deps:
267 dep_time = os.path.getmtime(dep)
264 dep_time = os.path.getmtime(dep)
268 if dep_time > target_time:
265 if dep_time > target_time:
269 #print "For target",target,"Dep failed:",dep # dbg
266 #print "For target",target,"Dep failed:",dep # dbg
270 #print "times (dep,tar):",dep_time,target_time # dbg
267 #print "times (dep,tar):",dep_time,target_time # dbg
271 return 1
268 return 1
272 return 0
269 return 0
273
270
274
271
275 def target_update(target,deps,cmd):
272 def target_update(target,deps,cmd):
276 """Update a target with a given command given a list of dependencies.
273 """Update a target with a given command given a list of dependencies.
277
274
278 target_update(target,deps,cmd) -> runs cmd if target is outdated.
275 target_update(target,deps,cmd) -> runs cmd if target is outdated.
279
276
280 This is just a wrapper around target_outdated() which calls the given
277 This is just a wrapper around target_outdated() which calls the given
281 command if target is outdated."""
278 command if target is outdated."""
282
279
283 if target_outdated(target,deps):
280 if target_outdated(target,deps):
284 os.system(cmd)
281 os.system(cmd)
285
282
286 #---------------------------------------------------------------------------
283 #---------------------------------------------------------------------------
287 # Find scripts
284 # Find scripts
288 #---------------------------------------------------------------------------
285 #---------------------------------------------------------------------------
289
286
290 def find_entry_points():
287 def find_entry_points():
291 """Defines the command line entry points for IPython
288 """Defines the command line entry points for IPython
292
289
293 This always uses setuptools-style entry points. When setuptools is not in
290 This always uses setuptools-style entry points. When setuptools is not in
294 use, our own build_scripts_entrypt class below parses these and builds
291 use, our own build_scripts_entrypt class below parses these and builds
295 command line scripts.
292 command line scripts.
296
293
297 Each of our entry points gets both a plain name, e.g. ipython, and one
294 Each of our entry points gets both a plain name, e.g. ipython, and one
298 suffixed with the Python major version number, e.g. ipython3.
295 suffixed with the Python major version number, e.g. ipython3.
299 """
296 """
300 ep = [
297 ep = [
301 'ipython%s = IPython:start_ipython',
298 'ipython%s = IPython:start_ipython',
302 'iptest%s = IPython.testing.iptestcontroller:main',
299 'iptest%s = IPython.testing.iptestcontroller:main',
303 ]
300 ]
304 suffix = str(sys.version_info[0])
301 suffix = str(sys.version_info[0])
305 return [e % '' for e in ep] + [e % suffix for e in ep]
302 return [e % '' for e in ep] + [e % suffix for e in ep]
306
303
307 script_src = """#!{executable}
304 script_src = """#!{executable}
308 # This script was automatically generated by setup.py
305 # This script was automatically generated by setup.py
309 if __name__ == '__main__':
306 if __name__ == '__main__':
310 from {mod} import {func}
307 from {mod} import {func}
311 {func}()
308 {func}()
312 """
309 """
313
310
314 class build_scripts_entrypt(build_scripts):
311 class build_scripts_entrypt(build_scripts):
315 """Build the command line scripts
312 """Build the command line scripts
316
313
317 Parse setuptools style entry points and write simple scripts to run the
314 Parse setuptools style entry points and write simple scripts to run the
318 target functions.
315 target functions.
319
316
320 On Windows, this also creates .cmd wrappers for the scripts so that you can
317 On Windows, this also creates .cmd wrappers for the scripts so that you can
321 easily launch them from a command line.
318 easily launch them from a command line.
322 """
319 """
323 def run(self):
320 def run(self):
324 self.mkpath(self.build_dir)
321 self.mkpath(self.build_dir)
325 outfiles = []
322 outfiles = []
326 for script in find_entry_points():
323 for script in find_entry_points():
327 name, entrypt = script.split('=')
324 name, entrypt = script.split('=')
328 name = name.strip()
325 name = name.strip()
329 entrypt = entrypt.strip()
326 entrypt = entrypt.strip()
330 outfile = os.path.join(self.build_dir, name)
327 outfile = os.path.join(self.build_dir, name)
331 outfiles.append(outfile)
328 outfiles.append(outfile)
332 print('Writing script to', outfile)
329 print('Writing script to', outfile)
333
330
334 mod, func = entrypt.split(':')
331 mod, func = entrypt.split(':')
335 with open(outfile, 'w') as f:
332 with open(outfile, 'w') as f:
336 f.write(script_src.format(executable=sys.executable,
333 f.write(script_src.format(executable=sys.executable,
337 mod=mod, func=func))
334 mod=mod, func=func))
338
335
339 if sys.platform == 'win32':
336 if sys.platform == 'win32':
340 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
337 # Write .cmd wrappers for Windows so 'ipython' etc. work at the
341 # command line
338 # command line
342 cmd_file = os.path.join(self.build_dir, name + '.cmd')
339 cmd_file = os.path.join(self.build_dir, name + '.cmd')
343 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
340 cmd = '@"{python}" "%~dp0\{script}" %*\r\n'.format(
344 python=sys.executable, script=name)
341 python=sys.executable, script=name)
345 log.info("Writing %s wrapper script" % cmd_file)
342 log.info("Writing %s wrapper script" % cmd_file)
346 with open(cmd_file, 'w') as f:
343 with open(cmd_file, 'w') as f:
347 f.write(cmd)
344 f.write(cmd)
348
345
349 return outfiles, outfiles
346 return outfiles, outfiles
350
347
351 class install_lib_symlink(Command):
348 class install_lib_symlink(Command):
352 user_options = [
349 user_options = [
353 ('install-dir=', 'd', "directory to install to"),
350 ('install-dir=', 'd', "directory to install to"),
354 ]
351 ]
355
352
356 def initialize_options(self):
353 def initialize_options(self):
357 self.install_dir = None
354 self.install_dir = None
358
355
359 def finalize_options(self):
356 def finalize_options(self):
360 self.set_undefined_options('symlink',
357 self.set_undefined_options('symlink',
361 ('install_lib', 'install_dir'),
358 ('install_lib', 'install_dir'),
362 )
359 )
363
360
364 def run(self):
361 def run(self):
365 if sys.platform == 'win32':
362 if sys.platform == 'win32':
366 raise Exception("This doesn't work on Windows.")
363 raise Exception("This doesn't work on Windows.")
367 pkg = os.path.join(os.getcwd(), 'IPython')
364 pkg = os.path.join(os.getcwd(), 'IPython')
368 dest = os.path.join(self.install_dir, 'IPython')
365 dest = os.path.join(self.install_dir, 'IPython')
369 if os.path.islink(dest):
366 if os.path.islink(dest):
370 print('removing existing symlink at %s' % dest)
367 print('removing existing symlink at %s' % dest)
371 os.unlink(dest)
368 os.unlink(dest)
372 print('symlinking %s -> %s' % (pkg, dest))
369 print('symlinking %s -> %s' % (pkg, dest))
373 os.symlink(pkg, dest)
370 os.symlink(pkg, dest)
374
371
375 class unsymlink(install):
372 class unsymlink(install):
376 def run(self):
373 def run(self):
377 dest = os.path.join(self.install_lib, 'IPython')
374 dest = os.path.join(self.install_lib, 'IPython')
378 if os.path.islink(dest):
375 if os.path.islink(dest):
379 print('removing symlink at %s' % dest)
376 print('removing symlink at %s' % dest)
380 os.unlink(dest)
377 os.unlink(dest)
381 else:
378 else:
382 print('No symlink exists at %s' % dest)
379 print('No symlink exists at %s' % dest)
383
380
384 class install_symlinked(install):
381 class install_symlinked(install):
385 def run(self):
382 def run(self):
386 if sys.platform == 'win32':
383 if sys.platform == 'win32':
387 raise Exception("This doesn't work on Windows.")
384 raise Exception("This doesn't work on Windows.")
388
385
389 # Run all sub-commands (at least those that need to be run)
386 # Run all sub-commands (at least those that need to be run)
390 for cmd_name in self.get_sub_commands():
387 for cmd_name in self.get_sub_commands():
391 self.run_command(cmd_name)
388 self.run_command(cmd_name)
392
389
393 # 'sub_commands': a list of commands this command might have to run to
390 # 'sub_commands': a list of commands this command might have to run to
394 # get its work done. See cmd.py for more info.
391 # get its work done. See cmd.py for more info.
395 sub_commands = [('install_lib_symlink', lambda self:True),
392 sub_commands = [('install_lib_symlink', lambda self:True),
396 ('install_scripts_sym', lambda self:True),
393 ('install_scripts_sym', lambda self:True),
397 ]
394 ]
398
395
399 class install_scripts_for_symlink(install_scripts):
396 class install_scripts_for_symlink(install_scripts):
400 """Redefined to get options from 'symlink' instead of 'install'.
397 """Redefined to get options from 'symlink' instead of 'install'.
401
398
402 I love distutils almost as much as I love setuptools.
399 I love distutils almost as much as I love setuptools.
403 """
400 """
404 def finalize_options(self):
401 def finalize_options(self):
405 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
402 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
406 self.set_undefined_options('symlink',
403 self.set_undefined_options('symlink',
407 ('install_scripts', 'install_dir'),
404 ('install_scripts', 'install_dir'),
408 ('force', 'force'),
405 ('force', 'force'),
409 ('skip_build', 'skip_build'),
406 ('skip_build', 'skip_build'),
410 )
407 )
411
408
412
409
413 #---------------------------------------------------------------------------
410 #---------------------------------------------------------------------------
414 # VCS related
411 # VCS related
415 #---------------------------------------------------------------------------
412 #---------------------------------------------------------------------------
416
413
417
414
418 def git_prebuild(pkg_dir, build_cmd=build_py):
415 def git_prebuild(pkg_dir, build_cmd=build_py):
419 """Return extended build or sdist command class for recording commit
416 """Return extended build or sdist command class for recording commit
420
417
421 records git commit in IPython.utils._sysinfo.commit
418 records git commit in IPython.utils._sysinfo.commit
422
419
423 for use in IPython.utils.sysinfo.sys_info() calls after installation.
420 for use in IPython.utils.sysinfo.sys_info() calls after installation.
424 """
421 """
425
422
426 class MyBuildPy(build_cmd):
423 class MyBuildPy(build_cmd):
427 ''' Subclass to write commit data into installation tree '''
424 ''' Subclass to write commit data into installation tree '''
428 def run(self):
425 def run(self):
426 # loose as `.dev` is suppose to be invalid
427 print("check version number")
428 loose_pep440re = re.compile('^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$')
429 import IPython.core.release as r
430 if not loose_pep440re.match(r.version):
431 raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % r.version)
432
433
429 build_cmd.run(self)
434 build_cmd.run(self)
430 # this one will only fire for build commands
435 # this one will only fire for build commands
431 if hasattr(self, 'build_lib'):
436 if hasattr(self, 'build_lib'):
432 self._record_commit(self.build_lib)
437 self._record_commit(self.build_lib)
433
438
434 def make_release_tree(self, base_dir, files):
439 def make_release_tree(self, base_dir, files):
435 # this one will fire for sdist
440 # this one will fire for sdist
436 build_cmd.make_release_tree(self, base_dir, files)
441 build_cmd.make_release_tree(self, base_dir, files)
437 self._record_commit(base_dir)
442 self._record_commit(base_dir)
438
443
439 def _record_commit(self, base_dir):
444 def _record_commit(self, base_dir):
440 import subprocess
445 import subprocess
441 proc = subprocess.Popen('git rev-parse --short HEAD',
446 proc = subprocess.Popen('git rev-parse --short HEAD',
442 stdout=subprocess.PIPE,
447 stdout=subprocess.PIPE,
443 stderr=subprocess.PIPE,
448 stderr=subprocess.PIPE,
444 shell=True)
449 shell=True)
445 repo_commit, _ = proc.communicate()
450 repo_commit, _ = proc.communicate()
446 repo_commit = repo_commit.strip().decode("ascii")
451 repo_commit = repo_commit.strip().decode("ascii")
447
452
448 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
453 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
449 if os.path.isfile(out_pth) and not repo_commit:
454 if os.path.isfile(out_pth) and not repo_commit:
450 # nothing to write, don't clobber
455 # nothing to write, don't clobber
451 return
456 return
452
457
453 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
458 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
454
459
455 # remove to avoid overwriting original via hard link
460 # remove to avoid overwriting original via hard link
456 try:
461 try:
457 os.remove(out_pth)
462 os.remove(out_pth)
458 except (IOError, OSError):
463 except (IOError, OSError):
459 pass
464 pass
460 with open(out_pth, 'w') as out_file:
465 with open(out_pth, 'w') as out_file:
461 out_file.writelines([
466 out_file.writelines([
462 '# GENERATED BY setup.py\n',
467 '# GENERATED BY setup.py\n',
463 'commit = u"%s"\n' % repo_commit,
468 'commit = u"%s"\n' % repo_commit,
464 ])
469 ])
465 return MyBuildPy
470 return MyBuildPy
466
471
General Comments 0
You need to be logged in to leave comments. Login now