]> git.lizzy.rs Git - plan9front.git/blob - sys/lib/python/platform.py
igfx: fix sandybridge fdi link training bits and ordering
[plan9front.git] / sys / lib / python / platform.py
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     _uname_cache = system,node,release,version,machine,processor
1031     return _uname_cache
1032
1033 ### Direct interfaces to some of the uname() return values
1034
1035 def system():
1036
1037     """ Returns the system/OS name, e.g. 'Linux', 'Windows' or 'Java'.
1038
1039         An empty string is returned if the value cannot be determined.
1040
1041     """
1042     return uname()[0]
1043
1044 def node():
1045
1046     """ Returns the computer's network name (which may not be fully
1047         qualified)
1048
1049         An empty string is returned if the value cannot be determined.
1050
1051     """
1052     return uname()[1]
1053
1054 def release():
1055
1056     """ Returns the system's release, e.g. '2.2.0' or 'NT'
1057
1058         An empty string is returned if the value cannot be determined.
1059
1060     """
1061     return uname()[2]
1062
1063 def version():
1064
1065     """ Returns the system's release version, e.g. '#3 on degas'
1066
1067         An empty string is returned if the value cannot be determined.
1068
1069     """
1070     return uname()[3]
1071
1072 def machine():
1073
1074     """ Returns the machine type, e.g. 'i386'
1075
1076         An empty string is returned if the value cannot be determined.
1077
1078     """
1079     return uname()[4]
1080
1081 def processor():
1082
1083     """ Returns the (true) processor name, e.g. 'amdk6'
1084
1085         An empty string is returned if the value cannot be
1086         determined. Note that many platforms do not provide this
1087         information or simply return the same value as for machine(),
1088         e.g.  NetBSD does this.
1089
1090     """
1091     return uname()[5]
1092
1093 ### Various APIs for extracting information from sys.version
1094
1095 _sys_version_parser = re.compile(r'([\w.+]+)\s*'
1096                                   '\(#?([^,]+),\s*([\w ]+),\s*([\w :]+)\)\s*'
1097                                   '\[([^\]]+)\]?')
1098 _sys_version_cache = None
1099
1100 def _sys_version():
1101
1102     """ Returns a parsed version of Python's sys.version as tuple
1103         (version, buildno, builddate, compiler) referring to the Python
1104         version, build number, build date/time as string and the compiler
1105         identification string.
1106
1107         Note that unlike the Python sys.version, the returned value
1108         for the Python version will always include the patchlevel (it
1109         defaults to '.0').
1110
1111     """
1112     global _sys_version_cache
1113
1114     if _sys_version_cache is not None:
1115         return _sys_version_cache
1116     version, buildno, builddate, buildtime, compiler = \
1117              _sys_version_parser.match(sys.version).groups()
1118     builddate = builddate + ' ' + buildtime
1119     l = string.split(version, '.')
1120     if len(l) == 2:
1121         l.append('0')
1122         version = string.join(l, '.')
1123     _sys_version_cache = (version, buildno, builddate, compiler)
1124     return _sys_version_cache
1125
1126 def python_version():
1127
1128     """ Returns the Python version as string 'major.minor.patchlevel'
1129
1130         Note that unlike the Python sys.version, the returned value
1131         will always include the patchlevel (it defaults to 0).
1132
1133     """
1134     return _sys_version()[0]
1135
1136 def python_version_tuple():
1137
1138     """ Returns the Python version as tuple (major, minor, patchlevel)
1139         of strings.
1140
1141         Note that unlike the Python sys.version, the returned value
1142         will always include the patchlevel (it defaults to 0).
1143
1144     """
1145     return string.split(_sys_version()[0], '.')
1146
1147 def python_build():
1148
1149     """ Returns a tuple (buildno, builddate) stating the Python
1150         build number and date as strings.
1151
1152     """
1153     return _sys_version()[1:3]
1154
1155 def python_compiler():
1156
1157     """ Returns a string identifying the compiler used for compiling
1158         Python.
1159
1160     """
1161     return _sys_version()[3]
1162
1163 ### The Opus Magnum of platform strings :-)
1164
1165 _platform_cache = {}
1166
1167 def platform(aliased=0, terse=0):
1168
1169     """ Returns a single string identifying the underlying platform
1170         with as much useful information as possible (but no more :).
1171
1172         The output is intended to be human readable rather than
1173         machine parseable. It may look different on different
1174         platforms and this is intended.
1175
1176         If "aliased" is true, the function will use aliases for
1177         various platforms that report system names which differ from
1178         their common names, e.g. SunOS will be reported as
1179         Solaris. The system_alias() function is used to implement
1180         this.
1181
1182         Setting terse to true causes the function to return only the
1183         absolute minimum information needed to identify the platform.
1184
1185     """
1186     result = _platform_cache.get((aliased, terse), None)
1187     if result is not None:
1188         return result
1189
1190     # Get uname information and then apply platform specific cosmetics
1191     # to it...
1192     system,node,release,version,machine,processor = uname()
1193     if machine == processor:
1194         processor = ''
1195     if aliased:
1196         system,release,version = system_alias(system,release,version)
1197
1198     if system == 'Windows':
1199         # MS platforms
1200         rel,vers,csd,ptype = win32_ver(version)
1201         if terse:
1202             platform = _platform(system,release)
1203         else:
1204             platform = _platform(system,release,version,csd)
1205
1206     elif system in ('Linux',):
1207         # Linux based systems
1208         distname,distversion,distid = dist('')
1209         if distname and not terse:
1210             platform = _platform(system,release,machine,processor,
1211                                  'with',
1212                                  distname,distversion,distid)
1213         else:
1214             # If the distribution name is unknown check for libc vs. glibc
1215             libcname,libcversion = libc_ver(sys.executable)
1216             platform = _platform(system,release,machine,processor,
1217                                  'with',
1218                                  libcname+libcversion)
1219     elif system == 'Java':
1220         # Java platforms
1221         r,v,vminfo,(os_name,os_version,os_arch) = java_ver()
1222         if terse:
1223             platform = _platform(system,release,version)
1224         else:
1225             platform = _platform(system,release,version,
1226                                  'on',
1227                                  os_name,os_version,os_arch)
1228
1229     elif system == 'MacOS':
1230         # MacOS platforms
1231         if terse:
1232             platform = _platform(system,release)
1233         else:
1234             platform = _platform(system,release,machine)
1235
1236     else:
1237         # Generic handler
1238         if terse:
1239             platform = _platform(system,release)
1240         else:
1241             bits,linkage = architecture(sys.executable)
1242             platform = _platform(system,release,machine,processor,bits,linkage)
1243
1244     _platform_cache[(aliased, terse)] = platform
1245     return platform
1246
1247 ### Command line interface
1248
1249 if __name__ == '__main__':
1250     # Default is to print the aliased verbose platform string
1251     terse = ('terse' in sys.argv or '--terse' in sys.argv)
1252     aliased = (not 'nonaliased' in sys.argv and not '--nonaliased' in sys.argv)
1253     print platform(aliased,terse)
1254     sys.exit(0)