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