##// END OF EJS Templates
store git commit hash in utils._sysinfo instead of hidden git_commit_info.ini data file.
MinRK -
Show More
@@ -0,0 +1,2 b''
1 # GENERATED BY setup.py
2 commit = ''
@@ -1,191 +1,167 b''
1 1 # encoding: utf-8
2 2 """
3 3 Utilities for getting information about IPython and the system it's running in.
4 4 """
5 5
6 6 #-----------------------------------------------------------------------------
7 7 # Copyright (C) 2008-2011 The IPython Development Team
8 8 #
9 9 # Distributed under the terms of the BSD License. The full license is in
10 10 # the file COPYING, distributed as part of this software.
11 11 #-----------------------------------------------------------------------------
12 12
13 13 #-----------------------------------------------------------------------------
14 14 # Imports
15 15 #-----------------------------------------------------------------------------
16 16
17 17 import os
18 18 import platform
19 19 import pprint
20 20 import sys
21 21 import subprocess
22 22
23 23 from ConfigParser import ConfigParser
24 24
25 25 from IPython.core import release
26 from IPython.utils import py3compat
27
28 #-----------------------------------------------------------------------------
29 # Globals
30 #-----------------------------------------------------------------------------
31 COMMIT_INFO_FNAME = '.git_commit_info.ini'
26 from IPython.utils import py3compat, _sysinfo
32 27
33 28 #-----------------------------------------------------------------------------
34 29 # Code
35 30 #-----------------------------------------------------------------------------
36 31
37 32 def pkg_commit_hash(pkg_path):
38 33 """Get short form of commit hash given directory `pkg_path`
39 34
40 There should be a file called 'COMMIT_INFO.txt' in `pkg_path`. This is a
41 file in INI file format, with at least one section: ``commit hash``, and two
42 variables ``archive_subst_hash`` and ``install_hash``. The first has a
43 substitution pattern in it which may have been filled by the execution of
44 ``git archive`` if this is an archive generated that way. The second is
45 filled in by the installation, if the installation is from a git archive.
46
47 35 We get the commit hash from (in order of preference):
48 36
49 * A substituted value in ``archive_subst_hash``
50 * A written commit hash value in ``install_hash`
37 * IPython.utils._sysinfo.commit
51 38 * git output, if we are in a git repository
52 39
53 If all these fail, we return a not-found placeholder tuple
40 If these fail, we return a not-found placeholder tuple
54 41
55 42 Parameters
56 43 ----------
57 44 pkg_path : str
58 45 directory containing package
46 only used for getting commit from active repo
59 47
60 48 Returns
61 49 -------
62 50 hash_from : str
63 51 Where we got the hash from - description
64 52 hash_str : str
65 53 short form of hash
66 54 """
67 55 # Try and get commit from written commit text file
68 pth = os.path.join(pkg_path, COMMIT_INFO_FNAME)
69 if not os.path.isfile(pth):
70 raise IOError('Missing commit info file %s' % pth)
71 cfg_parser = ConfigParser()
72 cfg_parser.read(pth)
73 try:
74 archive_subst = cfg_parser.get('commit hash', 'archive_subst_hash')
75 except Exception:
76 pass
77 else:
78 if not archive_subst.startswith('$Format'): # it has been substituted
79 return 'archive substitution', archive_subst
80 install_subst = cfg_parser.get('commit hash', 'install_hash')
81 if install_subst != '':
82 return 'installation', install_subst
56 if _sysinfo.commit:
57 return "installation", _sysinfo.commit
58
83 59 # maybe we are in a repository
84 60 proc = subprocess.Popen('git rev-parse --short HEAD',
85 61 stdout=subprocess.PIPE,
86 62 stderr=subprocess.PIPE,
87 63 cwd=pkg_path, shell=True)
88 64 repo_commit, _ = proc.communicate()
89 65 if repo_commit:
90 66 return 'repository', repo_commit.strip()
91 67 return '(none found)', '<not found>'
92 68
93 69
94 70 def pkg_info(pkg_path):
95 71 """Return dict describing the context of this package
96 72
97 73 Parameters
98 74 ----------
99 75 pkg_path : str
100 76 path containing __init__.py for package
101 77
102 78 Returns
103 79 -------
104 80 context : dict
105 81 with named parameters of interest
106 82 """
107 83 src, hsh = pkg_commit_hash(pkg_path)
108 84 return dict(
109 85 ipython_version=release.version,
110 86 ipython_path=pkg_path,
111 87 commit_source=src,
112 88 commit_hash=hsh,
113 89 sys_version=sys.version,
114 90 sys_executable=sys.executable,
115 91 sys_platform=sys.platform,
116 92 platform=platform.platform(),
117 93 os_name=os.name,
118 94 )
119 95
120 96
121 97 @py3compat.doctest_refactor_print
122 98 def sys_info():
123 99 """Return useful information about IPython and the system, as a string.
124 100
125 101 Example
126 102 -------
127 103 In [2]: print sys_info()
128 104 {'commit_hash': '144fdae', # random
129 105 'commit_source': 'repository',
130 106 'ipython_path': '/home/fperez/usr/lib/python2.6/site-packages/IPython',
131 107 'ipython_version': '0.11.dev',
132 108 'os_name': 'posix',
133 109 'platform': 'Linux-2.6.35-22-generic-i686-with-Ubuntu-10.10-maverick',
134 110 'sys_executable': '/usr/bin/python',
135 111 'sys_platform': 'linux2',
136 112 'sys_version': '2.6.6 (r266:84292, Sep 15 2010, 15:52:39) \\n[GCC 4.4.5]'}
137 113 """
138 114 p = os.path
139 115 path = p.dirname(p.abspath(p.join(__file__, '..')))
140 116 return pprint.pformat(pkg_info(path))
141 117
142 118
143 119 def _num_cpus_unix():
144 120 """Return the number of active CPUs on a Unix system."""
145 121 return os.sysconf("SC_NPROCESSORS_ONLN")
146 122
147 123
148 124 def _num_cpus_darwin():
149 125 """Return the number of active CPUs on a Darwin system."""
150 126 p = subprocess.Popen(['sysctl','-n','hw.ncpu'],stdout=subprocess.PIPE)
151 127 return p.stdout.read()
152 128
153 129
154 130 def _num_cpus_windows():
155 131 """Return the number of active CPUs on a Windows system."""
156 132 return os.environ.get("NUMBER_OF_PROCESSORS")
157 133
158 134
159 135 def num_cpus():
160 136 """Return the effective number of CPUs in the system as an integer.
161 137
162 138 This cross-platform function makes an attempt at finding the total number of
163 139 available CPUs in the system, as returned by various underlying system and
164 140 python calls.
165 141
166 142 If it can't find a sensible answer, it returns 1 (though an error *may* make
167 143 it return a large positive number that's actually incorrect).
168 144 """
169 145
170 146 # Many thanks to the Parallel Python project (http://www.parallelpython.com)
171 147 # for the names of the keys we needed to look up for this function. This
172 148 # code was inspired by their equivalent function.
173 149
174 150 ncpufuncs = {'Linux':_num_cpus_unix,
175 151 'Darwin':_num_cpus_darwin,
176 152 'Windows':_num_cpus_windows,
177 153 # On Vista, python < 2.5.2 has a bug and returns 'Microsoft'
178 154 # See http://bugs.python.org/issue1082 for details.
179 155 'Microsoft':_num_cpus_windows,
180 156 }
181 157
182 158 ncpufunc = ncpufuncs.get(platform.system(),
183 159 # default to unix version (Solaris, AIX, etc)
184 160 _num_cpus_unix)
185 161
186 162 try:
187 163 ncpus = max(1,int(ncpufunc()))
188 164 except:
189 165 ncpus = 1
190 166 return ncpus
191 167
@@ -1,428 +1,397 b''
1 1 # encoding: utf-8
2 2 """
3 3 This module defines the things that are used in setup.py for building IPython
4 4
5 5 This includes:
6 6
7 7 * The basic arguments to setup
8 8 * Functions for finding things like packages, package data, etc.
9 9 * A function for checking dependencies.
10 10 """
11 11 from __future__ import print_function
12 12
13 13 #-------------------------------------------------------------------------------
14 14 # Copyright (C) 2008 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-------------------------------------------------------------------------------
19 19
20 20 #-------------------------------------------------------------------------------
21 21 # Imports
22 22 #-------------------------------------------------------------------------------
23 import io
23 24 import os
24 25 import sys
25 26
26 27 try:
27 28 from configparser import ConfigParser
28 29 except:
29 30 from ConfigParser import ConfigParser
30 31 from distutils.command.build_py import build_py
31 32 from glob import glob
32 33
33 34 from setupext import install_data_ext
34 35
35 36 #-------------------------------------------------------------------------------
36 37 # Useful globals and utility functions
37 38 #-------------------------------------------------------------------------------
38 39
39 40 # A few handy globals
40 41 isfile = os.path.isfile
41 42 pjoin = os.path.join
42 43
43 44 def oscmd(s):
44 45 print(">", s)
45 46 os.system(s)
46 47
47 48 try:
48 49 execfile
49 50 except NameError:
50 51 def execfile(fname, globs, locs=None):
51 52 locs = locs or globs
52 53 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
53 54
54 55 # A little utility we'll need below, since glob() does NOT allow you to do
55 56 # exclusion on multiple endings!
56 57 def file_doesnt_endwith(test,endings):
57 58 """Return true if test is a file and its name does NOT end with any
58 59 of the strings listed in endings."""
59 60 if not isfile(test):
60 61 return False
61 62 for e in endings:
62 63 if test.endswith(e):
63 64 return False
64 65 return True
65 66
66 67 #---------------------------------------------------------------------------
67 68 # Basic project information
68 69 #---------------------------------------------------------------------------
69 70
70 71 # release.py contains version, authors, license, url, keywords, etc.
71 72 execfile(pjoin('IPython','core','release.py'), globals())
72 73
73 74 # Create a dict with the basic information
74 75 # This dict is eventually passed to setup after additional keys are added.
75 76 setup_args = dict(
76 77 name = name,
77 78 version = version,
78 79 description = description,
79 80 long_description = long_description,
80 81 author = author,
81 82 author_email = author_email,
82 83 url = url,
83 84 download_url = download_url,
84 85 license = license,
85 86 platforms = platforms,
86 87 keywords = keywords,
87 88 classifiers = classifiers,
88 89 cmdclass = {'install_data': install_data_ext},
89 90 )
90 91
91 92
92 93 #---------------------------------------------------------------------------
93 94 # Find packages
94 95 #---------------------------------------------------------------------------
95 96
96 97 def find_packages():
97 98 """
98 99 Find all of IPython's packages.
99 100 """
100 101 excludes = ['deathrow']
101 102 packages = []
102 103 for dir,subdirs,files in os.walk('IPython'):
103 104 package = dir.replace(os.path.sep, '.')
104 105 if any([ package.startswith('IPython.'+exc) for exc in excludes ]):
105 106 # package is to be excluded (e.g. deathrow)
106 107 continue
107 108 if '__init__.py' not in files:
108 109 # not a package
109 110 continue
110 111 packages.append(package)
111 112 return packages
112 113
113 114 #---------------------------------------------------------------------------
114 115 # Find package data
115 116 #---------------------------------------------------------------------------
116 117
117 118 def find_package_data():
118 119 """
119 120 Find IPython's package_data.
120 121 """
121 122 # This is not enough for these things to appear in an sdist.
122 123 # We need to muck with the MANIFEST to get this to work
123 124
124 125 # exclude static things that we don't ship (e.g. mathjax)
125 126 excludes = ['mathjax']
126 127
127 128 # add 'static/' prefix to exclusions, and tuplify for use in startswith
128 129 excludes = tuple([os.path.join('static', ex) for ex in excludes])
129 130
130 131 # walk notebook resources:
131 132 cwd = os.getcwd()
132 133 os.chdir(os.path.join('IPython', 'frontend', 'html', 'notebook'))
133 134 static_walk = list(os.walk('static'))
134 135 os.chdir(cwd)
135 136 static_data = []
136 137 for parent, dirs, files in static_walk:
137 138 if parent.startswith(excludes):
138 139 continue
139 140 for f in files:
140 141 static_data.append(os.path.join(parent, f))
141 142
142 143 package_data = {
143 144 'IPython.config.profile' : ['README*', '*/*.py'],
144 145 'IPython.testing' : ['*.txt'],
145 146 'IPython.frontend.html.notebook' : ['templates/*'] + static_data,
146 147 'IPython.frontend.qt.console' : ['resources/icon/*.svg'],
147 148 }
148 149 return package_data
149 150
150 151
151 152 #---------------------------------------------------------------------------
152 153 # Find data files
153 154 #---------------------------------------------------------------------------
154 155
155 156 def make_dir_struct(tag,base,out_base):
156 157 """Make the directory structure of all files below a starting dir.
157 158
158 159 This is just a convenience routine to help build a nested directory
159 160 hierarchy because distutils is too stupid to do this by itself.
160 161
161 162 XXX - this needs a proper docstring!
162 163 """
163 164
164 165 # we'll use these a lot below
165 166 lbase = len(base)
166 167 pathsep = os.path.sep
167 168 lpathsep = len(pathsep)
168 169
169 170 out = []
170 171 for (dirpath,dirnames,filenames) in os.walk(base):
171 172 # we need to strip out the dirpath from the base to map it to the
172 173 # output (installation) path. This requires possibly stripping the
173 174 # path separator, because otherwise pjoin will not work correctly
174 175 # (pjoin('foo/','/bar') returns '/bar').
175 176
176 177 dp_eff = dirpath[lbase:]
177 178 if dp_eff.startswith(pathsep):
178 179 dp_eff = dp_eff[lpathsep:]
179 180 # The output path must be anchored at the out_base marker
180 181 out_path = pjoin(out_base,dp_eff)
181 182 # Now we can generate the final filenames. Since os.walk only produces
182 183 # filenames, we must join back with the dirpath to get full valid file
183 184 # paths:
184 185 pfiles = [pjoin(dirpath,f) for f in filenames]
185 186 # Finally, generate the entry we need, which is a pari of (output
186 187 # path, files) for use as a data_files parameter in install_data.
187 188 out.append((out_path, pfiles))
188 189
189 190 return out
190 191
191 192
192 193 def find_data_files():
193 194 """
194 195 Find IPython's data_files.
195 196
196 197 Most of these are docs.
197 198 """
198 199
199 200 docdirbase = pjoin('share', 'doc', 'ipython')
200 201 manpagebase = pjoin('share', 'man', 'man1')
201 202
202 203 # Simple file lists can be made by hand
203 204 manpages = filter(isfile, glob(pjoin('docs','man','*.1.gz')))
204 205 if not manpages:
205 206 # When running from a source tree, the manpages aren't gzipped
206 207 manpages = filter(isfile, glob(pjoin('docs','man','*.1')))
207 208 igridhelpfiles = filter(isfile,
208 209 glob(pjoin('IPython','extensions','igrid_help.*')))
209 210
210 211 # For nested structures, use the utility above
211 212 example_files = make_dir_struct(
212 213 'data',
213 214 pjoin('docs','examples'),
214 215 pjoin(docdirbase,'examples')
215 216 )
216 217 manual_files = make_dir_struct(
217 218 'data',
218 219 pjoin('docs','html'),
219 220 pjoin(docdirbase,'manual')
220 221 )
221 222
222 223 # And assemble the entire output list
223 224 data_files = [ (manpagebase, manpages),
224 225 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
225 226 ] + manual_files + example_files
226 227
227 228 return data_files
228 229
229 230
230 231 def make_man_update_target(manpage):
231 232 """Return a target_update-compliant tuple for the given manpage.
232 233
233 234 Parameters
234 235 ----------
235 236 manpage : string
236 237 Name of the manpage, must include the section number (trailing number).
237 238
238 239 Example
239 240 -------
240 241
241 242 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
242 243 ('docs/man/ipython.1.gz',
243 244 ['docs/man/ipython.1'],
244 245 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
245 246 """
246 247 man_dir = pjoin('docs', 'man')
247 248 manpage_gz = manpage + '.gz'
248 249 manpath = pjoin(man_dir, manpage)
249 250 manpath_gz = pjoin(man_dir, manpage_gz)
250 251 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
251 252 locals() )
252 253 return (manpath_gz, [manpath], gz_cmd)
253 254
254 255 # The two functions below are copied from IPython.utils.path, so we don't need
255 256 # to import IPython during setup, which fails on Python 3.
256 257
257 258 def target_outdated(target,deps):
258 259 """Determine whether a target is out of date.
259 260
260 261 target_outdated(target,deps) -> 1/0
261 262
262 263 deps: list of filenames which MUST exist.
263 264 target: single filename which may or may not exist.
264 265
265 266 If target doesn't exist or is older than any file listed in deps, return
266 267 true, otherwise return false.
267 268 """
268 269 try:
269 270 target_time = os.path.getmtime(target)
270 271 except os.error:
271 272 return 1
272 273 for dep in deps:
273 274 dep_time = os.path.getmtime(dep)
274 275 if dep_time > target_time:
275 276 #print "For target",target,"Dep failed:",dep # dbg
276 277 #print "times (dep,tar):",dep_time,target_time # dbg
277 278 return 1
278 279 return 0
279 280
280 281
281 282 def target_update(target,deps,cmd):
282 283 """Update a target with a given command given a list of dependencies.
283 284
284 285 target_update(target,deps,cmd) -> runs cmd if target is outdated.
285 286
286 287 This is just a wrapper around target_outdated() which calls the given
287 288 command if target is outdated."""
288 289
289 290 if target_outdated(target,deps):
290 291 system(cmd)
291 292
292 293 #---------------------------------------------------------------------------
293 294 # Find scripts
294 295 #---------------------------------------------------------------------------
295 296
296 297 def find_scripts(entry_points=False, suffix=''):
297 298 """Find IPython's scripts.
298 299
299 300 if entry_points is True:
300 301 return setuptools entry_point-style definitions
301 302 else:
302 303 return file paths of plain scripts [default]
303 304
304 305 suffix is appended to script names if entry_points is True, so that the
305 306 Python 3 scripts get named "ipython3" etc.
306 307 """
307 308 if entry_points:
308 309 console_scripts = [s % suffix for s in [
309 310 'ipython%s = IPython.frontend.terminal.ipapp:launch_new_instance',
310 311 'pycolor%s = IPython.utils.PyColorize:main',
311 312 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
312 313 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
313 314 'iplogger%s = IPython.parallel.apps.iploggerapp:launch_new_instance',
314 315 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
315 316 'iptest%s = IPython.testing.iptest:main',
316 317 'irunner%s = IPython.lib.irunner:main'
317 318 ]]
318 319 gui_scripts = [s % suffix for s in [
319 320 'ipython%s-qtconsole = IPython.frontend.qt.console.qtconsoleapp:main',
320 321 ]]
321 322 scripts = dict(console_scripts=console_scripts, gui_scripts=gui_scripts)
322 323 else:
323 324 parallel_scripts = pjoin('IPython','parallel','scripts')
324 325 main_scripts = pjoin('IPython','scripts')
325 326 scripts = [
326 327 pjoin(parallel_scripts, 'ipengine'),
327 328 pjoin(parallel_scripts, 'ipcontroller'),
328 329 pjoin(parallel_scripts, 'ipcluster'),
329 330 pjoin(parallel_scripts, 'iplogger'),
330 331 pjoin(main_scripts, 'ipython'),
331 332 pjoin(main_scripts, 'pycolor'),
332 333 pjoin(main_scripts, 'irunner'),
333 334 pjoin(main_scripts, 'iptest')
334 335 ]
335 336 return scripts
336 337
337 338 #---------------------------------------------------------------------------
338 339 # Verify all dependencies
339 340 #---------------------------------------------------------------------------
340 341
341 342 def check_for_dependencies():
342 343 """Check for IPython's dependencies.
343 344
344 345 This function should NOT be called if running under setuptools!
345 346 """
346 347 from setupext.setupext import (
347 348 print_line, print_raw, print_status,
348 349 check_for_sphinx, check_for_pygments,
349 350 check_for_nose, check_for_pexpect,
350 351 check_for_pyzmq, check_for_readline
351 352 )
352 353 print_line()
353 354 print_raw("BUILDING IPYTHON")
354 355 print_status('python', sys.version)
355 356 print_status('platform', sys.platform)
356 357 if sys.platform == 'win32':
357 358 print_status('Windows version', sys.getwindowsversion())
358 359
359 360 print_raw("")
360 361 print_raw("OPTIONAL DEPENDENCIES")
361 362
362 363 check_for_sphinx()
363 364 check_for_pygments()
364 365 check_for_nose()
365 366 check_for_pexpect()
366 367 check_for_pyzmq()
367 368 check_for_readline()
368 369
369 370 def record_commit_info(pkg_dir, build_cmd=build_py):
370 371 """ Return extended build command class for recording commit
371
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
374 in the `pkg_dir` path, named ``.git_commit_info.ini``.
375
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.
378
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
381 this::
382
383 # This is an ini file that may contain information about the code state
384 [commit hash]
385 # The line below may contain a valid hash if it has been substituted
386 # during 'git archive'
387 archive_subst_hash=$Format:%h$
388 # This line may be modified by the install process
389 install_hash=
390
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
393 root directory of your working tree that contains something like this::
394
395 myproject/.git_commit_info.ini export-subst
396
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
399 via the github 'download source' button.
400
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
403 information at the terminal. See the ``pkg_info.py`` module in the nipy
404 package for an example.
372
373 records git commit in IPython.utils._sysinfo.commit
374
375 for use in IPython.utils.sysinfo.sys_info() calls after installation.
405 376 """
377
406 378 class MyBuildPy(build_cmd):
407 379 ''' Subclass to write commit data into installation tree '''
408 380 def run(self):
409 381 build_cmd.run(self)
410 382 import subprocess
411 383 proc = subprocess.Popen('git rev-parse --short HEAD',
412 384 stdout=subprocess.PIPE,
413 385 stderr=subprocess.PIPE,
414 386 shell=True)
415 387 repo_commit, _ = proc.communicate()
388 repo_commit = repo_commit.strip()
416 389 # We write the installation commit even if it's empty
417 cfg_parser = ConfigParser()
418 cfg_parser.read(pjoin(pkg_dir, '.git_commit_info.ini'))
419 if not cfg_parser.has_section('commit hash'):
420 # just in case the ini file is empty or doesn't exist, somehow
421 # we don't want the next line to raise
422 cfg_parser.add_section('commit hash')
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')
425 out_file = open(out_pth, 'wt')
426 cfg_parser.write(out_file)
427 out_file.close()
390 out_pth = pjoin(self.build_lib, pkg_dir, 'utils', '_sysinfo.py')
391 with io.open(out_pth, 'w') as out_file:
392 for line in [
393 u"# GENERATED BY setup.py",
394 u"commit = '%s'" % repo_commit,
395 ]:
396 out_file.write(line + u'\n')
428 397 return MyBuildPy
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now