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