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