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