|
1 #!/usr/bin/env python |
|
2 |
|
3 """ This module tries to retrieve as much platform-identifying data as |
|
4 possible. It makes this information available via function APIs. |
|
5 |
|
6 If called from the command line, it prints the platform |
|
7 information concatenated as single string to stdout. The output |
|
8 format is useable as part of a filename. |
|
9 |
|
10 """ |
|
11 # This module is maintained by Marc-Andre Lemburg <mal@egenix.com>. |
|
12 # If you find problems, please submit bug reports/patches via the |
|
13 # Python SourceForge Project Page and assign them to "lemburg". |
|
14 # |
|
15 # Note: Please keep this module compatible to Python 1.5.2. |
|
16 # |
|
17 # Still needed: |
|
18 # * more support for WinCE |
|
19 # * support for MS-DOS (PythonDX ?) |
|
20 # * support for Amiga and other still unsupported platforms running Python |
|
21 # * support for additional Linux distributions |
|
22 # |
|
23 # Many thanks to all those who helped adding platform-specific |
|
24 # checks (in no particular order): |
|
25 # |
|
26 # Charles G Waldman, David Arnold, Gordon McMillan, Ben Darnell, |
|
27 # Jeff Bauer, Cliff Crawford, Ivan Van Laningham, Josef |
|
28 # Betancourt, Randall Hopper, Karl Putland, John Farrell, Greg |
|
29 # Andruk, Just van Rossum, Thomas Heller, Mark R. Levinson, Mark |
|
30 # Hammond, Bill Tutt, Hans Nowak, Uwe Zessin (OpenVMS support), |
|
31 # Colin Kong, Trent Mick, Guido van Rossum |
|
32 # |
|
33 # History: |
|
34 # |
|
35 # <see CVS and SVN checkin messages for history> |
|
36 # |
|
37 # 1.0.3 - added normalization of Windows system name |
|
38 # 1.0.2 - added more Windows support |
|
39 # 1.0.1 - reformatted to make doc.py happy |
|
40 # 1.0.0 - reformatted a bit and checked into Python CVS |
|
41 # 0.8.0 - added sys.version parser and various new access |
|
42 # APIs (python_version(), python_compiler(), etc.) |
|
43 # 0.7.2 - fixed architecture() to use sizeof(pointer) where available |
|
44 # 0.7.1 - added support for Caldera OpenLinux |
|
45 # 0.7.0 - some fixes for WinCE; untabified the source file |
|
46 # 0.6.2 - support for OpenVMS - requires version 1.5.2-V006 or higher and |
|
47 # vms_lib.getsyi() configured |
|
48 # 0.6.1 - added code to prevent 'uname -p' on platforms which are |
|
49 # known not to support it |
|
50 # 0.6.0 - fixed win32_ver() to hopefully work on Win95,98,NT and Win2k; |
|
51 # did some cleanup of the interfaces - some APIs have changed |
|
52 # 0.5.5 - fixed another type in the MacOS code... should have |
|
53 # used more coffee today ;-) |
|
54 # 0.5.4 - fixed a few typos in the MacOS code |
|
55 # 0.5.3 - added experimental MacOS support; added better popen() |
|
56 # workarounds in _syscmd_ver() -- still not 100% elegant |
|
57 # though |
|
58 # 0.5.2 - fixed uname() to return '' instead of 'unknown' in all |
|
59 # return values (the system uname command tends to return |
|
60 # 'unknown' instead of just leaving the field emtpy) |
|
61 # 0.5.1 - included code for slackware dist; added exception handlers |
|
62 # to cover up situations where platforms don't have os.popen |
|
63 # (e.g. Mac) or fail on socket.gethostname(); fixed libc |
|
64 # detection RE |
|
65 # 0.5.0 - changed the API names referring to system commands to *syscmd*; |
|
66 # added java_ver(); made syscmd_ver() a private |
|
67 # API (was system_ver() in previous versions) -- use uname() |
|
68 # instead; extended the win32_ver() to also return processor |
|
69 # type information |
|
70 # 0.4.0 - added win32_ver() and modified the platform() output for WinXX |
|
71 # 0.3.4 - fixed a bug in _follow_symlinks() |
|
72 # 0.3.3 - fixed popen() and "file" command invokation bugs |
|
73 # 0.3.2 - added architecture() API and support for it in platform() |
|
74 # 0.3.1 - fixed syscmd_ver() RE to support Windows NT |
|
75 # 0.3.0 - added system alias support |
|
76 # 0.2.3 - removed 'wince' again... oh well. |
|
77 # 0.2.2 - added 'wince' to syscmd_ver() supported platforms |
|
78 # 0.2.1 - added cache logic and changed the platform string format |
|
79 # 0.2.0 - changed the API to use functions instead of module globals |
|
80 # since some action take too long to be run on module import |
|
81 # 0.1.0 - first release |
|
82 # |
|
83 # You can always get the latest version of this module at: |
|
84 # |
|
85 # http://www.egenix.com/files/python/platform.py |
|
86 # |
|
87 # If that URL should fail, try contacting the author. |
|
88 |
|
89 __copyright__ = """ |
|
90 Copyright (c) 1999-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com |
|
91 Copyright (c) 2000-2003, eGenix.com Software GmbH; mailto:info@egenix.com |
|
92 |
|
93 Permission to use, copy, modify, and distribute this software and its |
|
94 documentation for any purpose and without fee or royalty is hereby granted, |
|
95 provided that the above copyright notice appear in all copies and that |
|
96 both that copyright notice and this permission notice appear in |
|
97 supporting documentation or portions thereof, including modifications, |
|
98 that you make. |
|
99 |
|
100 EGENIX.COM SOFTWARE GMBH DISCLAIMS ALL WARRANTIES WITH REGARD TO |
|
101 THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND |
|
102 FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, |
|
103 INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING |
|
104 FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, |
|
105 NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION |
|
106 WITH THE USE OR PERFORMANCE OF THIS SOFTWARE ! |
|
107 |
|
108 """ |
|
109 |
|
110 __version__ = '1.0.4' |
|
111 |
|
112 import sys,string,os,re |
|
113 |
|
114 ### Platform specific APIs |
|
115 |
|
116 _libc_search = re.compile(r'(__libc_init)' |
|
117 '|' |
|
118 '(GLIBC_([0-9.]+))' |
|
119 '|' |
|
120 '(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)') |
|
121 |
|
122 def libc_ver(executable=sys.executable,lib='',version='', |
|
123 |
|
124 chunksize=2048): |
|
125 |
|
126 """ Tries to determine the libc version that the file executable |
|
127 (which defaults to the Python interpreter) is linked against. |
|
128 |
|
129 Returns a tuple of strings (lib,version) which default to the |
|
130 given parameters in case the lookup fails. |
|
131 |
|
132 Note that the function has intimate knowledge of how different |
|
133 libc versions add symbols to the executable and thus is probably |
|
134 only useable for executables compiled using gcc. |
|
135 |
|
136 The file is read and scanned in chunks of chunksize bytes. |
|
137 |
|
138 """ |
|
139 f = open(executable,'rb') |
|
140 binary = f.read(chunksize) |
|
141 pos = 0 |
|
142 while 1: |
|
143 m = _libc_search.search(binary,pos) |
|
144 if not m: |
|
145 binary = f.read(chunksize) |
|
146 if not binary: |
|
147 break |
|
148 pos = 0 |
|
149 continue |
|
150 libcinit,glibc,glibcversion,so,threads,soversion = m.groups() |
|
151 if libcinit and not lib: |
|
152 lib = 'libc' |
|
153 elif glibc: |
|
154 if lib != 'glibc': |
|
155 lib = 'glibc' |
|
156 version = glibcversion |
|
157 elif glibcversion > version: |
|
158 version = glibcversion |
|
159 elif so: |
|
160 if lib != 'glibc': |
|
161 lib = 'libc' |
|
162 if soversion > version: |
|
163 version = soversion |
|
164 if threads and version[-len(threads):] != threads: |
|
165 version = version + threads |
|
166 pos = m.end() |
|
167 f.close() |
|
168 return lib,version |
|
169 |
|
170 def _dist_try_harder(distname,version,id): |
|
171 |
|
172 """ Tries some special tricks to get the distribution |
|
173 information in case the default method fails. |
|
174 |
|
175 Currently supports older SuSE Linux, Caldera OpenLinux and |
|
176 Slackware Linux distributions. |
|
177 |
|
178 """ |
|
179 if os.path.exists('/var/adm/inst-log/info'): |
|
180 # SuSE Linux stores distribution information in that file |
|
181 info = open('/var/adm/inst-log/info').readlines() |
|
182 distname = 'SuSE' |
|
183 for line in info: |
|
184 tv = string.split(line) |
|
185 if len(tv) == 2: |
|
186 tag,value = tv |
|
187 else: |
|
188 continue |
|
189 if tag == 'MIN_DIST_VERSION': |
|
190 version = string.strip(value) |
|
191 elif tag == 'DIST_IDENT': |
|
192 values = string.split(value,'-') |
|
193 id = values[2] |
|
194 return distname,version,id |
|
195 |
|
196 if os.path.exists('/etc/.installed'): |
|
197 # Caldera OpenLinux has some infos in that file (thanks to Colin Kong) |
|
198 info = open('/etc/.installed').readlines() |
|
199 for line in info: |
|
200 pkg = string.split(line,'-') |
|
201 if len(pkg) >= 2 and pkg[0] == 'OpenLinux': |
|
202 # XXX does Caldera support non Intel platforms ? If yes, |
|
203 # where can we find the needed id ? |
|
204 return 'OpenLinux',pkg[1],id |
|
205 |
|
206 if os.path.isdir('/usr/lib/setup'): |
|
207 # Check for slackware verson tag file (thanks to Greg Andruk) |
|
208 verfiles = os.listdir('/usr/lib/setup') |
|
209 for n in range(len(verfiles)-1, -1, -1): |
|
210 if verfiles[n][:14] != 'slack-version-': |
|
211 del verfiles[n] |
|
212 if verfiles: |
|
213 verfiles.sort() |
|
214 distname = 'slackware' |
|
215 version = verfiles[-1][14:] |
|
216 return distname,version,id |
|
217 |
|
218 return distname,version,id |
|
219 |
|
220 _release_filename = re.compile(r'(\w+)[-_](release|version)') |
|
221 _release_version = re.compile(r'([\d.]+)[^(]*(?:\((.+)\))?') |
|
222 |
|
223 # Note:In supported_dists below we need 'fedora' before 'redhat' as in |
|
224 # Fedora redhat-release is a link to fedora-release. |
|
225 |
|
226 def dist(distname='',version='',id='', |
|
227 |
|
228 supported_dists=('SuSE', 'debian', 'fedora', 'redhat', 'mandrake')): |
|
229 |
|
230 """ Tries to determine the name of the Linux OS distribution name. |
|
231 |
|
232 The function first looks for a distribution release file in |
|
233 /etc and then reverts to _dist_try_harder() in case no |
|
234 suitable files are found. |
|
235 |
|
236 Returns a tuple (distname,version,id) which default to the |
|
237 args given as parameters. |
|
238 |
|
239 """ |
|
240 try: |
|
241 etc = os.listdir('/etc') |
|
242 except os.error: |
|
243 # Probably not a Unix system |
|
244 return distname,version,id |
|
245 for file in etc: |
|
246 m = _release_filename.match(file) |
|
247 if m: |
|
248 _distname,dummy = m.groups() |
|
249 if _distname in supported_dists: |
|
250 distname = _distname |
|
251 break |
|
252 else: |
|
253 return _dist_try_harder(distname,version,id) |
|
254 f = open('/etc/'+file,'r') |
|
255 firstline = f.readline() |
|
256 f.close() |
|
257 m = _release_version.search(firstline) |
|
258 if m: |
|
259 _version,_id = m.groups() |
|
260 if _version: |
|
261 version = _version |
|
262 if _id: |
|
263 id = _id |
|
264 else: |
|
265 # Unkown format... take the first two words |
|
266 l = string.split(string.strip(firstline)) |
|
267 if l: |
|
268 version = l[0] |
|
269 if len(l) > 1: |
|
270 id = l[1] |
|
271 return distname,version,id |
|
272 |
|
273 class _popen: |
|
274 |
|
275 """ Fairly portable (alternative) popen implementation. |
|
276 |
|
277 This is mostly needed in case os.popen() is not available, or |
|
278 doesn't work as advertised, e.g. in Win9X GUI programs like |
|
279 PythonWin or IDLE. |
|
280 |
|
281 Writing to the pipe is currently not supported. |
|
282 |
|
283 """ |
|
284 tmpfile = '' |
|
285 pipe = None |
|
286 bufsize = None |
|
287 mode = 'r' |
|
288 |
|
289 def __init__(self,cmd,mode='r',bufsize=None): |
|
290 |
|
291 if mode != 'r': |
|
292 raise ValueError,'popen()-emulation only supports read mode' |
|
293 import tempfile |
|
294 self.tmpfile = tmpfile = tempfile.mktemp() |
|
295 os.system(cmd + ' > %s' % tmpfile) |
|
296 self.pipe = open(tmpfile,'rb') |
|
297 self.bufsize = bufsize |
|
298 self.mode = mode |
|
299 |
|
300 def read(self): |
|
301 |
|
302 return self.pipe.read() |
|
303 |
|
304 def readlines(self): |
|
305 |
|
306 if self.bufsize is not None: |
|
307 return self.pipe.readlines() |
|
308 |
|
309 def close(self, |
|
310 |
|
311 remove=os.unlink,error=os.error): |
|
312 |
|
313 if self.pipe: |
|
314 rc = self.pipe.close() |
|
315 else: |
|
316 rc = 255 |
|
317 if self.tmpfile: |
|
318 try: |
|
319 remove(self.tmpfile) |
|
320 except error: |
|
321 pass |
|
322 return rc |
|
323 |
|
324 # Alias |
|
325 __del__ = close |
|
326 |
|
327 def popen(cmd, mode='r', bufsize=None): |
|
328 |
|
329 """ Portable popen() interface. |
|
330 """ |
|
331 # Find a working popen implementation preferring win32pipe.popen |
|
332 # over os.popen over _popen |
|
333 popen = None |
|
334 if os.environ.get('OS','') == 'Windows_NT': |
|
335 # On NT win32pipe should work; on Win9x it hangs due to bugs |
|
336 # in the MS C lib (see MS KnowledgeBase article Q150956) |
|
337 try: |
|
338 import win32pipe |
|
339 except ImportError: |
|
340 pass |
|
341 else: |
|
342 popen = win32pipe.popen |
|
343 if popen is None: |
|
344 if hasattr(os,'popen'): |
|
345 popen = os.popen |
|
346 # Check whether it works... it doesn't in GUI programs |
|
347 # on Windows platforms |
|
348 if sys.platform == 'win32': # XXX Others too ? |
|
349 try: |
|
350 popen('') |
|
351 except os.error: |
|
352 popen = _popen |
|
353 else: |
|
354 popen = _popen |
|
355 if bufsize is None: |
|
356 return popen(cmd,mode) |
|
357 else: |
|
358 return popen(cmd,mode,bufsize) |
|
359 |
|
360 def _norm_version(version,build=''): |
|
361 |
|
362 """ Normalize the version and build strings and return a single |
|
363 version string using the format major.minor.build (or patchlevel). |
|
364 """ |
|
365 l = string.split(version,'.') |
|
366 if build: |
|
367 l.append(build) |
|
368 try: |
|
369 ints = map(int,l) |
|
370 except ValueError: |
|
371 strings = l |
|
372 else: |
|
373 strings = map(str,ints) |
|
374 version = string.join(strings[:3],'.') |
|
375 return version |
|
376 |
|
377 _ver_output = re.compile(r'(?:([\w ]+) ([\w.]+) ' |
|
378 '.*' |
|
379 'Version ([\d.]+))') |
|
380 |
|
381 def _syscmd_ver(system='',release='',version='', |
|
382 |
|
383 supported_platforms=('win32','win16','dos','os2')): |
|
384 |
|
385 """ Tries to figure out the OS version used and returns |
|
386 a tuple (system,release,version). |
|
387 |
|
388 It uses the "ver" shell command for this which is known |
|
389 to exists on Windows, DOS and OS/2. XXX Others too ? |
|
390 |
|
391 In case this fails, the given parameters are used as |
|
392 defaults. |
|
393 |
|
394 """ |
|
395 if sys.platform not in supported_platforms: |
|
396 return system,release,version |
|
397 |
|
398 # Try some common cmd strings |
|
399 for cmd in ('ver','command /c ver','cmd /c ver'): |
|
400 try: |
|
401 pipe = popen(cmd) |
|
402 info = pipe.read() |
|
403 if pipe.close(): |
|
404 raise os.error,'command failed' |
|
405 # XXX How can I supress shell errors from being written |
|
406 # to stderr ? |
|
407 except os.error,why: |
|
408 #print 'Command %s failed: %s' % (cmd,why) |
|
409 continue |
|
410 except IOError,why: |
|
411 #print 'Command %s failed: %s' % (cmd,why) |
|
412 continue |
|
413 else: |
|
414 break |
|
415 else: |
|
416 return system,release,version |
|
417 |
|
418 # Parse the output |
|
419 info = string.strip(info) |
|
420 m = _ver_output.match(info) |
|
421 if m: |
|
422 system,release,version = m.groups() |
|
423 # Strip trailing dots from version and release |
|
424 if release[-1] == '.': |
|
425 release = release[:-1] |
|
426 if version[-1] == '.': |
|
427 version = version[:-1] |
|
428 # Normalize the version and build strings (eliminating additional |
|
429 # zeros) |
|
430 version = _norm_version(version) |
|
431 return system,release,version |
|
432 |
|
433 def _win32_getvalue(key,name,default=''): |
|
434 |
|
435 """ Read a value for name from the registry key. |
|
436 |
|
437 In case this fails, default is returned. |
|
438 |
|
439 """ |
|
440 from win32api import RegQueryValueEx |
|
441 try: |
|
442 return RegQueryValueEx(key,name) |
|
443 except: |
|
444 return default |
|
445 |
|
446 def win32_ver(release='',version='',csd='',ptype=''): |
|
447 |
|
448 """ Get additional version information from the Windows Registry |
|
449 and return a tuple (version,csd,ptype) referring to version |
|
450 number, CSD level and OS type (multi/single |
|
451 processor). |
|
452 |
|
453 As a hint: ptype returns 'Uniprocessor Free' on single |
|
454 processor NT machines and 'Multiprocessor Free' on multi |
|
455 processor machines. The 'Free' refers to the OS version being |
|
456 free of debugging code. It could also state 'Checked' which |
|
457 means the OS version uses debugging code, i.e. code that |
|
458 checks arguments, ranges, etc. (Thomas Heller). |
|
459 |
|
460 Note: this function only works if Mark Hammond's win32 |
|
461 package is installed and obviously only runs on Win32 |
|
462 compatible platforms. |
|
463 |
|
464 """ |
|
465 # XXX Is there any way to find out the processor type on WinXX ? |
|
466 # XXX Is win32 available on Windows CE ? |
|
467 # |
|
468 # Adapted from code posted by Karl Putland to comp.lang.python. |
|
469 # |
|
470 # The mappings between reg. values and release names can be found |
|
471 # here: http://msdn.microsoft.com/library/en-us/sysinfo/base/osversioninfo_str.asp |
|
472 |
|
473 # Import the needed APIs |
|
474 try: |
|
475 import win32api |
|
476 except ImportError: |
|
477 return release,version,csd,ptype |
|
478 from win32api import RegQueryValueEx,RegOpenKeyEx,RegCloseKey,GetVersionEx |
|
479 from win32con import HKEY_LOCAL_MACHINE,VER_PLATFORM_WIN32_NT,\ |
|
480 VER_PLATFORM_WIN32_WINDOWS |
|
481 |
|
482 # Find out the registry key and some general version infos |
|
483 maj,min,buildno,plat,csd = GetVersionEx() |
|
484 version = '%i.%i.%i' % (maj,min,buildno & 0xFFFF) |
|
485 if csd[:13] == 'Service Pack ': |
|
486 csd = 'SP' + csd[13:] |
|
487 if plat == VER_PLATFORM_WIN32_WINDOWS: |
|
488 regkey = 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion' |
|
489 # Try to guess the release name |
|
490 if maj == 4: |
|
491 if min == 0: |
|
492 release = '95' |
|
493 elif min == 10: |
|
494 release = '98' |
|
495 elif min == 90: |
|
496 release = 'Me' |
|
497 else: |
|
498 release = 'postMe' |
|
499 elif maj == 5: |
|
500 release = '2000' |
|
501 elif plat == VER_PLATFORM_WIN32_NT: |
|
502 regkey = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion' |
|
503 if maj <= 4: |
|
504 release = 'NT' |
|
505 elif maj == 5: |
|
506 if min == 0: |
|
507 release = '2000' |
|
508 elif min == 1: |
|
509 release = 'XP' |
|
510 elif min == 2: |
|
511 release = '2003Server' |
|
512 else: |
|
513 release = 'post2003' |
|
514 else: |
|
515 if not release: |
|
516 # E.g. Win3.1 with win32s |
|
517 release = '%i.%i' % (maj,min) |
|
518 return release,version,csd,ptype |
|
519 |
|
520 # Open the registry key |
|
521 try: |
|
522 keyCurVer = RegOpenKeyEx(HKEY_LOCAL_MACHINE,regkey) |
|
523 # Get a value to make sure the key exists... |
|
524 RegQueryValueEx(keyCurVer,'SystemRoot') |
|
525 except: |
|
526 return release,version,csd,ptype |
|
527 |
|
528 # Parse values |
|
529 #subversion = _win32_getvalue(keyCurVer, |
|
530 # 'SubVersionNumber', |
|
531 # ('',1))[0] |
|
532 #if subversion: |
|
533 # release = release + subversion # 95a, 95b, etc. |
|
534 build = _win32_getvalue(keyCurVer, |
|
535 'CurrentBuildNumber', |
|
536 ('',1))[0] |
|
537 ptype = _win32_getvalue(keyCurVer, |
|
538 'CurrentType', |
|
539 (ptype,1))[0] |
|
540 |
|
541 # Normalize version |
|
542 version = _norm_version(version,build) |
|
543 |
|
544 # Close key |
|
545 RegCloseKey(keyCurVer) |
|
546 return release,version,csd,ptype |
|
547 |
|
548 def _mac_ver_lookup(selectors,default=None): |
|
549 |
|
550 from gestalt import gestalt |
|
551 import MacOS |
|
552 l = [] |
|
553 append = l.append |
|
554 for selector in selectors: |
|
555 try: |
|
556 append(gestalt(selector)) |
|
557 except (RuntimeError, MacOS.Error): |
|
558 append(default) |
|
559 return l |
|
560 |
|
561 def _bcd2str(bcd): |
|
562 |
|
563 return hex(bcd)[2:] |
|
564 |
|
565 def mac_ver(release='',versioninfo=('','',''),machine=''): |
|
566 |
|
567 """ Get MacOS version information and return it as tuple (release, |
|
568 versioninfo, machine) with versioninfo being a tuple (version, |
|
569 dev_stage, non_release_version). |
|
570 |
|
571 Entries which cannot be determined are set to the paramter values |
|
572 which default to ''. All tuple entries are strings. |
|
573 |
|
574 Thanks to Mark R. Levinson for mailing documentation links and |
|
575 code examples for this function. Documentation for the |
|
576 gestalt() API is available online at: |
|
577 |
|
578 http://www.rgaros.nl/gestalt/ |
|
579 |
|
580 """ |
|
581 # Check whether the version info module is available |
|
582 try: |
|
583 import gestalt |
|
584 import MacOS |
|
585 except ImportError: |
|
586 return release,versioninfo,machine |
|
587 # Get the infos |
|
588 sysv,sysu,sysa = _mac_ver_lookup(('sysv','sysu','sysa')) |
|
589 # Decode the infos |
|
590 if sysv: |
|
591 major = (sysv & 0xFF00) >> 8 |
|
592 minor = (sysv & 0x00F0) >> 4 |
|
593 patch = (sysv & 0x000F) |
|
594 release = '%s.%i.%i' % (_bcd2str(major),minor,patch) |
|
595 if sysu: |
|
596 major = int((sysu & 0xFF000000L) >> 24) |
|
597 minor = (sysu & 0x00F00000) >> 20 |
|
598 bugfix = (sysu & 0x000F0000) >> 16 |
|
599 stage = (sysu & 0x0000FF00) >> 8 |
|
600 nonrel = (sysu & 0x000000FF) |
|
601 version = '%s.%i.%i' % (_bcd2str(major),minor,bugfix) |
|
602 nonrel = _bcd2str(nonrel) |
|
603 stage = {0x20:'development', |
|
604 0x40:'alpha', |
|
605 0x60:'beta', |
|
606 0x80:'final'}.get(stage,'') |
|
607 versioninfo = (version,stage,nonrel) |
|
608 if sysa: |
|
609 machine = {0x1: '68k', |
|
610 0x2: 'PowerPC', |
|
611 0xa: 'i386'}.get(sysa,'') |
|
612 return release,versioninfo,machine |
|
613 |
|
614 def _java_getprop(name,default): |
|
615 |
|
616 from java.lang import System |
|
617 try: |
|
618 return System.getProperty(name) |
|
619 except: |
|
620 return default |
|
621 |
|
622 def java_ver(release='',vendor='',vminfo=('','',''),osinfo=('','','')): |
|
623 |
|
624 """ Version interface for Jython. |
|
625 |
|
626 Returns a tuple (release,vendor,vminfo,osinfo) with vminfo being |
|
627 a tuple (vm_name,vm_release,vm_vendor) and osinfo being a |
|
628 tuple (os_name,os_version,os_arch). |
|
629 |
|
630 Values which cannot be determined are set to the defaults |
|
631 given as parameters (which all default to ''). |
|
632 |
|
633 """ |
|
634 # Import the needed APIs |
|
635 try: |
|
636 import java.lang |
|
637 except ImportError: |
|
638 return release,vendor,vminfo,osinfo |
|
639 |
|
640 vendor = _java_getprop('java.vendor',vendor) |
|
641 release = _java_getprop('java.version',release) |
|
642 vm_name,vm_release,vm_vendor = vminfo |
|
643 vm_name = _java_getprop('java.vm.name',vm_name) |
|
644 vm_vendor = _java_getprop('java.vm.vendor',vm_vendor) |
|
645 vm_release = _java_getprop('java.vm.version',vm_release) |
|
646 vminfo = vm_name,vm_release,vm_vendor |
|
647 os_name,os_version,os_arch = osinfo |
|
648 os_arch = _java_getprop('java.os.arch',os_arch) |
|
649 os_name = _java_getprop('java.os.name',os_name) |
|
650 os_version = _java_getprop('java.os.version',os_version) |
|
651 osinfo = os_name,os_version,os_arch |
|
652 |
|
653 return release,vendor,vminfo,osinfo |
|
654 |
|
655 ### System name aliasing |
|
656 |
|
657 def system_alias(system,release,version): |
|
658 |
|
659 """ Returns (system,release,version) aliased to common |
|
660 marketing names used for some systems. |
|
661 |
|
662 It also does some reordering of the information in some cases |
|
663 where it would otherwise cause confusion. |
|
664 |
|
665 """ |
|
666 if system == 'Rhapsody': |
|
667 # Apple's BSD derivative |
|
668 # XXX How can we determine the marketing release number ? |
|
669 return 'MacOS X Server',system+release,version |
|
670 |
|
671 elif system == 'SunOS': |
|
672 # Sun's OS |
|
673 if release < '5': |
|
674 # These releases use the old name SunOS |
|
675 return system,release,version |
|
676 # Modify release (marketing release = SunOS release - 3) |
|
677 l = string.split(release,'.') |
|
678 if l: |
|
679 try: |
|
680 major = int(l[0]) |
|
681 except ValueError: |
|
682 pass |
|
683 else: |
|
684 major = major - 3 |
|
685 l[0] = str(major) |
|
686 release = string.join(l,'.') |
|
687 if release < '6': |
|
688 system = 'Solaris' |
|
689 else: |
|
690 # XXX Whatever the new SunOS marketing name is... |
|
691 system = 'Solaris' |
|
692 |
|
693 elif system == 'IRIX64': |
|
694 # IRIX reports IRIX64 on platforms with 64-bit support; yet it |
|
695 # is really a version and not a different platform, since 32-bit |
|
696 # apps are also supported.. |
|
697 system = 'IRIX' |
|
698 if version: |
|
699 version = version + ' (64bit)' |
|
700 else: |
|
701 version = '64bit' |
|
702 |
|
703 elif system in ('win32','win16'): |
|
704 # In case one of the other tricks |
|
705 system = 'Windows' |
|
706 |
|
707 return system,release,version |
|
708 |
|
709 ### Various internal helpers |
|
710 |
|
711 def _platform(*args): |
|
712 |
|
713 """ Helper to format the platform string in a filename |
|
714 compatible format e.g. "system-version-machine". |
|
715 """ |
|
716 # Format the platform string |
|
717 platform = string.join( |
|
718 map(string.strip, |
|
719 filter(len,args)), |
|
720 '-') |
|
721 |
|
722 # Cleanup some possible filename obstacles... |
|
723 replace = string.replace |
|
724 platform = replace(platform,' ','_') |
|
725 platform = replace(platform,'/','-') |
|
726 platform = replace(platform,'\\','-') |
|
727 platform = replace(platform,':','-') |
|
728 platform = replace(platform,';','-') |
|
729 platform = replace(platform,'"','-') |
|
730 platform = replace(platform,'(','-') |
|
731 platform = replace(platform,')','-') |
|
732 |
|
733 # No need to report 'unknown' information... |
|
734 platform = replace(platform,'unknown','') |
|
735 |
|
736 # Fold '--'s and remove trailing '-' |
|
737 while 1: |
|
738 cleaned = replace(platform,'--','-') |
|
739 if cleaned == platform: |
|
740 break |
|
741 platform = cleaned |
|
742 while platform[-1] == '-': |
|
743 platform = platform[:-1] |
|
744 |
|
745 return platform |
|
746 |
|
747 def _node(default=''): |
|
748 |
|
749 """ Helper to determine the node name of this machine. |
|
750 """ |
|
751 try: |
|
752 import socket |
|
753 except ImportError: |
|
754 # No sockets... |
|
755 return default |
|
756 try: |
|
757 return socket.gethostname() |
|
758 except socket.error: |
|
759 # Still not working... |
|
760 return default |
|
761 |
|
762 # os.path.abspath is new in Python 1.5.2: |
|
763 if not hasattr(os.path,'abspath'): |
|
764 |
|
765 def _abspath(path, |
|
766 |
|
767 isabs=os.path.isabs,join=os.path.join,getcwd=os.getcwd, |
|
768 normpath=os.path.normpath): |
|
769 |
|
770 if not isabs(path): |
|
771 path = join(getcwd(), path) |
|
772 return normpath(path) |
|
773 |
|
774 else: |
|
775 |
|
776 _abspath = os.path.abspath |
|
777 |
|
778 def _follow_symlinks(filepath): |
|
779 |
|
780 """ In case filepath is a symlink, follow it until a |
|
781 real file is reached. |
|
782 """ |
|
783 filepath = _abspath(filepath) |
|
784 while os.path.islink(filepath): |
|
785 filepath = os.path.normpath( |
|
786 os.path.join(filepath,os.readlink(filepath))) |
|
787 return filepath |
|
788 |
|
789 def _syscmd_uname(option,default=''): |
|
790 |
|
791 """ Interface to the system's uname command. |
|
792 """ |
|
793 if sys.platform in ('dos','win32','win16','os2'): |
|
794 # XXX Others too ? |
|
795 return default |
|
796 try: |
|
797 f = os.popen('uname %s 2> /dev/null' % option) |
|
798 except (AttributeError,os.error): |
|
799 return default |
|
800 output = string.strip(f.read()) |
|
801 rc = f.close() |
|
802 if not output or rc: |
|
803 return default |
|
804 else: |
|
805 return output |
|
806 |
|
807 def _syscmd_file(target,default=''): |
|
808 |
|
809 """ Interface to the system's file command. |
|
810 |
|
811 The function uses the -b option of the file command to have it |
|
812 ommit the filename in its output and if possible the -L option |
|
813 to have the command follow symlinks. It returns default in |
|
814 case the command should fail. |
|
815 |
|
816 """ |
|
817 target = _follow_symlinks(target) |
|
818 try: |
|
819 f = os.popen('file %s 2> /dev/null' % target) |
|
820 except (AttributeError,os.error): |
|
821 return default |
|
822 output = string.strip(f.read()) |
|
823 rc = f.close() |
|
824 if not output or rc: |
|
825 return default |
|
826 else: |
|
827 return output |
|
828 |
|
829 ### Information about the used architecture |
|
830 |
|
831 # Default values for architecture; non-empty strings override the |
|
832 # defaults given as parameters |
|
833 _default_architecture = { |
|
834 'win32': ('','WindowsPE'), |
|
835 'win16': ('','Windows'), |
|
836 'dos': ('','MSDOS'), |
|
837 } |
|
838 |
|
839 _architecture_split = re.compile(r'[\s,]').split |
|
840 |
|
841 def architecture(executable=sys.executable,bits='',linkage=''): |
|
842 |
|
843 """ Queries the given executable (defaults to the Python interpreter |
|
844 binary) for various architecture information. |
|
845 |
|
846 Returns a tuple (bits,linkage) which contains information about |
|
847 the bit architecture and the linkage format used for the |
|
848 executable. Both values are returned as strings. |
|
849 |
|
850 Values that cannot be determined are returned as given by the |
|
851 parameter presets. If bits is given as '', the sizeof(pointer) |
|
852 (or sizeof(long) on Python version < 1.5.2) is used as |
|
853 indicator for the supported pointer size. |
|
854 |
|
855 The function relies on the system's "file" command to do the |
|
856 actual work. This is available on most if not all Unix |
|
857 platforms. On some non-Unix platforms where the "file" command |
|
858 does not exist and the executable is set to the Python interpreter |
|
859 binary defaults from _default_architecture are used. |
|
860 |
|
861 """ |
|
862 # Use the sizeof(pointer) as default number of bits if nothing |
|
863 # else is given as default. |
|
864 if not bits: |
|
865 import struct |
|
866 try: |
|
867 size = struct.calcsize('P') |
|
868 except struct.error: |
|
869 # Older installations can only query longs |
|
870 size = struct.calcsize('l') |
|
871 bits = str(size*8) + 'bit' |
|
872 |
|
873 # Get data from the 'file' system command |
|
874 output = _syscmd_file(executable,'') |
|
875 |
|
876 if not output and \ |
|
877 executable == sys.executable: |
|
878 # "file" command did not return anything; we'll try to provide |
|
879 # some sensible defaults then... |
|
880 if _default_architecture.has_key(sys.platform): |
|
881 b,l = _default_architecture[sys.platform] |
|
882 if b: |
|
883 bits = b |
|
884 if l: |
|
885 linkage = l |
|
886 return bits,linkage |
|
887 |
|
888 # Split the output into a list of strings omitting the filename |
|
889 fileout = _architecture_split(output)[1:] |
|
890 |
|
891 if 'executable' not in fileout: |
|
892 # Format not supported |
|
893 return bits,linkage |
|
894 |
|
895 # Bits |
|
896 if '32-bit' in fileout: |
|
897 bits = '32bit' |
|
898 elif 'N32' in fileout: |
|
899 # On Irix only |
|
900 bits = 'n32bit' |
|
901 elif '64-bit' in fileout: |
|
902 bits = '64bit' |
|
903 |
|
904 # Linkage |
|
905 if 'ELF' in fileout: |
|
906 linkage = 'ELF' |
|
907 elif 'PE' in fileout: |
|
908 # E.g. Windows uses this format |
|
909 if 'Windows' in fileout: |
|
910 linkage = 'WindowsPE' |
|
911 else: |
|
912 linkage = 'PE' |
|
913 elif 'COFF' in fileout: |
|
914 linkage = 'COFF' |
|
915 elif 'MS-DOS' in fileout: |
|
916 linkage = 'MSDOS' |
|
917 else: |
|
918 # XXX the A.OUT format also falls under this class... |
|
919 pass |
|
920 |
|
921 return bits,linkage |
|
922 |
|
923 ### Portable uname() interface |
|
924 |
|
925 _uname_cache = None |
|
926 |
|
927 def uname(): |
|
928 |
|
929 """ Fairly portable uname interface. Returns a tuple |
|
930 of strings (system,node,release,version,machine,processor) |
|
931 identifying the underlying platform. |
|
932 |
|
933 Note that unlike the os.uname function this also returns |
|
934 possible processor information as an additional tuple entry. |
|
935 |
|
936 Entries which cannot be determined are set to ''. |
|
937 |
|
938 """ |
|
939 global _uname_cache |
|
940 |
|
941 if _uname_cache is not None: |
|
942 return _uname_cache |
|
943 |
|
944 # Get some infos from the builtin os.uname API... |
|
945 try: |
|
946 system,node,release,version,machine = os.uname() |
|
947 |
|
948 except AttributeError: |
|
949 # Hmm, no uname... we'll have to poke around the system then. |
|
950 system = sys.platform |
|
951 release = '' |
|
952 version = '' |
|
953 node = _node() |
|
954 machine = '' |
|
955 processor = '' |
|
956 use_syscmd_ver = 1 |
|
957 |
|
958 # Try win32_ver() on win32 platforms |
|
959 if system == 'win32': |
|
960 release,version,csd,ptype = win32_ver() |
|
961 if release and version: |
|
962 use_syscmd_ver = 0 |
|
963 |
|
964 # Try the 'ver' system command available on some |
|
965 # platforms |
|
966 if use_syscmd_ver: |
|
967 system,release,version = _syscmd_ver(system) |
|
968 # Normalize system to what win32_ver() normally returns |
|
969 # (_syscmd_ver() tends to return the vendor name as well) |
|
970 if system == 'Microsoft Windows': |
|
971 system = 'Windows' |
|
972 |
|
973 # In case we still don't know anything useful, we'll try to |
|
974 # help ourselves |
|
975 if system in ('win32','win16'): |
|
976 if not version: |
|
977 if system == 'win32': |
|
978 version = '32bit' |
|
979 else: |
|
980 version = '16bit' |
|
981 system = 'Windows' |
|
982 |
|
983 elif system[:4] == 'java': |
|
984 release,vendor,vminfo,osinfo = java_ver() |
|
985 system = 'Java' |
|
986 version = string.join(vminfo,', ') |
|
987 if not version: |
|
988 version = vendor |
|
989 |
|
990 elif os.name == 'mac': |
|
991 release,(version,stage,nonrel),machine = mac_ver() |
|
992 system = 'MacOS' |
|
993 |
|
994 else: |
|
995 # System specific extensions |
|
996 if system == 'OpenVMS': |
|
997 # OpenVMS seems to have release and version mixed up |
|
998 if not release or release == '0': |
|
999 release = version |
|
1000 version = '' |
|
1001 # Get processor information |
|
1002 try: |
|
1003 import vms_lib |
|
1004 except ImportError: |
|
1005 pass |
|
1006 else: |
|
1007 csid, cpu_number = vms_lib.getsyi('SYI$_CPU',0) |
|
1008 if (cpu_number >= 128): |
|
1009 processor = 'Alpha' |
|
1010 else: |
|
1011 processor = 'VAX' |
|
1012 else: |
|
1013 # Get processor information from the uname system command |
|
1014 processor = _syscmd_uname('-p','') |
|
1015 |
|
1016 # 'unknown' is not really any useful as information; we'll convert |
|
1017 # it to '' which is more portable |
|
1018 if system == 'unknown': |
|
1019 system = '' |
|
1020 if node == 'unknown': |
|
1021 node = '' |
|
1022 if release == 'unknown': |
|
1023 release = '' |
|
1024 if version == 'unknown': |
|
1025 version = '' |
|
1026 if machine == 'unknown': |
|
1027 machine = '' |
|
1028 if processor == 'unknown': |
|
1029 processor = '' |
|
1030 |
|
1031 # normalize name |
|
1032 if system == 'Microsoft' and release == 'Windows': |
|
1033 system = 'Windows' |
|
1034 release = 'Vista' |
|
1035 |
|
1036 _uname_cache = system,node,release,version,machine,processor |
|
1037 return _uname_cache |
|
1038 |
|
1039 ### Direct interfaces to some of the uname() return values |
|
1040 |
|
1041 def system(): |
|
1042 |
|
1043 """ Returns the system/OS name, e.g. 'Linux', 'Windows' or 'Java'. |
|
1044 |
|
1045 An empty string is returned if the value cannot be determined. |
|
1046 |
|
1047 """ |
|
1048 return uname()[0] |
|
1049 |
|
1050 def node(): |
|
1051 |
|
1052 """ Returns the computer's network name (which may not be fully |
|
1053 qualified) |
|
1054 |
|
1055 An empty string is returned if the value cannot be determined. |
|
1056 |
|
1057 """ |
|
1058 return uname()[1] |
|
1059 |
|
1060 def release(): |
|
1061 |
|
1062 """ Returns the system's release, e.g. '2.2.0' or 'NT' |
|
1063 |
|
1064 An empty string is returned if the value cannot be determined. |
|
1065 |
|
1066 """ |
|
1067 return uname()[2] |
|
1068 |
|
1069 def version(): |
|
1070 |
|
1071 """ Returns the system's release version, e.g. '#3 on degas' |
|
1072 |
|
1073 An empty string is returned if the value cannot be determined. |
|
1074 |
|
1075 """ |
|
1076 return uname()[3] |
|
1077 |
|
1078 def machine(): |
|
1079 |
|
1080 """ Returns the machine type, e.g. 'i386' |
|
1081 |
|
1082 An empty string is returned if the value cannot be determined. |
|
1083 |
|
1084 """ |
|
1085 return uname()[4] |
|
1086 |
|
1087 def processor(): |
|
1088 |
|
1089 """ Returns the (true) processor name, e.g. 'amdk6' |
|
1090 |
|
1091 An empty string is returned if the value cannot be |
|
1092 determined. Note that many platforms do not provide this |
|
1093 information or simply return the same value as for machine(), |
|
1094 e.g. NetBSD does this. |
|
1095 |
|
1096 """ |
|
1097 return uname()[5] |
|
1098 |
|
1099 ### Various APIs for extracting information from sys.version |
|
1100 |
|
1101 _sys_version_parser = re.compile(r'([\w.+]+)\s*' |
|
1102 '\(#?([^,]+),\s*([\w ]+),\s*([\w :]+)\)\s*' |
|
1103 '\[([^\]]+)\]?') |
|
1104 _sys_version_cache = None |
|
1105 |
|
1106 def _sys_version(): |
|
1107 |
|
1108 """ Returns a parsed version of Python's sys.version as tuple |
|
1109 (version, buildno, builddate, compiler) referring to the Python |
|
1110 version, build number, build date/time as string and the compiler |
|
1111 identification string. |
|
1112 |
|
1113 Note that unlike the Python sys.version, the returned value |
|
1114 for the Python version will always include the patchlevel (it |
|
1115 defaults to '.0'). |
|
1116 |
|
1117 """ |
|
1118 global _sys_version_cache |
|
1119 |
|
1120 if _sys_version_cache is not None: |
|
1121 return _sys_version_cache |
|
1122 version, buildno, builddate, buildtime, compiler = \ |
|
1123 _sys_version_parser.match(sys.version).groups() |
|
1124 builddate = builddate + ' ' + buildtime |
|
1125 l = string.split(version, '.') |
|
1126 if len(l) == 2: |
|
1127 l.append('0') |
|
1128 version = string.join(l, '.') |
|
1129 _sys_version_cache = (version, buildno, builddate, compiler) |
|
1130 return _sys_version_cache |
|
1131 |
|
1132 def python_version(): |
|
1133 |
|
1134 """ Returns the Python version as string 'major.minor.patchlevel' |
|
1135 |
|
1136 Note that unlike the Python sys.version, the returned value |
|
1137 will always include the patchlevel (it defaults to 0). |
|
1138 |
|
1139 """ |
|
1140 return _sys_version()[0] |
|
1141 |
|
1142 def python_version_tuple(): |
|
1143 |
|
1144 """ Returns the Python version as tuple (major, minor, patchlevel) |
|
1145 of strings. |
|
1146 |
|
1147 Note that unlike the Python sys.version, the returned value |
|
1148 will always include the patchlevel (it defaults to 0). |
|
1149 |
|
1150 """ |
|
1151 return string.split(_sys_version()[0], '.') |
|
1152 |
|
1153 def python_build(): |
|
1154 |
|
1155 """ Returns a tuple (buildno, builddate) stating the Python |
|
1156 build number and date as strings. |
|
1157 |
|
1158 """ |
|
1159 return _sys_version()[1:3] |
|
1160 |
|
1161 def python_compiler(): |
|
1162 |
|
1163 """ Returns a string identifying the compiler used for compiling |
|
1164 Python. |
|
1165 |
|
1166 """ |
|
1167 return _sys_version()[3] |
|
1168 |
|
1169 ### The Opus Magnum of platform strings :-) |
|
1170 |
|
1171 _platform_cache = {} |
|
1172 |
|
1173 def platform(aliased=0, terse=0): |
|
1174 |
|
1175 """ Returns a single string identifying the underlying platform |
|
1176 with as much useful information as possible (but no more :). |
|
1177 |
|
1178 The output is intended to be human readable rather than |
|
1179 machine parseable. It may look different on different |
|
1180 platforms and this is intended. |
|
1181 |
|
1182 If "aliased" is true, the function will use aliases for |
|
1183 various platforms that report system names which differ from |
|
1184 their common names, e.g. SunOS will be reported as |
|
1185 Solaris. The system_alias() function is used to implement |
|
1186 this. |
|
1187 |
|
1188 Setting terse to true causes the function to return only the |
|
1189 absolute minimum information needed to identify the platform. |
|
1190 |
|
1191 """ |
|
1192 result = _platform_cache.get((aliased, terse), None) |
|
1193 if result is not None: |
|
1194 return result |
|
1195 |
|
1196 # Get uname information and then apply platform specific cosmetics |
|
1197 # to it... |
|
1198 system,node,release,version,machine,processor = uname() |
|
1199 if machine == processor: |
|
1200 processor = '' |
|
1201 if aliased: |
|
1202 system,release,version = system_alias(system,release,version) |
|
1203 |
|
1204 if system == 'Windows': |
|
1205 # MS platforms |
|
1206 rel,vers,csd,ptype = win32_ver(version) |
|
1207 if terse: |
|
1208 platform = _platform(system,release) |
|
1209 else: |
|
1210 platform = _platform(system,release,version,csd) |
|
1211 |
|
1212 elif system in ('Linux',): |
|
1213 # Linux based systems |
|
1214 distname,distversion,distid = dist('') |
|
1215 if distname and not terse: |
|
1216 platform = _platform(system,release,machine,processor, |
|
1217 'with', |
|
1218 distname,distversion,distid) |
|
1219 else: |
|
1220 # If the distribution name is unknown check for libc vs. glibc |
|
1221 libcname,libcversion = libc_ver(sys.executable) |
|
1222 platform = _platform(system,release,machine,processor, |
|
1223 'with', |
|
1224 libcname+libcversion) |
|
1225 elif system == 'Java': |
|
1226 # Java platforms |
|
1227 r,v,vminfo,(os_name,os_version,os_arch) = java_ver() |
|
1228 if terse: |
|
1229 platform = _platform(system,release,version) |
|
1230 else: |
|
1231 platform = _platform(system,release,version, |
|
1232 'on', |
|
1233 os_name,os_version,os_arch) |
|
1234 |
|
1235 elif system == 'MacOS': |
|
1236 # MacOS platforms |
|
1237 if terse: |
|
1238 platform = _platform(system,release) |
|
1239 else: |
|
1240 platform = _platform(system,release,machine) |
|
1241 |
|
1242 else: |
|
1243 # Generic handler |
|
1244 if terse: |
|
1245 platform = _platform(system,release) |
|
1246 else: |
|
1247 bits,linkage = architecture(sys.executable) |
|
1248 platform = _platform(system,release,machine,processor,bits,linkage) |
|
1249 |
|
1250 _platform_cache[(aliased, terse)] = platform |
|
1251 return platform |
|
1252 |
|
1253 ### Command line interface |
|
1254 |
|
1255 if __name__ == '__main__': |
|
1256 # Default is to print the aliased verbose platform string |
|
1257 terse = ('terse' in sys.argv or '--terse' in sys.argv) |
|
1258 aliased = (not 'nonaliased' in sys.argv and not '--nonaliased' in sys.argv) |
|
1259 print platform(aliased,terse) |
|
1260 sys.exit(0) |