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