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