##// END OF EJS Templates
fix system->os.system typo...
MinRK -
Show More
@@ -1,428 +1,428 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 This module defines the things that are used in setup.py for building IPython
3 This module defines the things that are used in setup.py for building IPython
4
4
5 This includes:
5 This includes:
6
6
7 * The basic arguments to setup
7 * The basic arguments to setup
8 * Functions for finding things like packages, package data, etc.
8 * Functions for finding things like packages, package data, etc.
9 * A function for checking dependencies.
9 * A function for checking dependencies.
10 """
10 """
11 from __future__ import print_function
11 from __future__ import print_function
12
12
13 #-------------------------------------------------------------------------------
13 #-------------------------------------------------------------------------------
14 # Copyright (C) 2008 The IPython Development Team
14 # Copyright (C) 2008 The IPython Development Team
15 #
15 #
16 # Distributed under the terms of the BSD License. The full license is in
16 # Distributed under the terms of the BSD License. The full license is in
17 # the file COPYING, distributed as part of this software.
17 # the file COPYING, distributed as part of this software.
18 #-------------------------------------------------------------------------------
18 #-------------------------------------------------------------------------------
19
19
20 #-------------------------------------------------------------------------------
20 #-------------------------------------------------------------------------------
21 # Imports
21 # Imports
22 #-------------------------------------------------------------------------------
22 #-------------------------------------------------------------------------------
23 import os
23 import os
24 import sys
24 import sys
25
25
26 try:
26 try:
27 from configparser import ConfigParser
27 from configparser import ConfigParser
28 except:
28 except:
29 from ConfigParser import ConfigParser
29 from ConfigParser import ConfigParser
30 from distutils.command.build_py import build_py
30 from distutils.command.build_py import build_py
31 from glob import glob
31 from glob import glob
32
32
33 from setupext import install_data_ext
33 from setupext import install_data_ext
34
34
35 #-------------------------------------------------------------------------------
35 #-------------------------------------------------------------------------------
36 # Useful globals and utility functions
36 # Useful globals and utility functions
37 #-------------------------------------------------------------------------------
37 #-------------------------------------------------------------------------------
38
38
39 # A few handy globals
39 # A few handy globals
40 isfile = os.path.isfile
40 isfile = os.path.isfile
41 pjoin = os.path.join
41 pjoin = os.path.join
42
42
43 def oscmd(s):
43 def oscmd(s):
44 print(">", s)
44 print(">", s)
45 os.system(s)
45 os.system(s)
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('IPython','core','release.py'), globals())
71 execfile(pjoin('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 download_url = download_url,
83 download_url = download_url,
84 license = license,
84 license = license,
85 platforms = platforms,
85 platforms = platforms,
86 keywords = keywords,
86 keywords = keywords,
87 classifiers = classifiers,
87 classifiers = classifiers,
88 cmdclass = {'install_data': install_data_ext},
88 cmdclass = {'install_data': install_data_ext},
89 )
89 )
90
90
91
91
92 #---------------------------------------------------------------------------
92 #---------------------------------------------------------------------------
93 # Find packages
93 # Find packages
94 #---------------------------------------------------------------------------
94 #---------------------------------------------------------------------------
95
95
96 def find_packages():
96 def find_packages():
97 """
97 """
98 Find all of IPython's packages.
98 Find all of IPython's packages.
99 """
99 """
100 excludes = ['deathrow']
100 excludes = ['deathrow']
101 packages = []
101 packages = []
102 for dir,subdirs,files in os.walk('IPython'):
102 for dir,subdirs,files in os.walk('IPython'):
103 package = dir.replace(os.path.sep, '.')
103 package = dir.replace(os.path.sep, '.')
104 if any([ package.startswith('IPython.'+exc) for exc in excludes ]):
104 if any([ package.startswith('IPython.'+exc) for exc in excludes ]):
105 # package is to be excluded (e.g. deathrow)
105 # package is to be excluded (e.g. deathrow)
106 continue
106 continue
107 if '__init__.py' not in files:
107 if '__init__.py' not in files:
108 # not a package
108 # not a package
109 continue
109 continue
110 packages.append(package)
110 packages.append(package)
111 return packages
111 return packages
112
112
113 #---------------------------------------------------------------------------
113 #---------------------------------------------------------------------------
114 # Find package data
114 # Find package data
115 #---------------------------------------------------------------------------
115 #---------------------------------------------------------------------------
116
116
117 def find_package_data():
117 def find_package_data():
118 """
118 """
119 Find IPython's package_data.
119 Find IPython's package_data.
120 """
120 """
121 # 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.
122 # 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
123
123
124 # exclude static things that we don't ship (e.g. mathjax)
124 # exclude static things that we don't ship (e.g. mathjax)
125 excludes = ['mathjax']
125 excludes = ['mathjax']
126
126
127 # add 'static/' prefix to exclusions, and tuplify for use in startswith
127 # add 'static/' prefix to exclusions, and tuplify for use in startswith
128 excludes = tuple([os.path.join('static', ex) for ex in excludes])
128 excludes = tuple([os.path.join('static', ex) for ex in excludes])
129
129
130 # walk notebook resources:
130 # walk notebook resources:
131 cwd = os.getcwd()
131 cwd = os.getcwd()
132 os.chdir(os.path.join('IPython', 'frontend', 'html', 'notebook'))
132 os.chdir(os.path.join('IPython', 'frontend', 'html', 'notebook'))
133 static_walk = list(os.walk('static'))
133 static_walk = list(os.walk('static'))
134 os.chdir(cwd)
134 os.chdir(cwd)
135 static_data = []
135 static_data = []
136 for parent, dirs, files in static_walk:
136 for parent, dirs, files in static_walk:
137 if parent.startswith(excludes):
137 if parent.startswith(excludes):
138 continue
138 continue
139 for f in files:
139 for f in files:
140 static_data.append(os.path.join(parent, f))
140 static_data.append(os.path.join(parent, f))
141
141
142 package_data = {
142 package_data = {
143 'IPython.config.profile' : ['README*', '*/*.py'],
143 'IPython.config.profile' : ['README*', '*/*.py'],
144 'IPython.testing' : ['*.txt'],
144 'IPython.testing' : ['*.txt'],
145 'IPython.frontend.html.notebook' : ['templates/*'] + static_data,
145 'IPython.frontend.html.notebook' : ['templates/*'] + static_data,
146 'IPython.frontend.qt.console' : ['resources/icon/*.svg'],
146 'IPython.frontend.qt.console' : ['resources/icon/*.svg'],
147 }
147 }
148 return package_data
148 return package_data
149
149
150
150
151 #---------------------------------------------------------------------------
151 #---------------------------------------------------------------------------
152 # Find data files
152 # Find data files
153 #---------------------------------------------------------------------------
153 #---------------------------------------------------------------------------
154
154
155 def make_dir_struct(tag,base,out_base):
155 def make_dir_struct(tag,base,out_base):
156 """Make the directory structure of all files below a starting dir.
156 """Make the directory structure of all files below a starting dir.
157
157
158 This is just a convenience routine to help build a nested directory
158 This is just a convenience routine to help build a nested directory
159 hierarchy because distutils is too stupid to do this by itself.
159 hierarchy because distutils is too stupid to do this by itself.
160
160
161 XXX - this needs a proper docstring!
161 XXX - this needs a proper docstring!
162 """
162 """
163
163
164 # we'll use these a lot below
164 # we'll use these a lot below
165 lbase = len(base)
165 lbase = len(base)
166 pathsep = os.path.sep
166 pathsep = os.path.sep
167 lpathsep = len(pathsep)
167 lpathsep = len(pathsep)
168
168
169 out = []
169 out = []
170 for (dirpath,dirnames,filenames) in os.walk(base):
170 for (dirpath,dirnames,filenames) in os.walk(base):
171 # we need to strip out the dirpath from the base to map it to the
171 # we need to strip out the dirpath from the base to map it to the
172 # output (installation) path. This requires possibly stripping the
172 # output (installation) path. This requires possibly stripping the
173 # path separator, because otherwise pjoin will not work correctly
173 # path separator, because otherwise pjoin will not work correctly
174 # (pjoin('foo/','/bar') returns '/bar').
174 # (pjoin('foo/','/bar') returns '/bar').
175
175
176 dp_eff = dirpath[lbase:]
176 dp_eff = dirpath[lbase:]
177 if dp_eff.startswith(pathsep):
177 if dp_eff.startswith(pathsep):
178 dp_eff = dp_eff[lpathsep:]
178 dp_eff = dp_eff[lpathsep:]
179 # The output path must be anchored at the out_base marker
179 # The output path must be anchored at the out_base marker
180 out_path = pjoin(out_base,dp_eff)
180 out_path = pjoin(out_base,dp_eff)
181 # Now we can generate the final filenames. Since os.walk only produces
181 # 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
182 # filenames, we must join back with the dirpath to get full valid file
183 # paths:
183 # paths:
184 pfiles = [pjoin(dirpath,f) for f in filenames]
184 pfiles = [pjoin(dirpath,f) for f in filenames]
185 # Finally, generate the entry we need, which is a pari of (output
185 # 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.
186 # path, files) for use as a data_files parameter in install_data.
187 out.append((out_path, pfiles))
187 out.append((out_path, pfiles))
188
188
189 return out
189 return out
190
190
191
191
192 def find_data_files():
192 def find_data_files():
193 """
193 """
194 Find IPython's data_files.
194 Find IPython's data_files.
195
195
196 Most of these are docs.
196 Most of these are docs.
197 """
197 """
198
198
199 docdirbase = pjoin('share', 'doc', 'ipython')
199 docdirbase = pjoin('share', 'doc', 'ipython')
200 manpagebase = pjoin('share', 'man', 'man1')
200 manpagebase = pjoin('share', 'man', 'man1')
201
201
202 # Simple file lists can be made by hand
202 # Simple file lists can be made by hand
203 manpages = filter(isfile, glob(pjoin('docs','man','*.1.gz')))
203 manpages = filter(isfile, glob(pjoin('docs','man','*.1.gz')))
204 if not manpages:
204 if not manpages:
205 # When running from a source tree, the manpages aren't gzipped
205 # When running from a source tree, the manpages aren't gzipped
206 manpages = filter(isfile, glob(pjoin('docs','man','*.1')))
206 manpages = filter(isfile, glob(pjoin('docs','man','*.1')))
207 igridhelpfiles = filter(isfile,
207 igridhelpfiles = filter(isfile,
208 glob(pjoin('IPython','extensions','igrid_help.*')))
208 glob(pjoin('IPython','extensions','igrid_help.*')))
209
209
210 # For nested structures, use the utility above
210 # For nested structures, use the utility above
211 example_files = make_dir_struct(
211 example_files = make_dir_struct(
212 'data',
212 'data',
213 pjoin('docs','examples'),
213 pjoin('docs','examples'),
214 pjoin(docdirbase,'examples')
214 pjoin(docdirbase,'examples')
215 )
215 )
216 manual_files = make_dir_struct(
216 manual_files = make_dir_struct(
217 'data',
217 'data',
218 pjoin('docs','html'),
218 pjoin('docs','html'),
219 pjoin(docdirbase,'manual')
219 pjoin(docdirbase,'manual')
220 )
220 )
221
221
222 # And assemble the entire output list
222 # And assemble the entire output list
223 data_files = [ (manpagebase, manpages),
223 data_files = [ (manpagebase, manpages),
224 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
224 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
225 ] + manual_files + example_files
225 ] + manual_files + example_files
226
226
227 return data_files
227 return data_files
228
228
229
229
230 def make_man_update_target(manpage):
230 def make_man_update_target(manpage):
231 """Return a target_update-compliant tuple for the given manpage.
231 """Return a target_update-compliant tuple for the given manpage.
232
232
233 Parameters
233 Parameters
234 ----------
234 ----------
235 manpage : string
235 manpage : string
236 Name of the manpage, must include the section number (trailing number).
236 Name of the manpage, must include the section number (trailing number).
237
237
238 Example
238 Example
239 -------
239 -------
240
240
241 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
241 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
242 ('docs/man/ipython.1.gz',
242 ('docs/man/ipython.1.gz',
243 ['docs/man/ipython.1'],
243 ['docs/man/ipython.1'],
244 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
244 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
245 """
245 """
246 man_dir = pjoin('docs', 'man')
246 man_dir = pjoin('docs', 'man')
247 manpage_gz = manpage + '.gz'
247 manpage_gz = manpage + '.gz'
248 manpath = pjoin(man_dir, manpage)
248 manpath = pjoin(man_dir, manpage)
249 manpath_gz = pjoin(man_dir, manpage_gz)
249 manpath_gz = pjoin(man_dir, manpage_gz)
250 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
250 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
251 locals() )
251 locals() )
252 return (manpath_gz, [manpath], gz_cmd)
252 return (manpath_gz, [manpath], gz_cmd)
253
253
254 # The two functions below are copied from IPython.utils.path, so we don't need
254 # The two functions below are copied from IPython.utils.path, so we don't need
255 # to import IPython during setup, which fails on Python 3.
255 # to import IPython during setup, which fails on Python 3.
256
256
257 def target_outdated(target,deps):
257 def target_outdated(target,deps):
258 """Determine whether a target is out of date.
258 """Determine whether a target is out of date.
259
259
260 target_outdated(target,deps) -> 1/0
260 target_outdated(target,deps) -> 1/0
261
261
262 deps: list of filenames which MUST exist.
262 deps: list of filenames which MUST exist.
263 target: single filename which may or may not exist.
263 target: single filename which may or may not exist.
264
264
265 If target doesn't exist or is older than any file listed in deps, return
265 If target doesn't exist or is older than any file listed in deps, return
266 true, otherwise return false.
266 true, otherwise return false.
267 """
267 """
268 try:
268 try:
269 target_time = os.path.getmtime(target)
269 target_time = os.path.getmtime(target)
270 except os.error:
270 except os.error:
271 return 1
271 return 1
272 for dep in deps:
272 for dep in deps:
273 dep_time = os.path.getmtime(dep)
273 dep_time = os.path.getmtime(dep)
274 if dep_time > target_time:
274 if dep_time > target_time:
275 #print "For target",target,"Dep failed:",dep # dbg
275 #print "For target",target,"Dep failed:",dep # dbg
276 #print "times (dep,tar):",dep_time,target_time # dbg
276 #print "times (dep,tar):",dep_time,target_time # dbg
277 return 1
277 return 1
278 return 0
278 return 0
279
279
280
280
281 def target_update(target,deps,cmd):
281 def target_update(target,deps,cmd):
282 """Update a target with a given command given a list of dependencies.
282 """Update a target with a given command given a list of dependencies.
283
283
284 target_update(target,deps,cmd) -> runs cmd if target is outdated.
284 target_update(target,deps,cmd) -> runs cmd if target is outdated.
285
285
286 This is just a wrapper around target_outdated() which calls the given
286 This is just a wrapper around target_outdated() which calls the given
287 command if target is outdated."""
287 command if target is outdated."""
288
288
289 if target_outdated(target,deps):
289 if target_outdated(target,deps):
290 system(cmd)
290 os.system(cmd)
291
291
292 #---------------------------------------------------------------------------
292 #---------------------------------------------------------------------------
293 # Find scripts
293 # Find scripts
294 #---------------------------------------------------------------------------
294 #---------------------------------------------------------------------------
295
295
296 def find_scripts(entry_points=False, suffix=''):
296 def find_scripts(entry_points=False, suffix=''):
297 """Find IPython's scripts.
297 """Find IPython's scripts.
298
298
299 if entry_points is True:
299 if entry_points is True:
300 return setuptools entry_point-style definitions
300 return setuptools entry_point-style definitions
301 else:
301 else:
302 return file paths of plain scripts [default]
302 return file paths of plain scripts [default]
303
303
304 suffix is appended to script names if entry_points is True, so that the
304 suffix is appended to script names if entry_points is True, so that the
305 Python 3 scripts get named "ipython3" etc.
305 Python 3 scripts get named "ipython3" etc.
306 """
306 """
307 if entry_points:
307 if entry_points:
308 console_scripts = [s % suffix for s in [
308 console_scripts = [s % suffix for s in [
309 'ipython%s = IPython.frontend.terminal.ipapp:launch_new_instance',
309 'ipython%s = IPython.frontend.terminal.ipapp:launch_new_instance',
310 'pycolor%s = IPython.utils.PyColorize:main',
310 'pycolor%s = IPython.utils.PyColorize:main',
311 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
311 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
312 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
312 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
313 'iplogger%s = IPython.parallel.apps.iploggerapp:launch_new_instance',
313 'iplogger%s = IPython.parallel.apps.iploggerapp:launch_new_instance',
314 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
314 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
315 'iptest%s = IPython.testing.iptest:main',
315 'iptest%s = IPython.testing.iptest:main',
316 'irunner%s = IPython.lib.irunner:main'
316 'irunner%s = IPython.lib.irunner:main'
317 ]]
317 ]]
318 gui_scripts = [s % suffix for s in [
318 gui_scripts = [s % suffix for s in [
319 'ipython%s-qtconsole = IPython.frontend.qt.console.qtconsoleapp:main',
319 'ipython%s-qtconsole = IPython.frontend.qt.console.qtconsoleapp:main',
320 ]]
320 ]]
321 scripts = dict(console_scripts=console_scripts, gui_scripts=gui_scripts)
321 scripts = dict(console_scripts=console_scripts, gui_scripts=gui_scripts)
322 else:
322 else:
323 parallel_scripts = pjoin('IPython','parallel','scripts')
323 parallel_scripts = pjoin('IPython','parallel','scripts')
324 main_scripts = pjoin('IPython','scripts')
324 main_scripts = pjoin('IPython','scripts')
325 scripts = [
325 scripts = [
326 pjoin(parallel_scripts, 'ipengine'),
326 pjoin(parallel_scripts, 'ipengine'),
327 pjoin(parallel_scripts, 'ipcontroller'),
327 pjoin(parallel_scripts, 'ipcontroller'),
328 pjoin(parallel_scripts, 'ipcluster'),
328 pjoin(parallel_scripts, 'ipcluster'),
329 pjoin(parallel_scripts, 'iplogger'),
329 pjoin(parallel_scripts, 'iplogger'),
330 pjoin(main_scripts, 'ipython'),
330 pjoin(main_scripts, 'ipython'),
331 pjoin(main_scripts, 'pycolor'),
331 pjoin(main_scripts, 'pycolor'),
332 pjoin(main_scripts, 'irunner'),
332 pjoin(main_scripts, 'irunner'),
333 pjoin(main_scripts, 'iptest')
333 pjoin(main_scripts, 'iptest')
334 ]
334 ]
335 return scripts
335 return scripts
336
336
337 #---------------------------------------------------------------------------
337 #---------------------------------------------------------------------------
338 # Verify all dependencies
338 # Verify all dependencies
339 #---------------------------------------------------------------------------
339 #---------------------------------------------------------------------------
340
340
341 def check_for_dependencies():
341 def check_for_dependencies():
342 """Check for IPython's dependencies.
342 """Check for IPython's dependencies.
343
343
344 This function should NOT be called if running under setuptools!
344 This function should NOT be called if running under setuptools!
345 """
345 """
346 from setupext.setupext import (
346 from setupext.setupext import (
347 print_line, print_raw, print_status,
347 print_line, print_raw, print_status,
348 check_for_sphinx, check_for_pygments,
348 check_for_sphinx, check_for_pygments,
349 check_for_nose, check_for_pexpect,
349 check_for_nose, check_for_pexpect,
350 check_for_pyzmq, check_for_readline
350 check_for_pyzmq, check_for_readline
351 )
351 )
352 print_line()
352 print_line()
353 print_raw("BUILDING IPYTHON")
353 print_raw("BUILDING IPYTHON")
354 print_status('python', sys.version)
354 print_status('python', sys.version)
355 print_status('platform', sys.platform)
355 print_status('platform', sys.platform)
356 if sys.platform == 'win32':
356 if sys.platform == 'win32':
357 print_status('Windows version', sys.getwindowsversion())
357 print_status('Windows version', sys.getwindowsversion())
358
358
359 print_raw("")
359 print_raw("")
360 print_raw("OPTIONAL DEPENDENCIES")
360 print_raw("OPTIONAL DEPENDENCIES")
361
361
362 check_for_sphinx()
362 check_for_sphinx()
363 check_for_pygments()
363 check_for_pygments()
364 check_for_nose()
364 check_for_nose()
365 check_for_pexpect()
365 check_for_pexpect()
366 check_for_pyzmq()
366 check_for_pyzmq()
367 check_for_readline()
367 check_for_readline()
368
368
369 def record_commit_info(pkg_dir, build_cmd=build_py):
369 def record_commit_info(pkg_dir, build_cmd=build_py):
370 """ Return extended build command class for recording commit
370 """ Return extended build command class for recording commit
371
371
372 The extended command tries to run git to find the current commit, getting
372 The extended command tries to run git to find the current commit, getting
373 the empty string if it fails. It then writes the commit hash into a file
373 the empty string if it fails. It then writes the commit hash into a file
374 in the `pkg_dir` path, named ``.git_commit_info.ini``.
374 in the `pkg_dir` path, named ``.git_commit_info.ini``.
375
375
376 In due course this information can be used by the package after it is
376 In due course this information can be used by the package after it is
377 installed, to tell you what commit it was installed from if known.
377 installed, to tell you what commit it was installed from if known.
378
378
379 To make use of this system, you need a package with a .git_commit_info.ini
379 To make use of this system, you need a package with a .git_commit_info.ini
380 file - e.g. ``myproject/.git_commit_info.ini`` - that might well look like
380 file - e.g. ``myproject/.git_commit_info.ini`` - that might well look like
381 this::
381 this::
382
382
383 # This is an ini file that may contain information about the code state
383 # This is an ini file that may contain information about the code state
384 [commit hash]
384 [commit hash]
385 # The line below may contain a valid hash if it has been substituted
385 # The line below may contain a valid hash if it has been substituted
386 # during 'git archive'
386 # during 'git archive'
387 archive_subst_hash=$Format:%h$
387 archive_subst_hash=$Format:%h$
388 # This line may be modified by the install process
388 # This line may be modified by the install process
389 install_hash=
389 install_hash=
390
390
391 The .git_commit_info file above is also designed to be used with git
391 The .git_commit_info file above is also designed to be used with git
392 substitution - so you probably also want a ``.gitattributes`` file in the
392 substitution - so you probably also want a ``.gitattributes`` file in the
393 root directory of your working tree that contains something like this::
393 root directory of your working tree that contains something like this::
394
394
395 myproject/.git_commit_info.ini export-subst
395 myproject/.git_commit_info.ini export-subst
396
396
397 That will cause the ``.git_commit_info.ini`` file to get filled in by ``git
397 That will cause the ``.git_commit_info.ini`` file to get filled in by ``git
398 archive`` - useful in case someone makes such an archive - for example with
398 archive`` - useful in case someone makes such an archive - for example with
399 via the github 'download source' button.
399 via the github 'download source' button.
400
400
401 Although all the above will work as is, you might consider having something
401 Although all the above will work as is, you might consider having something
402 like a ``get_info()`` function in your package to display the commit
402 like a ``get_info()`` function in your package to display the commit
403 information at the terminal. See the ``pkg_info.py`` module in the nipy
403 information at the terminal. See the ``pkg_info.py`` module in the nipy
404 package for an example.
404 package for an example.
405 """
405 """
406 class MyBuildPy(build_cmd):
406 class MyBuildPy(build_cmd):
407 ''' Subclass to write commit data into installation tree '''
407 ''' Subclass to write commit data into installation tree '''
408 def run(self):
408 def run(self):
409 build_cmd.run(self)
409 build_cmd.run(self)
410 import subprocess
410 import subprocess
411 proc = subprocess.Popen('git rev-parse --short HEAD',
411 proc = subprocess.Popen('git rev-parse --short HEAD',
412 stdout=subprocess.PIPE,
412 stdout=subprocess.PIPE,
413 stderr=subprocess.PIPE,
413 stderr=subprocess.PIPE,
414 shell=True)
414 shell=True)
415 repo_commit, _ = proc.communicate()
415 repo_commit, _ = proc.communicate()
416 # We write the installation commit even if it's empty
416 # We write the installation commit even if it's empty
417 cfg_parser = ConfigParser()
417 cfg_parser = ConfigParser()
418 cfg_parser.read(pjoin(pkg_dir, '.git_commit_info.ini'))
418 cfg_parser.read(pjoin(pkg_dir, '.git_commit_info.ini'))
419 if not cfg_parser.has_section('commit hash'):
419 if not cfg_parser.has_section('commit hash'):
420 # just in case the ini file is empty or doesn't exist, somehow
420 # just in case the ini file is empty or doesn't exist, somehow
421 # we don't want the next line to raise
421 # we don't want the next line to raise
422 cfg_parser.add_section('commit hash')
422 cfg_parser.add_section('commit hash')
423 cfg_parser.set('commit hash', 'install_hash', repo_commit.decode('ascii'))
423 cfg_parser.set('commit hash', 'install_hash', repo_commit.decode('ascii'))
424 out_pth = pjoin(self.build_lib, pkg_dir, '.git_commit_info.ini')
424 out_pth = pjoin(self.build_lib, pkg_dir, '.git_commit_info.ini')
425 out_file = open(out_pth, 'wt')
425 out_file = open(out_pth, 'wt')
426 cfg_parser.write(out_file)
426 cfg_parser.write(out_file)
427 out_file.close()
427 out_file.close()
428 return MyBuildPy
428 return MyBuildPy
General Comments 0
You need to be logged in to leave comments. Login now