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