Home | History | Annotate | Download | only in Lib
      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 (at] 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 (at] lemburg.com
     94     Copyright (c) 2000-2010, eGenix.com Software GmbH; mailto:info (at] 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 
    115 import collections
    116 import sys, os, re, subprocess
    117 
    118 import warnings
    119 
    120 ### Globals & Constants
    121 
    122 # Determine the platform's /dev/null device
    123 try:
    124     DEV_NULL = os.devnull
    125 except 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 
    147 def 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 
    204 def _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 
    272 def _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 
    299 def 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 
    309 def _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 
    363 def 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 
    384 def 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 
    392 def _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 
    422 def _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 
    500 def 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 
    548 def _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 
    570 def 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 
    589 def _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 
    600 def 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 
    635 def 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 
    689 def _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 
    721 def _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 
    736 def _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 
    747 def _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 
    765 def _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 
    801 def 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 
    885 uname_result = collections.namedtuple("uname_result",
    886                     "system node release version machine processor")
    887 
    888 _uname_cache = None
    889 
    890 def 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 
   1028 def 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 
   1037 def 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 
   1047 def 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 
   1056 def 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 
   1065 def 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 
   1074 def 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 
   1116 def _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, '_git'):
   1202         _, branch, revision = sys._git
   1203     elif hasattr(sys, '_mercurial'):
   1204         _, branch, revision = sys._mercurial
   1205     elif hasattr(sys, 'subversion'):
   1206         # sys.subversion was added in Python 2.5
   1207         _, branch, revision = sys.subversion
   1208     else:
   1209         branch = ''
   1210         revision = ''
   1211 
   1212     # Add the patchlevel version if missing
   1213     l = version.split('.')
   1214     if len(l) == 2:
   1215         l.append('0')
   1216         version = '.'.join(l)
   1217 
   1218     # Build and cache the result
   1219     result = (name, version, branch, revision, buildno, builddate, compiler)
   1220     _sys_version_cache[sys_version] = result
   1221     return result
   1222 
   1223 def python_implementation():
   1224 
   1225     """ Returns a string identifying the Python implementation.
   1226 
   1227         Currently, the following implementations are identified:
   1228           'CPython' (C implementation of Python),
   1229           'IronPython' (.NET implementation of Python),
   1230           'Jython' (Java implementation of Python),
   1231           'PyPy' (Python implementation of Python).
   1232 
   1233     """
   1234     return _sys_version()[0]
   1235 
   1236 def python_version():
   1237 
   1238     """ Returns the Python version as string 'major.minor.patchlevel'
   1239 
   1240         Note that unlike the Python sys.version, the returned value
   1241         will always include the patchlevel (it defaults to 0).
   1242 
   1243     """
   1244     return _sys_version()[1]
   1245 
   1246 def python_version_tuple():
   1247 
   1248     """ Returns the Python version as tuple (major, minor, patchlevel)
   1249         of strings.
   1250 
   1251         Note that unlike the Python sys.version, the returned value
   1252         will always include the patchlevel (it defaults to 0).
   1253 
   1254     """
   1255     return tuple(_sys_version()[1].split('.'))
   1256 
   1257 def python_branch():
   1258 
   1259     """ Returns a string identifying the Python implementation
   1260         branch.
   1261 
   1262         For CPython this is the Subversion branch from which the
   1263         Python binary was built.
   1264 
   1265         If not available, an empty string is returned.
   1266 
   1267     """
   1268 
   1269     return _sys_version()[2]
   1270 
   1271 def python_revision():
   1272 
   1273     """ Returns a string identifying the Python implementation
   1274         revision.
   1275 
   1276         For CPython this is the Subversion revision from which the
   1277         Python binary was built.
   1278 
   1279         If not available, an empty string is returned.
   1280 
   1281     """
   1282     return _sys_version()[3]
   1283 
   1284 def python_build():
   1285 
   1286     """ Returns a tuple (buildno, builddate) stating the Python
   1287         build number and date as strings.
   1288 
   1289     """
   1290     return _sys_version()[4:6]
   1291 
   1292 def python_compiler():
   1293 
   1294     """ Returns a string identifying the compiler used for compiling
   1295         Python.
   1296 
   1297     """
   1298     return _sys_version()[6]
   1299 
   1300 ### The Opus Magnum of platform strings :-)
   1301 
   1302 _platform_cache = {}
   1303 
   1304 def platform(aliased=0, terse=0):
   1305 
   1306     """ Returns a single string identifying the underlying platform
   1307         with as much useful information as possible (but no more :).
   1308 
   1309         The output is intended to be human readable rather than
   1310         machine parseable. It may look different on different
   1311         platforms and this is intended.
   1312 
   1313         If "aliased" is true, the function will use aliases for
   1314         various platforms that report system names which differ from
   1315         their common names, e.g. SunOS will be reported as
   1316         Solaris. The system_alias() function is used to implement
   1317         this.
   1318 
   1319         Setting terse to true causes the function to return only the
   1320         absolute minimum information needed to identify the platform.
   1321 
   1322     """
   1323     result = _platform_cache.get((aliased, terse), None)
   1324     if result is not None:
   1325         return result
   1326 
   1327     # Get uname information and then apply platform specific cosmetics
   1328     # to it...
   1329     system, node, release, version, machine, processor = uname()
   1330     if machine == processor:
   1331         processor = ''
   1332     if aliased:
   1333         system, release, version = system_alias(system, release, version)
   1334 
   1335     if system == 'Windows':
   1336         # MS platforms
   1337         rel, vers, csd, ptype = win32_ver(version)
   1338         if terse:
   1339             platform = _platform(system, release)
   1340         else:
   1341             platform = _platform(system, release, version, csd)
   1342 
   1343     elif system in ('Linux',):
   1344         # Linux based systems
   1345         with warnings.catch_warnings():
   1346             # see issue #1322 for more information
   1347             warnings.filterwarnings(
   1348                 'ignore',
   1349                 r'dist\(\) and linux_distribution\(\) '
   1350                 'functions are deprecated .*',
   1351                 PendingDeprecationWarning,
   1352             )
   1353             distname, distversion, distid = dist('')
   1354         if distname and not terse:
   1355             platform = _platform(system, release, machine, processor,
   1356                                  'with',
   1357                                  distname, distversion, distid)
   1358         else:
   1359             # If the distribution name is unknown check for libc vs. glibc
   1360             libcname, libcversion = libc_ver(sys.executable)
   1361             platform = _platform(system, release, machine, processor,
   1362                                  'with',
   1363                                  libcname+libcversion)
   1364     elif system == 'Java':
   1365         # Java platforms
   1366         r, v, vminfo, (os_name, os_version, os_arch) = java_ver()
   1367         if terse or not os_name:
   1368             platform = _platform(system, release, version)
   1369         else:
   1370             platform = _platform(system, release, version,
   1371                                  'on',
   1372                                  os_name, os_version, os_arch)
   1373 
   1374     elif system == 'MacOS':
   1375         # MacOS platforms
   1376         if terse:
   1377             platform = _platform(system, release)
   1378         else:
   1379             platform = _platform(system, release, machine)
   1380 
   1381     else:
   1382         # Generic handler
   1383         if terse:
   1384             platform = _platform(system, release)
   1385         else:
   1386             bits, linkage = architecture(sys.executable)
   1387             platform = _platform(system, release, machine,
   1388                                  processor, bits, linkage)
   1389 
   1390     _platform_cache[(aliased, terse)] = platform
   1391     return platform
   1392 
   1393 ### Command line interface
   1394 
   1395 if __name__ == '__main__':
   1396     # Default is to print the aliased verbose platform string
   1397     terse = ('terse' in sys.argv or '--terse' in sys.argv)
   1398     aliased = (not 'nonaliased' in sys.argv and not '--nonaliased' in sys.argv)
   1399     print(platform(aliased, terse))
   1400     sys.exit(0)
   1401