• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 bug tracker (http://bugs.python.org) 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, Anthony Baxter
32#
33#    History:
34#
35#    <see CVS and SVN checkin messages for history>
36#
37#    1.0.7 - added DEV_NULL
38#    1.0.6 - added linux_distribution()
39#    1.0.5 - fixed Java support to allow running the module on Jython
40#    1.0.4 - added IronPython support
41#    1.0.3 - added normalization of Windows system name
42#    1.0.2 - added more Windows support
43#    1.0.1 - reformatted to make doc.py happy
44#    1.0.0 - reformatted a bit and checked into Python CVS
45#    0.8.0 - added sys.version parser and various new access
46#            APIs (python_version(), python_compiler(), etc.)
47#    0.7.2 - fixed architecture() to use sizeof(pointer) where available
48#    0.7.1 - added support for Caldera OpenLinux
49#    0.7.0 - some fixes for WinCE; untabified the source file
50#    0.6.2 - support for OpenVMS - requires version 1.5.2-V006 or higher and
51#            vms_lib.getsyi() configured
52#    0.6.1 - added code to prevent 'uname -p' on platforms which are
53#            known not to support it
54#    0.6.0 - fixed win32_ver() to hopefully work on Win95,98,NT and Win2k;
55#            did some cleanup of the interfaces - some APIs have changed
56#    0.5.5 - fixed another type in the MacOS code... should have
57#            used more coffee today ;-)
58#    0.5.4 - fixed a few typos in the MacOS code
59#    0.5.3 - added experimental MacOS support; added better popen()
60#            workarounds in _syscmd_ver() -- still not 100% elegant
61#            though
62#    0.5.2 - fixed uname() to return '' instead of 'unknown' in all
63#            return values (the system uname command tends to return
64#            'unknown' instead of just leaving the field emtpy)
65#    0.5.1 - included code for slackware dist; added exception handlers
66#            to cover up situations where platforms don't have os.popen
67#            (e.g. Mac) or fail on socket.gethostname(); fixed libc
68#            detection RE
69#    0.5.0 - changed the API names referring to system commands to *syscmd*;
70#            added java_ver(); made syscmd_ver() a private
71#            API (was system_ver() in previous versions) -- use uname()
72#            instead; extended the win32_ver() to also return processor
73#            type information
74#    0.4.0 - added win32_ver() and modified the platform() output for WinXX
75#    0.3.4 - fixed a bug in _follow_symlinks()
76#    0.3.3 - fixed popen() and "file" command invokation bugs
77#    0.3.2 - added architecture() API and support for it in platform()
78#    0.3.1 - fixed syscmd_ver() RE to support Windows NT
79#    0.3.0 - added system alias support
80#    0.2.3 - removed 'wince' again... oh well.
81#    0.2.2 - added 'wince' to syscmd_ver() supported platforms
82#    0.2.1 - added cache logic and changed the platform string format
83#    0.2.0 - changed the API to use functions instead of module globals
84#            since some action take too long to be run on module import
85#    0.1.0 - first release
86#
87#    You can always get the latest version of this module at:
88#
89#             http://www.egenix.com/files/python/platform.py
90#
91#    If that URL should fail, try contacting the author.
92
93__copyright__ = """
94    Copyright (c) 1999-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com
95    Copyright (c) 2000-2010, eGenix.com Software GmbH; mailto:info@egenix.com
96
97    Permission to use, copy, modify, and distribute this software and its
98    documentation for any purpose and without fee or royalty is hereby granted,
99    provided that the above copyright notice appear in all copies and that
100    both that copyright notice and this permission notice appear in
101    supporting documentation or portions thereof, including modifications,
102    that you make.
103
104    EGENIX.COM SOFTWARE GMBH DISCLAIMS ALL WARRANTIES WITH REGARD TO
105    THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
106    FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
107    INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
108    FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
109    NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
110    WITH THE USE OR PERFORMANCE OF THIS SOFTWARE !
111
112"""
113
114__version__ = '1.0.7'
115
116import sys,string,os,re
117
118### Globals & Constants
119
120# Determine the platform's /dev/null device
121try:
122    DEV_NULL = os.devnull
123except AttributeError:
124    # os.devnull was added in Python 2.4, so emulate it for earlier
125    # Python versions
126    if sys.platform in ('dos','win32','win16','os2'):
127        # Use the old CP/M NUL as device name
128        DEV_NULL = 'NUL'
129    else:
130        # Standard Unix uses /dev/null
131        DEV_NULL = '/dev/null'
132
133### Platform specific APIs
134
135_libc_search = re.compile(r'(__libc_init)'
136                          '|'
137                          '(GLIBC_([0-9.]+))'
138                          '|'
139                          '(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)')
140
141def libc_ver(executable=sys.executable,lib='',version='',
142
143             chunksize=2048):
144
145    """ Tries to determine the libc version that the file executable
146        (which defaults to the Python interpreter) is linked against.
147
148        Returns a tuple of strings (lib,version) which default to the
149        given parameters in case the lookup fails.
150
151        Note that the function has intimate knowledge of how different
152        libc versions add symbols to the executable and thus is probably
153        only useable for executables compiled using gcc.
154
155        The file is read and scanned in chunks of chunksize bytes.
156
157    """
158    if hasattr(os.path, 'realpath'):
159        # Python 2.2 introduced os.path.realpath(); it is used
160        # here to work around problems with Cygwin not being
161        # able to open symlinks for reading
162        executable = os.path.realpath(executable)
163    f = open(executable,'rb')
164    binary = f.read(chunksize)
165    pos = 0
166    while 1:
167        m = _libc_search.search(binary,pos)
168        if not m:
169            binary = f.read(chunksize)
170            if not binary:
171                break
172            pos = 0
173            continue
174        libcinit,glibc,glibcversion,so,threads,soversion = m.groups()
175        if libcinit and not lib:
176            lib = 'libc'
177        elif glibc:
178            if lib != 'glibc':
179                lib = 'glibc'
180                version = glibcversion
181            elif glibcversion > version:
182                version = glibcversion
183        elif so:
184            if lib != 'glibc':
185                lib = 'libc'
186                if soversion and soversion > version:
187                    version = soversion
188                if threads and version[-len(threads):] != threads:
189                    version = version + threads
190        pos = m.end()
191    f.close()
192    return lib,version
193
194def _dist_try_harder(distname,version,id):
195
196    """ Tries some special tricks to get the distribution
197        information in case the default method fails.
198
199        Currently supports older SuSE Linux, Caldera OpenLinux and
200        Slackware Linux distributions.
201
202    """
203    if os.path.exists('/var/adm/inst-log/info'):
204        # SuSE Linux stores distribution information in that file
205        info = open('/var/adm/inst-log/info').readlines()
206        distname = 'SuSE'
207        for line in info:
208            tv = string.split(line)
209            if len(tv) == 2:
210                tag,value = tv
211            else:
212                continue
213            if tag == 'MIN_DIST_VERSION':
214                version = string.strip(value)
215            elif tag == 'DIST_IDENT':
216                values = string.split(value,'-')
217                id = values[2]
218        return distname,version,id
219
220    if os.path.exists('/etc/.installed'):
221        # Caldera OpenLinux has some infos in that file (thanks to Colin Kong)
222        info = open('/etc/.installed').readlines()
223        for line in info:
224            pkg = string.split(line,'-')
225            if len(pkg) >= 2 and pkg[0] == 'OpenLinux':
226                # XXX does Caldera support non Intel platforms ? If yes,
227                #     where can we find the needed id ?
228                return 'OpenLinux',pkg[1],id
229
230    if os.path.isdir('/usr/lib/setup'):
231        # Check for slackware version tag file (thanks to Greg Andruk)
232        verfiles = os.listdir('/usr/lib/setup')
233        for n in range(len(verfiles)-1, -1, -1):
234            if verfiles[n][:14] != 'slack-version-':
235                del verfiles[n]
236        if verfiles:
237            verfiles.sort()
238            distname = 'slackware'
239            version = verfiles[-1][14:]
240            return distname,version,id
241
242    return distname,version,id
243
244_release_filename = re.compile(r'(\w+)[-_](release|version)')
245_lsb_release_version = re.compile(r'(.+)'
246                                   ' release '
247                                   '([\d.]+)'
248                                   '[^(]*(?:\((.+)\))?')
249_release_version = re.compile(r'([^0-9]+)'
250                               '(?: release )?'
251                               '([\d.]+)'
252                               '[^(]*(?:\((.+)\))?')
253
254# See also http://www.novell.com/coolsolutions/feature/11251.html
255# and http://linuxmafia.com/faq/Admin/release-files.html
256# and http://data.linux-ntfs.org/rpm/whichrpm
257# and http://www.die.net/doc/linux/man/man1/lsb_release.1.html
258
259_supported_dists = (
260    'SuSE', 'debian', 'fedora', 'redhat', 'centos',
261    'mandrake', 'mandriva', 'rocks', 'slackware', 'yellowdog', 'gentoo',
262    'UnitedLinux', 'turbolinux')
263
264def _parse_release_file(firstline):
265
266    # Default to empty 'version' and 'id' strings.  Both defaults are used
267    # when 'firstline' is empty.  'id' defaults to empty when an id can not
268    # be deduced.
269    version = ''
270    id = ''
271
272    # Parse the first line
273    m = _lsb_release_version.match(firstline)
274    if m is not None:
275        # LSB format: "distro release x.x (codename)"
276        return tuple(m.groups())
277
278    # Pre-LSB format: "distro x.x (codename)"
279    m = _release_version.match(firstline)
280    if m is not None:
281        return tuple(m.groups())
282
283    # Unknown format... take the first two words
284    l = string.split(string.strip(firstline))
285    if l:
286        version = l[0]
287        if len(l) > 1:
288            id = l[1]
289    return '', version, id
290
291def linux_distribution(distname='', version='', id='',
292
293                       supported_dists=_supported_dists,
294                       full_distribution_name=1):
295
296    """ Tries to determine the name of the Linux OS distribution name.
297
298        The function first looks for a distribution release file in
299        /etc and then reverts to _dist_try_harder() in case no
300        suitable files are found.
301
302        supported_dists may be given to define the set of Linux
303        distributions to look for. It defaults to a list of currently
304        supported Linux distributions identified by their release file
305        name.
306
307        If full_distribution_name is true (default), the full
308        distribution read from the OS is returned. Otherwise the short
309        name taken from supported_dists is used.
310
311        Returns a tuple (distname,version,id) which default to the
312        args given as parameters.
313
314    """
315    try:
316        etc = os.listdir('/etc')
317    except os.error:
318        # Probably not a Unix system
319        return distname,version,id
320    etc.sort()
321    for file in etc:
322        m = _release_filename.match(file)
323        if m is not None:
324            _distname,dummy = m.groups()
325            if _distname in supported_dists:
326                distname = _distname
327                break
328    else:
329        return _dist_try_harder(distname,version,id)
330
331    # Read the first line
332    f = open('/etc/'+file, 'r')
333    firstline = f.readline()
334    f.close()
335    _distname, _version, _id = _parse_release_file(firstline)
336
337    if _distname and full_distribution_name:
338        distname = _distname
339    if _version:
340        version = _version
341    if _id:
342        id = _id
343    return distname, version, id
344
345# To maintain backwards compatibility:
346
347def dist(distname='',version='',id='',
348
349         supported_dists=_supported_dists):
350
351    """ Tries to determine the name of the Linux OS distribution name.
352
353        The function first looks for a distribution release file in
354        /etc and then reverts to _dist_try_harder() in case no
355        suitable files are found.
356
357        Returns a tuple (distname,version,id) which default to the
358        args given as parameters.
359
360    """
361    return linux_distribution(distname, version, id,
362                              supported_dists=supported_dists,
363                              full_distribution_name=0)
364
365class _popen:
366
367    """ Fairly portable (alternative) popen implementation.
368
369        This is mostly needed in case os.popen() is not available, or
370        doesn't work as advertised, e.g. in Win9X GUI programs like
371        PythonWin or IDLE.
372
373        Writing to the pipe is currently not supported.
374
375    """
376    tmpfile = ''
377    pipe = None
378    bufsize = None
379    mode = 'r'
380
381    def __init__(self,cmd,mode='r',bufsize=None):
382
383        if mode != 'r':
384            raise ValueError,'popen()-emulation only supports read mode'
385        import tempfile
386        self.tmpfile = tmpfile = tempfile.mktemp()
387        os.system(cmd + ' > %s' % tmpfile)
388        self.pipe = open(tmpfile,'rb')
389        self.bufsize = bufsize
390        self.mode = mode
391
392    def read(self):
393
394        return self.pipe.read()
395
396    def readlines(self):
397
398        if self.bufsize is not None:
399            return self.pipe.readlines()
400
401    def close(self,
402
403              remove=os.unlink,error=os.error):
404
405        if self.pipe:
406            rc = self.pipe.close()
407        else:
408            rc = 255
409        if self.tmpfile:
410            try:
411                remove(self.tmpfile)
412            except error:
413                pass
414        return rc
415
416    # Alias
417    __del__ = close
418
419def popen(cmd, mode='r', bufsize=None):
420
421    """ Portable popen() interface.
422    """
423    # Find a working popen implementation preferring win32pipe.popen
424    # over os.popen over _popen
425    popen = None
426    if os.environ.get('OS','') == 'Windows_NT':
427        # On NT win32pipe should work; on Win9x it hangs due to bugs
428        # in the MS C lib (see MS KnowledgeBase article Q150956)
429        try:
430            import win32pipe
431        except ImportError:
432            pass
433        else:
434            popen = win32pipe.popen
435    if popen is None:
436        if hasattr(os,'popen'):
437            popen = os.popen
438            # Check whether it works... it doesn't in GUI programs
439            # on Windows platforms
440            if sys.platform == 'win32': # XXX Others too ?
441                try:
442                    popen('')
443                except os.error:
444                    popen = _popen
445        else:
446            popen = _popen
447    if bufsize is None:
448        return popen(cmd,mode)
449    else:
450        return popen(cmd,mode,bufsize)
451
452def _norm_version(version, build=''):
453
454    """ Normalize the version and build strings and return a single
455        version string using the format major.minor.build (or patchlevel).
456    """
457    l = string.split(version,'.')
458    if build:
459        l.append(build)
460    try:
461        ints = map(int,l)
462    except ValueError:
463        strings = l
464    else:
465        strings = map(str,ints)
466    version = string.join(strings[:3],'.')
467    return version
468
469_ver_output = re.compile(r'(?:([\w ]+) ([\w.]+) '
470                         '.*'
471                         '\[.* ([\d.]+)\])')
472
473# Examples of VER command output:
474#
475#   Windows 2000:  Microsoft Windows 2000 [Version 5.00.2195]
476#   Windows XP:    Microsoft Windows XP [Version 5.1.2600]
477#   Windows Vista: Microsoft Windows [Version 6.0.6002]
478#
479# Note that the "Version" string gets localized on different
480# Windows versions.
481
482def _syscmd_ver(system='', release='', version='',
483
484               supported_platforms=('win32','win16','dos','os2')):
485
486    """ Tries to figure out the OS version used and returns
487        a tuple (system,release,version).
488
489        It uses the "ver" shell command for this which is known
490        to exists on Windows, DOS and OS/2. XXX Others too ?
491
492        In case this fails, the given parameters are used as
493        defaults.
494
495    """
496    if sys.platform not in supported_platforms:
497        return system,release,version
498
499    # Try some common cmd strings
500    for cmd in ('ver','command /c ver','cmd /c ver'):
501        try:
502            pipe = popen(cmd)
503            info = pipe.read()
504            if pipe.close():
505                raise os.error,'command failed'
506            # XXX How can I suppress shell errors from being written
507            #     to stderr ?
508        except os.error,why:
509            #print 'Command %s failed: %s' % (cmd,why)
510            continue
511        except IOError,why:
512            #print 'Command %s failed: %s' % (cmd,why)
513            continue
514        else:
515            break
516    else:
517        return system,release,version
518
519    # Parse the output
520    info = string.strip(info)
521    m = _ver_output.match(info)
522    if m is not None:
523        system,release,version = m.groups()
524        # Strip trailing dots from version and release
525        if release[-1] == '.':
526            release = release[:-1]
527        if version[-1] == '.':
528            version = version[:-1]
529        # Normalize the version and build strings (eliminating additional
530        # zeros)
531        version = _norm_version(version)
532    return system,release,version
533
534def _win32_getvalue(key,name,default=''):
535
536    """ Read a value for name from the registry key.
537
538        In case this fails, default is returned.
539
540    """
541    try:
542        # Use win32api if available
543        from win32api import RegQueryValueEx
544    except ImportError:
545        # On Python 2.0 and later, emulate using _winreg
546        import _winreg
547        RegQueryValueEx = _winreg.QueryValueEx
548    try:
549        return RegQueryValueEx(key,name)
550    except:
551        return default
552
553def win32_ver(release='',version='',csd='',ptype=''):
554
555    """ Get additional version information from the Windows Registry
556        and return a tuple (version,csd,ptype) referring to version
557        number, CSD level (service pack), and OS type (multi/single
558        processor).
559
560        As a hint: ptype returns 'Uniprocessor Free' on single
561        processor NT machines and 'Multiprocessor Free' on multi
562        processor machines. The 'Free' refers to the OS version being
563        free of debugging code. It could also state 'Checked' which
564        means the OS version uses debugging code, i.e. code that
565        checks arguments, ranges, etc. (Thomas Heller).
566
567        Note: this function works best with Mark Hammond's win32
568        package installed, but also on Python 2.3 and later. It
569        obviously only runs on Win32 compatible platforms.
570
571    """
572    # XXX Is there any way to find out the processor type on WinXX ?
573    # XXX Is win32 available on Windows CE ?
574    #
575    # Adapted from code posted by Karl Putland to comp.lang.python.
576    #
577    # The mappings between reg. values and release names can be found
578    # here: http://msdn.microsoft.com/library/en-us/sysinfo/base/osversioninfo_str.asp
579
580    # Import the needed APIs
581    try:
582        import win32api
583        from win32api import RegQueryValueEx, RegOpenKeyEx, \
584             RegCloseKey, GetVersionEx
585        from win32con import HKEY_LOCAL_MACHINE, VER_PLATFORM_WIN32_NT, \
586             VER_PLATFORM_WIN32_WINDOWS, VER_NT_WORKSTATION
587    except ImportError:
588        # Emulate the win32api module using Python APIs
589        try:
590            sys.getwindowsversion
591        except AttributeError:
592            # No emulation possible, so return the defaults...
593            return release,version,csd,ptype
594        else:
595            # Emulation using _winreg (added in Python 2.0) and
596            # sys.getwindowsversion() (added in Python 2.3)
597            import _winreg
598            GetVersionEx = sys.getwindowsversion
599            RegQueryValueEx = _winreg.QueryValueEx
600            RegOpenKeyEx = _winreg.OpenKeyEx
601            RegCloseKey = _winreg.CloseKey
602            HKEY_LOCAL_MACHINE = _winreg.HKEY_LOCAL_MACHINE
603            VER_PLATFORM_WIN32_WINDOWS = 1
604            VER_PLATFORM_WIN32_NT = 2
605            VER_NT_WORKSTATION = 1
606            VER_NT_SERVER = 3
607            REG_SZ = 1
608
609    # Find out the registry key and some general version infos
610    winver = GetVersionEx()
611    maj,min,buildno,plat,csd = winver
612    version = '%i.%i.%i' % (maj,min,buildno & 0xFFFF)
613    if hasattr(winver, "service_pack"):
614        if winver.service_pack != "":
615            csd = 'SP%s' % winver.service_pack_major
616    else:
617        if csd[:13] == 'Service Pack ':
618            csd = 'SP' + csd[13:]
619
620    if plat == VER_PLATFORM_WIN32_WINDOWS:
621        regkey = 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion'
622        # Try to guess the release name
623        if maj == 4:
624            if min == 0:
625                release = '95'
626            elif min == 10:
627                release = '98'
628            elif min == 90:
629                release = 'Me'
630            else:
631                release = 'postMe'
632        elif maj == 5:
633            release = '2000'
634
635    elif plat == VER_PLATFORM_WIN32_NT:
636        regkey = 'SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion'
637        if maj <= 4:
638            release = 'NT'
639        elif maj == 5:
640            if min == 0:
641                release = '2000'
642            elif min == 1:
643                release = 'XP'
644            elif min == 2:
645                release = '2003Server'
646            else:
647                release = 'post2003'
648        elif maj == 6:
649            if hasattr(winver, "product_type"):
650                product_type = winver.product_type
651            else:
652                product_type = VER_NT_WORKSTATION
653                # Without an OSVERSIONINFOEX capable sys.getwindowsversion(),
654                # or help from the registry, we cannot properly identify
655                # non-workstation versions.
656                try:
657                    key = RegOpenKeyEx(HKEY_LOCAL_MACHINE, regkey)
658                    name, type = RegQueryValueEx(key, "ProductName")
659                    # Discard any type that isn't REG_SZ
660                    if type == REG_SZ and name.find("Server") != -1:
661                        product_type = VER_NT_SERVER
662                except WindowsError:
663                    # Use default of VER_NT_WORKSTATION
664                    pass
665
666            if min == 0:
667                if product_type == VER_NT_WORKSTATION:
668                    release = 'Vista'
669                else:
670                    release = '2008Server'
671            elif min == 1:
672                if product_type == VER_NT_WORKSTATION:
673                    release = '7'
674                else:
675                    release = '2008ServerR2'
676            elif min == 2:
677                if product_type == VER_NT_WORKSTATION:
678                    release = '8'
679                else:
680                    release = '2012Server'
681            else:
682                release = 'post2012Server'
683
684    else:
685        if not release:
686            # E.g. Win3.1 with win32s
687            release = '%i.%i' % (maj,min)
688        return release,version,csd,ptype
689
690    # Open the registry key
691    try:
692        keyCurVer = RegOpenKeyEx(HKEY_LOCAL_MACHINE, regkey)
693        # Get a value to make sure the key exists...
694        RegQueryValueEx(keyCurVer, 'SystemRoot')
695    except:
696        return release,version,csd,ptype
697
698    # Parse values
699    #subversion = _win32_getvalue(keyCurVer,
700    #                            'SubVersionNumber',
701    #                            ('',1))[0]
702    #if subversion:
703    #   release = release + subversion # 95a, 95b, etc.
704    build = _win32_getvalue(keyCurVer,
705                            'CurrentBuildNumber',
706                            ('',1))[0]
707    ptype = _win32_getvalue(keyCurVer,
708                           'CurrentType',
709                           (ptype,1))[0]
710
711    # Normalize version
712    version = _norm_version(version,build)
713
714    # Close key
715    RegCloseKey(keyCurVer)
716    return release,version,csd,ptype
717
718def _mac_ver_lookup(selectors,default=None):
719
720    from gestalt import gestalt
721    import MacOS
722    l = []
723    append = l.append
724    for selector in selectors:
725        try:
726            append(gestalt(selector))
727        except (RuntimeError, MacOS.Error):
728            append(default)
729    return l
730
731def _bcd2str(bcd):
732
733    return hex(bcd)[2:]
734
735def _mac_ver_gestalt():
736    """
737        Thanks to Mark R. Levinson for mailing documentation links and
738        code examples for this function. Documentation for the
739        gestalt() API is available online at:
740
741           http://www.rgaros.nl/gestalt/
742    """
743    # Check whether the version info module is available
744    try:
745        import gestalt
746        import MacOS
747    except ImportError:
748        return None
749    # Get the infos
750    sysv,sysa = _mac_ver_lookup(('sysv','sysa'))
751    # Decode the infos
752    if sysv:
753        major = (sysv & 0xFF00) >> 8
754        minor = (sysv & 0x00F0) >> 4
755        patch = (sysv & 0x000F)
756
757        if (major, minor) >= (10, 4):
758            # the 'sysv' gestald cannot return patchlevels
759            # higher than 9. Apple introduced 3 new
760            # gestalt codes in 10.4 to deal with this
761            # issue (needed because patch levels can
762            # run higher than 9, such as 10.4.11)
763            major,minor,patch = _mac_ver_lookup(('sys1','sys2','sys3'))
764            release = '%i.%i.%i' %(major, minor, patch)
765        else:
766            release = '%s.%i.%i' % (_bcd2str(major),minor,patch)
767
768    if sysa:
769        machine = {0x1: '68k',
770                   0x2: 'PowerPC',
771                   0xa: 'i386'}.get(sysa,'')
772
773    versioninfo=('', '', '')
774    return release,versioninfo,machine
775
776def _mac_ver_xml():
777    fn = '/System/Library/CoreServices/SystemVersion.plist'
778    if not os.path.exists(fn):
779        return None
780
781    try:
782        import plistlib
783    except ImportError:
784        return None
785
786    pl = plistlib.readPlist(fn)
787    release = pl['ProductVersion']
788    versioninfo=('', '', '')
789    machine = os.uname()[4]
790    if machine in ('ppc', 'Power Macintosh'):
791        # for compatibility with the gestalt based code
792        machine = 'PowerPC'
793
794    return release,versioninfo,machine
795
796
797def mac_ver(release='',versioninfo=('','',''),machine=''):
798
799    """ Get MacOS version information and return it as tuple (release,
800        versioninfo, machine) with versioninfo being a tuple (version,
801        dev_stage, non_release_version).
802
803        Entries which cannot be determined are set to the parameter values
804        which default to ''. All tuple entries are strings.
805    """
806
807    # First try reading the information from an XML file which should
808    # always be present
809    info = _mac_ver_xml()
810    if info is not None:
811        return info
812
813    # If that doesn't work for some reason fall back to reading the
814    # information using gestalt calls.
815    info = _mac_ver_gestalt()
816    if info is not None:
817        return info
818
819    # If that also doesn't work return the default values
820    return release,versioninfo,machine
821
822def _java_getprop(name,default):
823
824    from java.lang import System
825    try:
826        value = System.getProperty(name)
827        if value is None:
828            return default
829        return value
830    except AttributeError:
831        return default
832
833def java_ver(release='',vendor='',vminfo=('','',''),osinfo=('','','')):
834
835    """ Version interface for Jython.
836
837        Returns a tuple (release,vendor,vminfo,osinfo) with vminfo being
838        a tuple (vm_name,vm_release,vm_vendor) and osinfo being a
839        tuple (os_name,os_version,os_arch).
840
841        Values which cannot be determined are set to the defaults
842        given as parameters (which all default to '').
843
844    """
845    # Import the needed APIs
846    try:
847        import java.lang
848    except ImportError:
849        return release,vendor,vminfo,osinfo
850
851    vendor = _java_getprop('java.vendor', vendor)
852    release = _java_getprop('java.version', release)
853    vm_name, vm_release, vm_vendor = vminfo
854    vm_name = _java_getprop('java.vm.name', vm_name)
855    vm_vendor = _java_getprop('java.vm.vendor', vm_vendor)
856    vm_release = _java_getprop('java.vm.version', vm_release)
857    vminfo = vm_name, vm_release, vm_vendor
858    os_name, os_version, os_arch = osinfo
859    os_arch = _java_getprop('java.os.arch', os_arch)
860    os_name = _java_getprop('java.os.name', os_name)
861    os_version = _java_getprop('java.os.version', os_version)
862    osinfo = os_name, os_version, os_arch
863
864    return release, vendor, vminfo, osinfo
865
866### System name aliasing
867
868def system_alias(system,release,version):
869
870    """ Returns (system,release,version) aliased to common
871        marketing names used for some systems.
872
873        It also does some reordering of the information in some cases
874        where it would otherwise cause confusion.
875
876    """
877    if system == 'Rhapsody':
878        # Apple's BSD derivative
879        # XXX How can we determine the marketing release number ?
880        return 'MacOS X Server',system+release,version
881
882    elif system == 'SunOS':
883        # Sun's OS
884        if release < '5':
885            # These releases use the old name SunOS
886            return system,release,version
887        # Modify release (marketing release = SunOS release - 3)
888        l = string.split(release,'.')
889        if l:
890            try:
891                major = int(l[0])
892            except ValueError:
893                pass
894            else:
895                major = major - 3
896                l[0] = str(major)
897                release = string.join(l,'.')
898        if release < '6':
899            system = 'Solaris'
900        else:
901            # XXX Whatever the new SunOS marketing name is...
902            system = 'Solaris'
903
904    elif system == 'IRIX64':
905        # IRIX reports IRIX64 on platforms with 64-bit support; yet it
906        # is really a version and not a different platform, since 32-bit
907        # apps are also supported..
908        system = 'IRIX'
909        if version:
910            version = version + ' (64bit)'
911        else:
912            version = '64bit'
913
914    elif system in ('win32','win16'):
915        # In case one of the other tricks
916        system = 'Windows'
917
918    return system,release,version
919
920### Various internal helpers
921
922def _platform(*args):
923
924    """ Helper to format the platform string in a filename
925        compatible format e.g. "system-version-machine".
926    """
927    # Format the platform string
928    platform = string.join(
929        map(string.strip,
930            filter(len, args)),
931        '-')
932
933    # Cleanup some possible filename obstacles...
934    replace = string.replace
935    platform = replace(platform,' ','_')
936    platform = replace(platform,'/','-')
937    platform = replace(platform,'\\','-')
938    platform = replace(platform,':','-')
939    platform = replace(platform,';','-')
940    platform = replace(platform,'"','-')
941    platform = replace(platform,'(','-')
942    platform = replace(platform,')','-')
943
944    # No need to report 'unknown' information...
945    platform = replace(platform,'unknown','')
946
947    # Fold '--'s and remove trailing '-'
948    while 1:
949        cleaned = replace(platform,'--','-')
950        if cleaned == platform:
951            break
952        platform = cleaned
953    while platform[-1] == '-':
954        platform = platform[:-1]
955
956    return platform
957
958def _node(default=''):
959
960    """ Helper to determine the node name of this machine.
961    """
962    try:
963        import socket
964    except ImportError:
965        # No sockets...
966        return default
967    try:
968        return socket.gethostname()
969    except socket.error:
970        # Still not working...
971        return default
972
973# os.path.abspath is new in Python 1.5.2:
974if not hasattr(os.path,'abspath'):
975
976    def _abspath(path,
977
978                 isabs=os.path.isabs,join=os.path.join,getcwd=os.getcwd,
979                 normpath=os.path.normpath):
980
981        if not isabs(path):
982            path = join(getcwd(), path)
983        return normpath(path)
984
985else:
986
987    _abspath = os.path.abspath
988
989def _follow_symlinks(filepath):
990
991    """ In case filepath is a symlink, follow it until a
992        real file is reached.
993    """
994    filepath = _abspath(filepath)
995    while os.path.islink(filepath):
996        filepath = os.path.normpath(
997            os.path.join(os.path.dirname(filepath),os.readlink(filepath)))
998    return filepath
999
1000def _syscmd_uname(option,default=''):
1001
1002    """ Interface to the system's uname command.
1003    """
1004    if sys.platform in ('dos','win32','win16','os2'):
1005        # XXX Others too ?
1006        return default
1007    try:
1008        f = os.popen('uname %s 2> %s' % (option, DEV_NULL))
1009    except (AttributeError,os.error):
1010        return default
1011    output = string.strip(f.read())
1012    rc = f.close()
1013    if not output or rc:
1014        return default
1015    else:
1016        return output
1017
1018def _syscmd_file(target,default=''):
1019
1020    """ Interface to the system's file command.
1021
1022        The function uses the -b option of the file command to have it
1023        ommit the filename in its output and if possible the -L option
1024        to have the command follow symlinks. It returns default in
1025        case the command should fail.
1026
1027    """
1028
1029    # We do the import here to avoid a bootstrap issue.
1030    # See c73b90b6dadd changeset.
1031    #
1032    # [..]
1033    # ranlib libpython2.7.a
1034    # gcc   -o python \
1035    #        Modules/python.o \
1036    #        libpython2.7.a -lsocket -lnsl -ldl    -lm
1037    # Traceback (most recent call last):
1038    #  File "./setup.py", line 8, in <module>
1039    #    from platform import machine as platform_machine
1040    #  File "[..]/build/Lib/platform.py", line 116, in <module>
1041    #    import sys,string,os,re,subprocess
1042    #  File "[..]/build/Lib/subprocess.py", line 429, in <module>
1043    #    import select
1044    # ImportError: No module named select
1045
1046    import subprocess
1047
1048    if sys.platform in ('dos','win32','win16','os2'):
1049        # XXX Others too ?
1050        return default
1051    target = _follow_symlinks(target)
1052    try:
1053        proc = subprocess.Popen(['file', target],
1054                stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
1055
1056    except (AttributeError,os.error):
1057        return default
1058    output = proc.communicate()[0]
1059    rc = proc.wait()
1060    if not output or rc:
1061        return default
1062    else:
1063        return output
1064
1065### Information about the used architecture
1066
1067# Default values for architecture; non-empty strings override the
1068# defaults given as parameters
1069_default_architecture = {
1070    'win32': ('','WindowsPE'),
1071    'win16': ('','Windows'),
1072    'dos': ('','MSDOS'),
1073}
1074
1075_architecture_split = re.compile(r'[\s,]').split
1076
1077def architecture(executable=sys.executable,bits='',linkage=''):
1078
1079    """ Queries the given executable (defaults to the Python interpreter
1080        binary) for various architecture information.
1081
1082        Returns a tuple (bits,linkage) which contains information about
1083        the bit architecture and the linkage format used for the
1084        executable. Both values are returned as strings.
1085
1086        Values that cannot be determined are returned as given by the
1087        parameter presets. If bits is given as '', the sizeof(pointer)
1088        (or sizeof(long) on Python version < 1.5.2) is used as
1089        indicator for the supported pointer size.
1090
1091        The function relies on the system's "file" command to do the
1092        actual work. This is available on most if not all Unix
1093        platforms. On some non-Unix platforms where the "file" command
1094        does not exist and the executable is set to the Python interpreter
1095        binary defaults from _default_architecture are used.
1096
1097    """
1098    # Use the sizeof(pointer) as default number of bits if nothing
1099    # else is given as default.
1100    if not bits:
1101        import struct
1102        try:
1103            size = struct.calcsize('P')
1104        except struct.error:
1105            # Older installations can only query longs
1106            size = struct.calcsize('l')
1107        bits = str(size*8) + 'bit'
1108
1109    # Get data from the 'file' system command
1110    if executable:
1111        output = _syscmd_file(executable, '')
1112    else:
1113        output = ''
1114
1115    if not output and \
1116       executable == sys.executable:
1117        # "file" command did not return anything; we'll try to provide
1118        # some sensible defaults then...
1119        if sys.platform in _default_architecture:
1120            b, l = _default_architecture[sys.platform]
1121            if b:
1122                bits = b
1123            if l:
1124                linkage = l
1125        return bits, linkage
1126
1127    # Split the output into a list of strings omitting the filename
1128    fileout = _architecture_split(output)[1:]
1129
1130    if 'executable' not in fileout:
1131        # Format not supported
1132        return bits,linkage
1133
1134    # Bits
1135    if '32-bit' in fileout:
1136        bits = '32bit'
1137    elif 'N32' in fileout:
1138        # On Irix only
1139        bits = 'n32bit'
1140    elif '64-bit' in fileout:
1141        bits = '64bit'
1142
1143    # Linkage
1144    if 'ELF' in fileout:
1145        linkage = 'ELF'
1146    elif 'PE' in fileout:
1147        # E.g. Windows uses this format
1148        if 'Windows' in fileout:
1149            linkage = 'WindowsPE'
1150        else:
1151            linkage = 'PE'
1152    elif 'COFF' in fileout:
1153        linkage = 'COFF'
1154    elif 'MS-DOS' in fileout:
1155        linkage = 'MSDOS'
1156    else:
1157        # XXX the A.OUT format also falls under this class...
1158        pass
1159
1160    return bits,linkage
1161
1162### Portable uname() interface
1163
1164_uname_cache = None
1165
1166def uname():
1167
1168    """ Fairly portable uname interface. Returns a tuple
1169        of strings (system,node,release,version,machine,processor)
1170        identifying the underlying platform.
1171
1172        Note that unlike the os.uname function this also returns
1173        possible processor information as an additional tuple entry.
1174
1175        Entries which cannot be determined are set to ''.
1176
1177    """
1178    global _uname_cache
1179    no_os_uname = 0
1180
1181    if _uname_cache is not None:
1182        return _uname_cache
1183
1184    processor = ''
1185
1186    # Get some infos from the builtin os.uname API...
1187    try:
1188        system,node,release,version,machine = os.uname()
1189    except AttributeError:
1190        no_os_uname = 1
1191
1192    if no_os_uname or not filter(None, (system, node, release, version, machine)):
1193        # Hmm, no there is either no uname or uname has returned
1194        #'unknowns'... we'll have to poke around the system then.
1195        if no_os_uname:
1196            system = sys.platform
1197            release = ''
1198            version = ''
1199            node = _node()
1200            machine = ''
1201
1202        use_syscmd_ver = 1
1203
1204        # Try win32_ver() on win32 platforms
1205        if system == 'win32':
1206            release,version,csd,ptype = win32_ver()
1207            if release and version:
1208                use_syscmd_ver = 0
1209            # Try to use the PROCESSOR_* environment variables
1210            # available on Win XP and later; see
1211            # http://support.microsoft.com/kb/888731 and
1212            # http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM
1213            if not machine:
1214                # WOW64 processes mask the native architecture
1215                if "PROCESSOR_ARCHITEW6432" in os.environ:
1216                    machine = os.environ.get("PROCESSOR_ARCHITEW6432", '')
1217                else:
1218                    machine = os.environ.get('PROCESSOR_ARCHITECTURE', '')
1219            if not processor:
1220                processor = os.environ.get('PROCESSOR_IDENTIFIER', machine)
1221
1222        # Try the 'ver' system command available on some
1223        # platforms
1224        if use_syscmd_ver:
1225            system,release,version = _syscmd_ver(system)
1226            # Normalize system to what win32_ver() normally returns
1227            # (_syscmd_ver() tends to return the vendor name as well)
1228            if system == 'Microsoft Windows':
1229                system = 'Windows'
1230            elif system == 'Microsoft' and release == 'Windows':
1231                # Under Windows Vista and Windows Server 2008,
1232                # Microsoft changed the output of the ver command. The
1233                # release is no longer printed.  This causes the
1234                # system and release to be misidentified.
1235                system = 'Windows'
1236                if '6.0' == version[:3]:
1237                    release = 'Vista'
1238                else:
1239                    release = ''
1240
1241        # In case we still don't know anything useful, we'll try to
1242        # help ourselves
1243        if system in ('win32','win16'):
1244            if not version:
1245                if system == 'win32':
1246                    version = '32bit'
1247                else:
1248                    version = '16bit'
1249            system = 'Windows'
1250
1251        elif system[:4] == 'java':
1252            release,vendor,vminfo,osinfo = java_ver()
1253            system = 'Java'
1254            version = string.join(vminfo,', ')
1255            if not version:
1256                version = vendor
1257
1258    # System specific extensions
1259    if system == 'OpenVMS':
1260        # OpenVMS seems to have release and version mixed up
1261        if not release or release == '0':
1262            release = version
1263            version = ''
1264        # Get processor information
1265        try:
1266            import vms_lib
1267        except ImportError:
1268            pass
1269        else:
1270            csid, cpu_number = vms_lib.getsyi('SYI$_CPU',0)
1271            if (cpu_number >= 128):
1272                processor = 'Alpha'
1273            else:
1274                processor = 'VAX'
1275    if not processor:
1276        # Get processor information from the uname system command
1277        processor = _syscmd_uname('-p','')
1278
1279    #If any unknowns still exist, replace them with ''s, which are more portable
1280    if system == 'unknown':
1281        system = ''
1282    if node == 'unknown':
1283        node = ''
1284    if release == 'unknown':
1285        release = ''
1286    if version == 'unknown':
1287        version = ''
1288    if machine == 'unknown':
1289        machine = ''
1290    if processor == 'unknown':
1291        processor = ''
1292
1293    #  normalize name
1294    if system == 'Microsoft' and release == 'Windows':
1295        system = 'Windows'
1296        release = 'Vista'
1297
1298    _uname_cache = system,node,release,version,machine,processor
1299    return _uname_cache
1300
1301### Direct interfaces to some of the uname() return values
1302
1303def system():
1304
1305    """ Returns the system/OS name, e.g. 'Linux', 'Windows' or 'Java'.
1306
1307        An empty string is returned if the value cannot be determined.
1308
1309    """
1310    return uname()[0]
1311
1312def node():
1313
1314    """ Returns the computer's network name (which may not be fully
1315        qualified)
1316
1317        An empty string is returned if the value cannot be determined.
1318
1319    """
1320    return uname()[1]
1321
1322def release():
1323
1324    """ Returns the system's release, e.g. '2.2.0' or 'NT'
1325
1326        An empty string is returned if the value cannot be determined.
1327
1328    """
1329    return uname()[2]
1330
1331def version():
1332
1333    """ Returns the system's release version, e.g. '#3 on degas'
1334
1335        An empty string is returned if the value cannot be determined.
1336
1337    """
1338    return uname()[3]
1339
1340def machine():
1341
1342    """ Returns the machine type, e.g. 'i386'
1343
1344        An empty string is returned if the value cannot be determined.
1345
1346    """
1347    return uname()[4]
1348
1349def processor():
1350
1351    """ Returns the (true) processor name, e.g. 'amdk6'
1352
1353        An empty string is returned if the value cannot be
1354        determined. Note that many platforms do not provide this
1355        information or simply return the same value as for machine(),
1356        e.g.  NetBSD does this.
1357
1358    """
1359    return uname()[5]
1360
1361### Various APIs for extracting information from sys.version
1362
1363_sys_version_parser = re.compile(
1364    r'([\w.+]+)\s*'
1365    '\(#?([^,]+),\s*([\w ]+),\s*([\w :]+)\)\s*'
1366    '\[([^\]]+)\]?')
1367
1368_ironpython_sys_version_parser = re.compile(
1369    r'IronPython\s*'
1370    '([\d\.]+)'
1371    '(?: \(([\d\.]+)\))?'
1372    ' on (.NET [\d\.]+)')
1373
1374# IronPython covering 2.6 and 2.7
1375_ironpython26_sys_version_parser = re.compile(
1376    r'([\d.]+)\s*'
1377    '\(IronPython\s*'
1378    '[\d.]+\s*'
1379    '\(([\d.]+)\) on ([\w.]+ [\d.]+(?: \(\d+-bit\))?)\)'
1380)
1381
1382_pypy_sys_version_parser = re.compile(
1383    r'([\w.+]+)\s*'
1384    '\(#?([^,]+),\s*([\w ]+),\s*([\w :]+)\)\s*'
1385    '\[PyPy [^\]]+\]?')
1386
1387_sys_version_cache = {}
1388
1389def _sys_version(sys_version=None):
1390
1391    """ Returns a parsed version of Python's sys.version as tuple
1392        (name, version, branch, revision, buildno, builddate, compiler)
1393        referring to the Python implementation name, version, branch,
1394        revision, build number, build date/time as string and the compiler
1395        identification string.
1396
1397        Note that unlike the Python sys.version, the returned value
1398        for the Python version will always include the patchlevel (it
1399        defaults to '.0').
1400
1401        The function returns empty strings for tuple entries that
1402        cannot be determined.
1403
1404        sys_version may be given to parse an alternative version
1405        string, e.g. if the version was read from a different Python
1406        interpreter.
1407
1408    """
1409    # Get the Python version
1410    if sys_version is None:
1411        sys_version = sys.version
1412
1413    # Try the cache first
1414    result = _sys_version_cache.get(sys_version, None)
1415    if result is not None:
1416        return result
1417
1418    # Parse it
1419    if 'IronPython' in sys_version:
1420        # IronPython
1421        name = 'IronPython'
1422        if sys_version.startswith('IronPython'):
1423            match = _ironpython_sys_version_parser.match(sys_version)
1424        else:
1425            match = _ironpython26_sys_version_parser.match(sys_version)
1426
1427        if match is None:
1428            raise ValueError(
1429                'failed to parse IronPython sys.version: %s' %
1430                repr(sys_version))
1431
1432        version, alt_version, compiler = match.groups()
1433        buildno = ''
1434        builddate = ''
1435
1436    elif sys.platform.startswith('java'):
1437        # Jython
1438        name = 'Jython'
1439        match = _sys_version_parser.match(sys_version)
1440        if match is None:
1441            raise ValueError(
1442                'failed to parse Jython sys.version: %s' %
1443                repr(sys_version))
1444        version, buildno, builddate, buildtime, _ = match.groups()
1445        compiler = sys.platform
1446
1447    elif "PyPy" in sys_version:
1448        # PyPy
1449        name = "PyPy"
1450        match = _pypy_sys_version_parser.match(sys_version)
1451        if match is None:
1452            raise ValueError("failed to parse PyPy sys.version: %s" %
1453                             repr(sys_version))
1454        version, buildno, builddate, buildtime = match.groups()
1455        compiler = ""
1456
1457    else:
1458        # CPython
1459        match = _sys_version_parser.match(sys_version)
1460        if match is None:
1461            raise ValueError(
1462                'failed to parse CPython sys.version: %s' %
1463                repr(sys_version))
1464        version, buildno, builddate, buildtime, compiler = \
1465              match.groups()
1466        name = 'CPython'
1467        builddate = builddate + ' ' + buildtime
1468
1469    if hasattr(sys, 'subversion'):
1470        # sys.subversion was added in Python 2.5
1471        _, branch, revision = sys.subversion
1472    else:
1473        branch = ''
1474        revision = ''
1475
1476    # Add the patchlevel version if missing
1477    l = string.split(version, '.')
1478    if len(l) == 2:
1479        l.append('0')
1480        version = string.join(l, '.')
1481
1482    # Build and cache the result
1483    result = (name, version, branch, revision, buildno, builddate, compiler)
1484    _sys_version_cache[sys_version] = result
1485    return result
1486
1487def python_implementation():
1488
1489    """ Returns a string identifying the Python implementation.
1490
1491        Currently, the following implementations are identified:
1492          'CPython' (C implementation of Python),
1493          'IronPython' (.NET implementation of Python),
1494          'Jython' (Java implementation of Python),
1495          'PyPy' (Python implementation of Python).
1496
1497    """
1498    return _sys_version()[0]
1499
1500def python_version():
1501
1502    """ Returns the Python version as string 'major.minor.patchlevel'
1503
1504        Note that unlike the Python sys.version, the returned value
1505        will always include the patchlevel (it defaults to 0).
1506
1507    """
1508    return _sys_version()[1]
1509
1510def python_version_tuple():
1511
1512    """ Returns the Python version as tuple (major, minor, patchlevel)
1513        of strings.
1514
1515        Note that unlike the Python sys.version, the returned value
1516        will always include the patchlevel (it defaults to 0).
1517
1518    """
1519    return tuple(string.split(_sys_version()[1], '.'))
1520
1521def python_branch():
1522
1523    """ Returns a string identifying the Python implementation
1524        branch.
1525
1526        For CPython this is the Subversion branch from which the
1527        Python binary was built.
1528
1529        If not available, an empty string is returned.
1530
1531    """
1532
1533    return _sys_version()[2]
1534
1535def python_revision():
1536
1537    """ Returns a string identifying the Python implementation
1538        revision.
1539
1540        For CPython this is the Subversion revision from which the
1541        Python binary was built.
1542
1543        If not available, an empty string is returned.
1544
1545    """
1546    return _sys_version()[3]
1547
1548def python_build():
1549
1550    """ Returns a tuple (buildno, builddate) stating the Python
1551        build number and date as strings.
1552
1553    """
1554    return _sys_version()[4:6]
1555
1556def python_compiler():
1557
1558    """ Returns a string identifying the compiler used for compiling
1559        Python.
1560
1561    """
1562    return _sys_version()[6]
1563
1564### The Opus Magnum of platform strings :-)
1565
1566_platform_cache = {}
1567
1568def platform(aliased=0, terse=0):
1569
1570    """ Returns a single string identifying the underlying platform
1571        with as much useful information as possible (but no more :).
1572
1573        The output is intended to be human readable rather than
1574        machine parseable. It may look different on different
1575        platforms and this is intended.
1576
1577        If "aliased" is true, the function will use aliases for
1578        various platforms that report system names which differ from
1579        their common names, e.g. SunOS will be reported as
1580        Solaris. The system_alias() function is used to implement
1581        this.
1582
1583        Setting terse to true causes the function to return only the
1584        absolute minimum information needed to identify the platform.
1585
1586    """
1587    result = _platform_cache.get((aliased, terse), None)
1588    if result is not None:
1589        return result
1590
1591    # Get uname information and then apply platform specific cosmetics
1592    # to it...
1593    system,node,release,version,machine,processor = uname()
1594    if machine == processor:
1595        processor = ''
1596    if aliased:
1597        system,release,version = system_alias(system,release,version)
1598
1599    if system == 'Windows':
1600        # MS platforms
1601        rel,vers,csd,ptype = win32_ver(version)
1602        if terse:
1603            platform = _platform(system,release)
1604        else:
1605            platform = _platform(system,release,version,csd)
1606
1607    elif system in ('Linux',):
1608        # Linux based systems
1609        distname,distversion,distid = dist('')
1610        if distname and not terse:
1611            platform = _platform(system,release,machine,processor,
1612                                 'with',
1613                                 distname,distversion,distid)
1614        else:
1615            # If the distribution name is unknown check for libc vs. glibc
1616            libcname,libcversion = libc_ver(sys.executable)
1617            platform = _platform(system,release,machine,processor,
1618                                 'with',
1619                                 libcname+libcversion)
1620    elif system == 'Java':
1621        # Java platforms
1622        r,v,vminfo,(os_name,os_version,os_arch) = java_ver()
1623        if terse or not os_name:
1624            platform = _platform(system,release,version)
1625        else:
1626            platform = _platform(system,release,version,
1627                                 'on',
1628                                 os_name,os_version,os_arch)
1629
1630    elif system == 'MacOS':
1631        # MacOS platforms
1632        if terse:
1633            platform = _platform(system,release)
1634        else:
1635            platform = _platform(system,release,machine)
1636
1637    else:
1638        # Generic handler
1639        if terse:
1640            platform = _platform(system,release)
1641        else:
1642            bits,linkage = architecture(sys.executable)
1643            platform = _platform(system,release,machine,processor,bits,linkage)
1644
1645    _platform_cache[(aliased, terse)] = platform
1646    return platform
1647
1648### Command line interface
1649
1650if __name__ == '__main__':
1651    # Default is to print the aliased verbose platform string
1652    terse = ('terse' in sys.argv or '--terse' in sys.argv)
1653    aliased = (not 'nonaliased' in sys.argv and not '--nonaliased' in sys.argv)
1654    print platform(aliased,terse)
1655    sys.exit(0)
1656