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