(root)/
Python-3.11.7/
Lib/
platform.py
       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 usable as part of a filename.
       9  
      10  """
      11  #    This module is maintained by Marc-Andre Lemburg <mal@egenix.com>.
      12  #    If you find problems, please submit bug reports/patches via the
      13  #    Python bug tracker (http://bugs.python.org) and assign them to "lemburg".
      14  #
      15  #    Still needed:
      16  #    * support for MS-DOS (PythonDX ?)
      17  #    * support for Amiga and other still unsupported platforms running Python
      18  #    * support for additional Linux distributions
      19  #
      20  #    Many thanks to all those who helped adding platform-specific
      21  #    checks (in no particular order):
      22  #
      23  #      Charles G Waldman, David Arnold, Gordon McMillan, Ben Darnell,
      24  #      Jeff Bauer, Cliff Crawford, Ivan Van Laningham, Josef
      25  #      Betancourt, Randall Hopper, Karl Putland, John Farrell, Greg
      26  #      Andruk, Just van Rossum, Thomas Heller, Mark R. Levinson, Mark
      27  #      Hammond, Bill Tutt, Hans Nowak, Uwe Zessin (OpenVMS support),
      28  #      Colin Kong, Trent Mick, Guido van Rossum, Anthony Baxter, Steve
      29  #      Dower
      30  #
      31  #    History:
      32  #
      33  #    <see CVS and SVN checkin messages for history>
      34  #
      35  #    1.0.8 - changed Windows support to read version from kernel32.dll
      36  #    1.0.7 - added DEV_NULL
      37  #    1.0.6 - added linux_distribution()
      38  #    1.0.5 - fixed Java support to allow running the module on Jython
      39  #    1.0.4 - added IronPython support
      40  #    1.0.3 - added normalization of Windows system name
      41  #    1.0.2 - added more Windows support
      42  #    1.0.1 - reformatted to make doc.py happy
      43  #    1.0.0 - reformatted a bit and checked into Python CVS
      44  #    0.8.0 - added sys.version parser and various new access
      45  #            APIs (python_version(), python_compiler(), etc.)
      46  #    0.7.2 - fixed architecture() to use sizeof(pointer) where available
      47  #    0.7.1 - added support for Caldera OpenLinux
      48  #    0.7.0 - some fixes for WinCE; untabified the source file
      49  #    0.6.2 - support for OpenVMS - requires version 1.5.2-V006 or higher and
      50  #            vms_lib.getsyi() configured
      51  #    0.6.1 - added code to prevent 'uname -p' on platforms which are
      52  #            known not to support it
      53  #    0.6.0 - fixed win32_ver() to hopefully work on Win95,98,NT and Win2k;
      54  #            did some cleanup of the interfaces - some APIs have changed
      55  #    0.5.5 - fixed another type in the MacOS code... should have
      56  #            used more coffee today ;-)
      57  #    0.5.4 - fixed a few typos in the MacOS code
      58  #    0.5.3 - added experimental MacOS support; added better popen()
      59  #            workarounds in _syscmd_ver() -- still not 100% elegant
      60  #            though
      61  #    0.5.2 - fixed uname() to return '' instead of 'unknown' in all
      62  #            return values (the system uname command tends to return
      63  #            'unknown' instead of just leaving the field empty)
      64  #    0.5.1 - included code for slackware dist; added exception handlers
      65  #            to cover up situations where platforms don't have os.popen
      66  #            (e.g. Mac) or fail on socket.gethostname(); fixed libc
      67  #            detection RE
      68  #    0.5.0 - changed the API names referring to system commands to *syscmd*;
      69  #            added java_ver(); made syscmd_ver() a private
      70  #            API (was system_ver() in previous versions) -- use uname()
      71  #            instead; extended the win32_ver() to also return processor
      72  #            type information
      73  #    0.4.0 - added win32_ver() and modified the platform() output for WinXX
      74  #    0.3.4 - fixed a bug in _follow_symlinks()
      75  #    0.3.3 - fixed popen() and "file" command invocation bugs
      76  #    0.3.2 - added architecture() API and support for it in platform()
      77  #    0.3.1 - fixed syscmd_ver() RE to support Windows NT
      78  #    0.3.0 - added system alias support
      79  #    0.2.3 - removed 'wince' again... oh well.
      80  #    0.2.2 - added 'wince' to syscmd_ver() supported platforms
      81  #    0.2.1 - added cache logic and changed the platform string format
      82  #    0.2.0 - changed the API to use functions instead of module globals
      83  #            since some action take too long to be run on module import
      84  #    0.1.0 - first release
      85  #
      86  #    You can always get the latest version of this module at:
      87  #
      88  #             http://www.egenix.com/files/python/platform.py
      89  #
      90  #    If that URL should fail, try contacting the author.
      91  
      92  __copyright__ = """
      93      Copyright (c) 1999-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com
      94      Copyright (c) 2000-2010, eGenix.com Software GmbH; mailto:info@egenix.com
      95  
      96      Permission to use, copy, modify, and distribute this software and its
      97      documentation for any purpose and without fee or royalty is hereby granted,
      98      provided that the above copyright notice appear in all copies and that
      99      both that copyright notice and this permission notice appear in
     100      supporting documentation or portions thereof, including modifications,
     101      that you make.
     102  
     103      EGENIX.COM SOFTWARE GMBH DISCLAIMS ALL WARRANTIES WITH REGARD TO
     104      THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
     105      FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
     106      INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
     107      FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
     108      NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
     109      WITH THE USE OR PERFORMANCE OF THIS SOFTWARE !
     110  
     111  """
     112  
     113  __version__ = '1.0.8'
     114  
     115  import collections
     116  import os
     117  import re
     118  import sys
     119  import functools
     120  import itertools
     121  
     122  ### Globals & Constants
     123  
     124  # Helper for comparing two version number strings.
     125  # Based on the description of the PHP's version_compare():
     126  # http://php.net/manual/en/function.version-compare.php
     127  
     128  _ver_stages = {
     129      # any string not found in this dict, will get 0 assigned
     130      'dev': 10,
     131      'alpha': 20, 'a': 20,
     132      'beta': 30, 'b': 30,
     133      'c': 40,
     134      'RC': 50, 'rc': 50,
     135      # number, will get 100 assigned
     136      'pl': 200, 'p': 200,
     137  }
     138  
     139  _component_re = re.compile(r'([0-9]+|[._+-])')
     140  
     141  def _comparable_version(version):
     142      result = []
     143      for v in _component_re.split(version):
     144          if v not in '._+-':
     145              try:
     146                  v = int(v, 10)
     147                  t = 100
     148              except ValueError:
     149                  t = _ver_stages.get(v, 0)
     150              result.extend((t, v))
     151      return result
     152  
     153  ### Platform specific APIs
     154  
     155  _libc_search = re.compile(b'(__libc_init)'
     156                            b'|'
     157                            b'(GLIBC_([0-9.]+))'
     158                            b'|'
     159                            br'(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)', re.ASCII)
     160  
     161  def libc_ver(executable=None, lib='', version='', chunksize=16384):
     162  
     163      """ Tries to determine the libc version that the file executable
     164          (which defaults to the Python interpreter) is linked against.
     165  
     166          Returns a tuple of strings (lib,version) which default to the
     167          given parameters in case the lookup fails.
     168  
     169          Note that the function has intimate knowledge of how different
     170          libc versions add symbols to the executable and thus is probably
     171          only usable for executables compiled using gcc.
     172  
     173          The file is read and scanned in chunks of chunksize bytes.
     174  
     175      """
     176      if not executable:
     177          try:
     178              ver = os.confstr('CS_GNU_LIBC_VERSION')
     179              # parse 'glibc 2.28' as ('glibc', '2.28')
     180              parts = ver.split(maxsplit=1)
     181              if len(parts) == 2:
     182                  return tuple(parts)
     183          except (AttributeError, ValueError, OSError):
     184              # os.confstr() or CS_GNU_LIBC_VERSION value not available
     185              pass
     186  
     187          executable = sys.executable
     188  
     189          if not executable:
     190              # sys.executable is not set.
     191              return lib, version
     192  
     193      V = _comparable_version
     194      # We use os.path.realpath()
     195      # here to work around problems with Cygwin not being
     196      # able to open symlinks for reading
     197      executable = os.path.realpath(executable)
     198      with open(executable, 'rb') as f:
     199          binary = f.read(chunksize)
     200          pos = 0
     201          while pos < len(binary):
     202              if b'libc' in binary or b'GLIBC' in binary:
     203                  m = _libc_search.search(binary, pos)
     204              else:
     205                  m = None
     206              if not m or m.end() == len(binary):
     207                  chunk = f.read(chunksize)
     208                  if chunk:
     209                      binary = binary[max(pos, len(binary) - 1000):] + chunk
     210                      pos = 0
     211                      continue
     212                  if not m:
     213                      break
     214              libcinit, glibc, glibcversion, so, threads, soversion = [
     215                  s.decode('latin1') if s is not None else s
     216                  for s in m.groups()]
     217              if libcinit and not lib:
     218                  lib = 'libc'
     219              elif glibc:
     220                  if lib != 'glibc':
     221                      lib = 'glibc'
     222                      version = glibcversion
     223                  elif V(glibcversion) > V(version):
     224                      version = glibcversion
     225              elif so:
     226                  if lib != 'glibc':
     227                      lib = 'libc'
     228                      if soversion and (not version or V(soversion) > V(version)):
     229                          version = soversion
     230                      if threads and version[-len(threads):] != threads:
     231                          version = version + threads
     232              pos = m.end()
     233      return lib, version
     234  
     235  def _norm_version(version, build=''):
     236  
     237      """ Normalize the version and build strings and return a single
     238          version string using the format major.minor.build (or patchlevel).
     239      """
     240      l = version.split('.')
     241      if build:
     242          l.append(build)
     243      try:
     244          strings = list(map(str, map(int, l)))
     245      except ValueError:
     246          strings = l
     247      version = '.'.join(strings[:3])
     248      return version
     249  
     250  _ver_output = re.compile(r'(?:([\w ]+) ([\w.]+) '
     251                           r'.*'
     252                           r'\[.* ([\d.]+)\])')
     253  
     254  # Examples of VER command output:
     255  #
     256  #   Windows 2000:  Microsoft Windows 2000 [Version 5.00.2195]
     257  #   Windows XP:    Microsoft Windows XP [Version 5.1.2600]
     258  #   Windows Vista: Microsoft Windows [Version 6.0.6002]
     259  #
     260  # Note that the "Version" string gets localized on different
     261  # Windows versions.
     262  
     263  def _syscmd_ver(system='', release='', version='',
     264  
     265                 supported_platforms=('win32', 'win16', 'dos')):
     266  
     267      """ Tries to figure out the OS version used and returns
     268          a tuple (system, release, version).
     269  
     270          It uses the "ver" shell command for this which is known
     271          to exists on Windows, DOS. XXX Others too ?
     272  
     273          In case this fails, the given parameters are used as
     274          defaults.
     275  
     276      """
     277      if sys.platform not in supported_platforms:
     278          return system, release, version
     279  
     280      # Try some common cmd strings
     281      import subprocess
     282      for cmd in ('ver', 'command /c ver', 'cmd /c ver'):
     283          try:
     284              info = subprocess.check_output(cmd,
     285                                             stdin=subprocess.DEVNULL,
     286                                             stderr=subprocess.DEVNULL,
     287                                             text=True,
     288                                             encoding="locale",
     289                                             shell=True)
     290          except (OSError, subprocess.CalledProcessError) as why:
     291              #print('Command %s failed: %s' % (cmd, why))
     292              continue
     293          else:
     294              break
     295      else:
     296          return system, release, version
     297  
     298      # Parse the output
     299      info = info.strip()
     300      m = _ver_output.match(info)
     301      if m is not None:
     302          system, release, version = m.groups()
     303          # Strip trailing dots from version and release
     304          if release[-1] == '.':
     305              release = release[:-1]
     306          if version[-1] == '.':
     307              version = version[:-1]
     308          # Normalize the version and build strings (eliminating additional
     309          # zeros)
     310          version = _norm_version(version)
     311      return system, release, version
     312  
     313  _WIN32_CLIENT_RELEASES = {
     314      (5, 0): "2000",
     315      (5, 1): "XP",
     316      # Strictly, 5.2 client is XP 64-bit, but platform.py historically
     317      # has always called it 2003 Server
     318      (5, 2): "2003Server",
     319      (5, None): "post2003",
     320  
     321      (6, 0): "Vista",
     322      (6, 1): "7",
     323      (6, 2): "8",
     324      (6, 3): "8.1",
     325      (6, None): "post8.1",
     326  
     327      (10, 0): "10",
     328      (10, None): "post10",
     329  }
     330  
     331  # Server release name lookup will default to client names if necessary
     332  _WIN32_SERVER_RELEASES = {
     333      (5, 2): "2003Server",
     334  
     335      (6, 0): "2008Server",
     336      (6, 1): "2008ServerR2",
     337      (6, 2): "2012Server",
     338      (6, 3): "2012ServerR2",
     339      (6, None): "post2012ServerR2",
     340  }
     341  
     342  def win32_is_iot():
     343      return win32_edition() in ('IoTUAP', 'NanoServer', 'WindowsCoreHeadless', 'IoTEdgeOS')
     344  
     345  def win32_edition():
     346      try:
     347          try:
     348              import winreg
     349          except ImportError:
     350              import _winreg as winreg
     351      except ImportError:
     352          pass
     353      else:
     354          try:
     355              cvkey = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion'
     356              with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, cvkey) as key:
     357                  return winreg.QueryValueEx(key, 'EditionId')[0]
     358          except OSError:
     359              pass
     360  
     361      return None
     362  
     363  def win32_ver(release='', version='', csd='', ptype=''):
     364      try:
     365          from sys import getwindowsversion
     366      except ImportError:
     367          return release, version, csd, ptype
     368  
     369      winver = getwindowsversion()
     370      try:
     371          major, minor, build = map(int, _syscmd_ver()[2].split('.'))
     372      except ValueError:
     373          major, minor, build = winver.platform_version or winver[:3]
     374      version = '{0}.{1}.{2}'.format(major, minor, build)
     375  
     376      release = (_WIN32_CLIENT_RELEASES.get((major, minor)) or
     377                 _WIN32_CLIENT_RELEASES.get((major, None)) or
     378                 release)
     379  
     380      # getwindowsversion() reflect the compatibility mode Python is
     381      # running under, and so the service pack value is only going to be
     382      # valid if the versions match.
     383      if winver[:2] == (major, minor):
     384          try:
     385              csd = 'SP{}'.format(winver.service_pack_major)
     386          except AttributeError:
     387              if csd[:13] == 'Service Pack ':
     388                  csd = 'SP' + csd[13:]
     389  
     390      # VER_NT_SERVER = 3
     391      if getattr(winver, 'product_type', None) == 3:
     392          release = (_WIN32_SERVER_RELEASES.get((major, minor)) or
     393                     _WIN32_SERVER_RELEASES.get((major, None)) or
     394                     release)
     395  
     396      try:
     397          try:
     398              import winreg
     399          except ImportError:
     400              import _winreg as winreg
     401      except ImportError:
     402          pass
     403      else:
     404          try:
     405              cvkey = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion'
     406              with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, cvkey) as key:
     407                  ptype = winreg.QueryValueEx(key, 'CurrentType')[0]
     408          except OSError:
     409              pass
     410  
     411      return release, version, csd, ptype
     412  
     413  
     414  def _mac_ver_xml():
     415      fn = '/System/Library/CoreServices/SystemVersion.plist'
     416      if not os.path.exists(fn):
     417          return None
     418  
     419      try:
     420          import plistlib
     421      except ImportError:
     422          return None
     423  
     424      with open(fn, 'rb') as f:
     425          pl = plistlib.load(f)
     426      release = pl['ProductVersion']
     427      versioninfo = ('', '', '')
     428      machine = os.uname().machine
     429      if machine in ('ppc', 'Power Macintosh'):
     430          # Canonical name
     431          machine = 'PowerPC'
     432  
     433      return release, versioninfo, machine
     434  
     435  
     436  def mac_ver(release='', versioninfo=('', '', ''), machine=''):
     437  
     438      """ Get macOS version information and return it as tuple (release,
     439          versioninfo, machine) with versioninfo being a tuple (version,
     440          dev_stage, non_release_version).
     441  
     442          Entries which cannot be determined are set to the parameter values
     443          which default to ''. All tuple entries are strings.
     444      """
     445  
     446      # First try reading the information from an XML file which should
     447      # always be present
     448      info = _mac_ver_xml()
     449      if info is not None:
     450          return info
     451  
     452      # If that also doesn't work return the default values
     453      return release, versioninfo, machine
     454  
     455  def _java_getprop(name, default):
     456  
     457      from java.lang import System
     458      try:
     459          value = System.getProperty(name)
     460          if value is None:
     461              return default
     462          return value
     463      except AttributeError:
     464          return default
     465  
     466  def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')):
     467  
     468      """ Version interface for Jython.
     469  
     470          Returns a tuple (release, vendor, vminfo, osinfo) with vminfo being
     471          a tuple (vm_name, vm_release, vm_vendor) and osinfo being a
     472          tuple (os_name, os_version, os_arch).
     473  
     474          Values which cannot be determined are set to the defaults
     475          given as parameters (which all default to '').
     476  
     477      """
     478      # Import the needed APIs
     479      try:
     480          import java.lang
     481      except ImportError:
     482          return release, vendor, vminfo, osinfo
     483  
     484      vendor = _java_getprop('java.vendor', vendor)
     485      release = _java_getprop('java.version', release)
     486      vm_name, vm_release, vm_vendor = vminfo
     487      vm_name = _java_getprop('java.vm.name', vm_name)
     488      vm_vendor = _java_getprop('java.vm.vendor', vm_vendor)
     489      vm_release = _java_getprop('java.vm.version', vm_release)
     490      vminfo = vm_name, vm_release, vm_vendor
     491      os_name, os_version, os_arch = osinfo
     492      os_arch = _java_getprop('java.os.arch', os_arch)
     493      os_name = _java_getprop('java.os.name', os_name)
     494      os_version = _java_getprop('java.os.version', os_version)
     495      osinfo = os_name, os_version, os_arch
     496  
     497      return release, vendor, vminfo, osinfo
     498  
     499  ### System name aliasing
     500  
     501  def system_alias(system, release, version):
     502  
     503      """ Returns (system, release, version) aliased to common
     504          marketing names used for some systems.
     505  
     506          It also does some reordering of the information in some cases
     507          where it would otherwise cause confusion.
     508  
     509      """
     510      if system == 'SunOS':
     511          # Sun's OS
     512          if release < '5':
     513              # These releases use the old name SunOS
     514              return system, release, version
     515          # Modify release (marketing release = SunOS release - 3)
     516          l = release.split('.')
     517          if l:
     518              try:
     519                  major = int(l[0])
     520              except ValueError:
     521                  pass
     522              else:
     523                  major = major - 3
     524                  l[0] = str(major)
     525                  release = '.'.join(l)
     526          if release < '6':
     527              system = 'Solaris'
     528          else:
     529              # XXX Whatever the new SunOS marketing name is...
     530              system = 'Solaris'
     531  
     532      elif system in ('win32', 'win16'):
     533          # In case one of the other tricks
     534          system = 'Windows'
     535  
     536      # bpo-35516: Don't replace Darwin with macOS since input release and
     537      # version arguments can be different than the currently running version.
     538  
     539      return system, release, version
     540  
     541  ### Various internal helpers
     542  
     543  def _platform(*args):
     544  
     545      """ Helper to format the platform string in a filename
     546          compatible format e.g. "system-version-machine".
     547      """
     548      # Format the platform string
     549      platform = '-'.join(x.strip() for x in filter(len, args))
     550  
     551      # Cleanup some possible filename obstacles...
     552      platform = platform.replace(' ', '_')
     553      platform = platform.replace('/', '-')
     554      platform = platform.replace('\\', '-')
     555      platform = platform.replace(':', '-')
     556      platform = platform.replace(';', '-')
     557      platform = platform.replace('"', '-')
     558      platform = platform.replace('(', '-')
     559      platform = platform.replace(')', '-')
     560  
     561      # No need to report 'unknown' information...
     562      platform = platform.replace('unknown', '')
     563  
     564      # Fold '--'s and remove trailing '-'
     565      while 1:
     566          cleaned = platform.replace('--', '-')
     567          if cleaned == platform:
     568              break
     569          platform = cleaned
     570      while platform[-1] == '-':
     571          platform = platform[:-1]
     572  
     573      return platform
     574  
     575  def _node(default=''):
     576  
     577      """ Helper to determine the node name of this machine.
     578      """
     579      try:
     580          import socket
     581      except ImportError:
     582          # No sockets...
     583          return default
     584      try:
     585          return socket.gethostname()
     586      except OSError:
     587          # Still not working...
     588          return default
     589  
     590  def _follow_symlinks(filepath):
     591  
     592      """ In case filepath is a symlink, follow it until a
     593          real file is reached.
     594      """
     595      filepath = os.path.abspath(filepath)
     596      while os.path.islink(filepath):
     597          filepath = os.path.normpath(
     598              os.path.join(os.path.dirname(filepath), os.readlink(filepath)))
     599      return filepath
     600  
     601  
     602  def _syscmd_file(target, default=''):
     603  
     604      """ Interface to the system's file command.
     605  
     606          The function uses the -b option of the file command to have it
     607          omit the filename in its output. Follow the symlinks. It returns
     608          default in case the command should fail.
     609  
     610      """
     611      if sys.platform in ('dos', 'win32', 'win16'):
     612          # XXX Others too ?
     613          return default
     614  
     615      try:
     616          import subprocess
     617      except ImportError:
     618          return default
     619      target = _follow_symlinks(target)
     620      # "file" output is locale dependent: force the usage of the C locale
     621      # to get deterministic behavior.
     622      env = dict(os.environ, LC_ALL='C')
     623      try:
     624          # -b: do not prepend filenames to output lines (brief mode)
     625          output = subprocess.check_output(['file', '-b', target],
     626                                           stderr=subprocess.DEVNULL,
     627                                           env=env)
     628      except (OSError, subprocess.CalledProcessError):
     629          return default
     630      if not output:
     631          return default
     632      # With the C locale, the output should be mostly ASCII-compatible.
     633      # Decode from Latin-1 to prevent Unicode decode error.
     634      return output.decode('latin-1')
     635  
     636  ### Information about the used architecture
     637  
     638  # Default values for architecture; non-empty strings override the
     639  # defaults given as parameters
     640  _default_architecture = {
     641      'win32': ('', 'WindowsPE'),
     642      'win16': ('', 'Windows'),
     643      'dos': ('', 'MSDOS'),
     644  }
     645  
     646  def architecture(executable=sys.executable, bits='', linkage=''):
     647  
     648      """ Queries the given executable (defaults to the Python interpreter
     649          binary) for various architecture information.
     650  
     651          Returns a tuple (bits, linkage) which contains information about
     652          the bit architecture and the linkage format used for the
     653          executable. Both values are returned as strings.
     654  
     655          Values that cannot be determined are returned as given by the
     656          parameter presets. If bits is given as '', the sizeof(pointer)
     657          (or sizeof(long) on Python version < 1.5.2) is used as
     658          indicator for the supported pointer size.
     659  
     660          The function relies on the system's "file" command to do the
     661          actual work. This is available on most if not all Unix
     662          platforms. On some non-Unix platforms where the "file" command
     663          does not exist and the executable is set to the Python interpreter
     664          binary defaults from _default_architecture are used.
     665  
     666      """
     667      # Use the sizeof(pointer) as default number of bits if nothing
     668      # else is given as default.
     669      if not bits:
     670          import struct
     671          size = struct.calcsize('P')
     672          bits = str(size * 8) + 'bit'
     673  
     674      # Get data from the 'file' system command
     675      if executable:
     676          fileout = _syscmd_file(executable, '')
     677      else:
     678          fileout = ''
     679  
     680      if not fileout and \
     681         executable == sys.executable:
     682          # "file" command did not return anything; we'll try to provide
     683          # some sensible defaults then...
     684          if sys.platform in _default_architecture:
     685              b, l = _default_architecture[sys.platform]
     686              if b:
     687                  bits = b
     688              if l:
     689                  linkage = l
     690          return bits, linkage
     691  
     692      if 'executable' not in fileout and 'shared object' not in fileout:
     693          # Format not supported
     694          return bits, linkage
     695  
     696      # Bits
     697      if '32-bit' in fileout:
     698          bits = '32bit'
     699      elif '64-bit' in fileout:
     700          bits = '64bit'
     701  
     702      # Linkage
     703      if 'ELF' in fileout:
     704          linkage = 'ELF'
     705      elif 'PE' in fileout:
     706          # E.g. Windows uses this format
     707          if 'Windows' in fileout:
     708              linkage = 'WindowsPE'
     709          else:
     710              linkage = 'PE'
     711      elif 'COFF' in fileout:
     712          linkage = 'COFF'
     713      elif 'MS-DOS' in fileout:
     714          linkage = 'MSDOS'
     715      else:
     716          # XXX the A.OUT format also falls under this class...
     717          pass
     718  
     719      return bits, linkage
     720  
     721  
     722  def _get_machine_win32():
     723      # Try to use the PROCESSOR_* environment variables
     724      # available on Win XP and later; see
     725      # http://support.microsoft.com/kb/888731 and
     726      # http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM
     727  
     728      # WOW64 processes mask the native architecture
     729      return (
     730          os.environ.get('PROCESSOR_ARCHITEW6432', '') or
     731          os.environ.get('PROCESSOR_ARCHITECTURE', '')
     732      )
     733  
     734  
     735  class ESC[4;38;5;81m_Processor:
     736      @classmethod
     737      def get(cls):
     738          func = getattr(cls, f'get_{sys.platform}', cls.from_subprocess)
     739          return func() or ''
     740  
     741      def get_win32():
     742          return os.environ.get('PROCESSOR_IDENTIFIER', _get_machine_win32())
     743  
     744      def get_OpenVMS():
     745          try:
     746              import vms_lib
     747          except ImportError:
     748              pass
     749          else:
     750              csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0)
     751              return 'Alpha' if cpu_number >= 128 else 'VAX'
     752  
     753      def from_subprocess():
     754          """
     755          Fall back to `uname -p`
     756          """
     757          try:
     758              import subprocess
     759          except ImportError:
     760              return None
     761          try:
     762              return subprocess.check_output(
     763                  ['uname', '-p'],
     764                  stderr=subprocess.DEVNULL,
     765                  text=True,
     766                  encoding="utf8",
     767              ).strip()
     768          except (OSError, subprocess.CalledProcessError):
     769              pass
     770  
     771  
     772  def _unknown_as_blank(val):
     773      return '' if val == 'unknown' else val
     774  
     775  
     776  ### Portable uname() interface
     777  
     778  class ESC[4;38;5;81muname_result(
     779      ESC[4;38;5;149mcollectionsESC[4;38;5;149m.ESC[4;38;5;149mnamedtuple(
     780          "uname_result_base",
     781          "system node release version machine")
     782          ):
     783      """
     784      A uname_result that's largely compatible with a
     785      simple namedtuple except that 'processor' is
     786      resolved late and cached to avoid calling "uname"
     787      except when needed.
     788      """
     789  
     790      _fields = ('system', 'node', 'release', 'version', 'machine', 'processor')
     791  
     792      @functools.cached_property
     793      def processor(self):
     794          return _unknown_as_blank(_Processor.get())
     795  
     796      def __iter__(self):
     797          return itertools.chain(
     798              super().__iter__(),
     799              (self.processor,)
     800          )
     801  
     802      @classmethod
     803      def _make(cls, iterable):
     804          # override factory to affect length check
     805          num_fields = len(cls._fields) - 1
     806          result = cls.__new__(cls, *iterable)
     807          if len(result) != num_fields + 1:
     808              msg = f'Expected {num_fields} arguments, got {len(result)}'
     809              raise TypeError(msg)
     810          return result
     811  
     812      def __getitem__(self, key):
     813          return tuple(self)[key]
     814  
     815      def __len__(self):
     816          return len(tuple(iter(self)))
     817  
     818      def __reduce__(self):
     819          return uname_result, tuple(self)[:len(self._fields) - 1]
     820  
     821  
     822  _uname_cache = None
     823  
     824  
     825  def uname():
     826  
     827      """ Fairly portable uname interface. Returns a tuple
     828          of strings (system, node, release, version, machine, processor)
     829          identifying the underlying platform.
     830  
     831          Note that unlike the os.uname function this also returns
     832          possible processor information as an additional tuple entry.
     833  
     834          Entries which cannot be determined are set to ''.
     835  
     836      """
     837      global _uname_cache
     838  
     839      if _uname_cache is not None:
     840          return _uname_cache
     841  
     842      # Get some infos from the builtin os.uname API...
     843      try:
     844          system, node, release, version, machine = infos = os.uname()
     845      except AttributeError:
     846          system = sys.platform
     847          node = _node()
     848          release = version = machine = ''
     849          infos = ()
     850  
     851      if not any(infos):
     852          # uname is not available
     853  
     854          # Try win32_ver() on win32 platforms
     855          if system == 'win32':
     856              release, version, csd, ptype = win32_ver()
     857              machine = machine or _get_machine_win32()
     858  
     859          # Try the 'ver' system command available on some
     860          # platforms
     861          if not (release and version):
     862              system, release, version = _syscmd_ver(system)
     863              # Normalize system to what win32_ver() normally returns
     864              # (_syscmd_ver() tends to return the vendor name as well)
     865              if system == 'Microsoft Windows':
     866                  system = 'Windows'
     867              elif system == 'Microsoft' and release == 'Windows':
     868                  # Under Windows Vista and Windows Server 2008,
     869                  # Microsoft changed the output of the ver command. The
     870                  # release is no longer printed.  This causes the
     871                  # system and release to be misidentified.
     872                  system = 'Windows'
     873                  if '6.0' == version[:3]:
     874                      release = 'Vista'
     875                  else:
     876                      release = ''
     877  
     878          # In case we still don't know anything useful, we'll try to
     879          # help ourselves
     880          if system in ('win32', 'win16'):
     881              if not version:
     882                  if system == 'win32':
     883                      version = '32bit'
     884                  else:
     885                      version = '16bit'
     886              system = 'Windows'
     887  
     888          elif system[:4] == 'java':
     889              release, vendor, vminfo, osinfo = java_ver()
     890              system = 'Java'
     891              version = ', '.join(vminfo)
     892              if not version:
     893                  version = vendor
     894  
     895      # System specific extensions
     896      if system == 'OpenVMS':
     897          # OpenVMS seems to have release and version mixed up
     898          if not release or release == '0':
     899              release = version
     900              version = ''
     901  
     902      #  normalize name
     903      if system == 'Microsoft' and release == 'Windows':
     904          system = 'Windows'
     905          release = 'Vista'
     906  
     907      vals = system, node, release, version, machine
     908      # Replace 'unknown' values with the more portable ''
     909      _uname_cache = uname_result(*map(_unknown_as_blank, vals))
     910      return _uname_cache
     911  
     912  ### Direct interfaces to some of the uname() return values
     913  
     914  def system():
     915  
     916      """ Returns the system/OS name, e.g. 'Linux', 'Windows' or 'Java'.
     917  
     918          An empty string is returned if the value cannot be determined.
     919  
     920      """
     921      return uname().system
     922  
     923  def node():
     924  
     925      """ Returns the computer's network name (which may not be fully
     926          qualified)
     927  
     928          An empty string is returned if the value cannot be determined.
     929  
     930      """
     931      return uname().node
     932  
     933  def release():
     934  
     935      """ Returns the system's release, e.g. '2.2.0' or 'NT'
     936  
     937          An empty string is returned if the value cannot be determined.
     938  
     939      """
     940      return uname().release
     941  
     942  def version():
     943  
     944      """ Returns the system's release version, e.g. '#3 on degas'
     945  
     946          An empty string is returned if the value cannot be determined.
     947  
     948      """
     949      return uname().version
     950  
     951  def machine():
     952  
     953      """ Returns the machine type, e.g. 'i386'
     954  
     955          An empty string is returned if the value cannot be determined.
     956  
     957      """
     958      return uname().machine
     959  
     960  def processor():
     961  
     962      """ Returns the (true) processor name, e.g. 'amdk6'
     963  
     964          An empty string is returned if the value cannot be
     965          determined. Note that many platforms do not provide this
     966          information or simply return the same value as for machine(),
     967          e.g.  NetBSD does this.
     968  
     969      """
     970      return uname().processor
     971  
     972  ### Various APIs for extracting information from sys.version
     973  
     974  _sys_version_parser = re.compile(
     975      r'([\w.+]+)\s*'  # "version<space>"
     976      r'\(#?([^,]+)'  # "(#buildno"
     977      r'(?:,\s*([\w ]*)'  # ", builddate"
     978      r'(?:,\s*([\w :]*))?)?\)\s*'  # ", buildtime)<space>"
     979      r'\[([^\]]+)\]?', re.ASCII)  # "[compiler]"
     980  
     981  _ironpython_sys_version_parser = re.compile(
     982      r'IronPython\s*'
     983      r'([\d\.]+)'
     984      r'(?: \(([\d\.]+)\))?'
     985      r' on (.NET [\d\.]+)', re.ASCII)
     986  
     987  # IronPython covering 2.6 and 2.7
     988  _ironpython26_sys_version_parser = re.compile(
     989      r'([\d.]+)\s*'
     990      r'\(IronPython\s*'
     991      r'[\d.]+\s*'
     992      r'\(([\d.]+)\) on ([\w.]+ [\d.]+(?: \(\d+-bit\))?)\)'
     993  )
     994  
     995  _pypy_sys_version_parser = re.compile(
     996      r'([\w.+]+)\s*'
     997      r'\(#?([^,]+),\s*([\w ]+),\s*([\w :]+)\)\s*'
     998      r'\[PyPy [^\]]+\]?')
     999  
    1000  _sys_version_cache = {}
    1001  
    1002  def _sys_version(sys_version=None):
    1003  
    1004      """ Returns a parsed version of Python's sys.version as tuple
    1005          (name, version, branch, revision, buildno, builddate, compiler)
    1006          referring to the Python implementation name, version, branch,
    1007          revision, build number, build date/time as string and the compiler
    1008          identification string.
    1009  
    1010          Note that unlike the Python sys.version, the returned value
    1011          for the Python version will always include the patchlevel (it
    1012          defaults to '.0').
    1013  
    1014          The function returns empty strings for tuple entries that
    1015          cannot be determined.
    1016  
    1017          sys_version may be given to parse an alternative version
    1018          string, e.g. if the version was read from a different Python
    1019          interpreter.
    1020  
    1021      """
    1022      # Get the Python version
    1023      if sys_version is None:
    1024          sys_version = sys.version
    1025  
    1026      # Try the cache first
    1027      result = _sys_version_cache.get(sys_version, None)
    1028      if result is not None:
    1029          return result
    1030  
    1031      # Parse it
    1032      if 'IronPython' in sys_version:
    1033          # IronPython
    1034          name = 'IronPython'
    1035          if sys_version.startswith('IronPython'):
    1036              match = _ironpython_sys_version_parser.match(sys_version)
    1037          else:
    1038              match = _ironpython26_sys_version_parser.match(sys_version)
    1039  
    1040          if match is None:
    1041              raise ValueError(
    1042                  'failed to parse IronPython sys.version: %s' %
    1043                  repr(sys_version))
    1044  
    1045          version, alt_version, compiler = match.groups()
    1046          buildno = ''
    1047          builddate = ''
    1048  
    1049      elif sys.platform.startswith('java'):
    1050          # Jython
    1051          name = 'Jython'
    1052          match = _sys_version_parser.match(sys_version)
    1053          if match is None:
    1054              raise ValueError(
    1055                  'failed to parse Jython sys.version: %s' %
    1056                  repr(sys_version))
    1057          version, buildno, builddate, buildtime, _ = match.groups()
    1058          if builddate is None:
    1059              builddate = ''
    1060          compiler = sys.platform
    1061  
    1062      elif "PyPy" in sys_version:
    1063          # PyPy
    1064          name = "PyPy"
    1065          match = _pypy_sys_version_parser.match(sys_version)
    1066          if match is None:
    1067              raise ValueError("failed to parse PyPy sys.version: %s" %
    1068                               repr(sys_version))
    1069          version, buildno, builddate, buildtime = match.groups()
    1070          compiler = ""
    1071  
    1072      else:
    1073          # CPython
    1074          match = _sys_version_parser.match(sys_version)
    1075          if match is None:
    1076              raise ValueError(
    1077                  'failed to parse CPython sys.version: %s' %
    1078                  repr(sys_version))
    1079          version, buildno, builddate, buildtime, compiler = \
    1080                match.groups()
    1081          name = 'CPython'
    1082          if builddate is None:
    1083              builddate = ''
    1084          elif buildtime:
    1085              builddate = builddate + ' ' + buildtime
    1086  
    1087      if hasattr(sys, '_git'):
    1088          _, branch, revision = sys._git
    1089      elif hasattr(sys, '_mercurial'):
    1090          _, branch, revision = sys._mercurial
    1091      else:
    1092          branch = ''
    1093          revision = ''
    1094  
    1095      # Add the patchlevel version if missing
    1096      l = version.split('.')
    1097      if len(l) == 2:
    1098          l.append('0')
    1099          version = '.'.join(l)
    1100  
    1101      # Build and cache the result
    1102      result = (name, version, branch, revision, buildno, builddate, compiler)
    1103      _sys_version_cache[sys_version] = result
    1104      return result
    1105  
    1106  def python_implementation():
    1107  
    1108      """ Returns a string identifying the Python implementation.
    1109  
    1110          Currently, the following implementations are identified:
    1111            'CPython' (C implementation of Python),
    1112            'IronPython' (.NET implementation of Python),
    1113            'Jython' (Java implementation of Python),
    1114            'PyPy' (Python implementation of Python).
    1115  
    1116      """
    1117      return _sys_version()[0]
    1118  
    1119  def python_version():
    1120  
    1121      """ Returns the Python version as string 'major.minor.patchlevel'
    1122  
    1123          Note that unlike the Python sys.version, the returned value
    1124          will always include the patchlevel (it defaults to 0).
    1125  
    1126      """
    1127      return _sys_version()[1]
    1128  
    1129  def python_version_tuple():
    1130  
    1131      """ Returns the Python version as tuple (major, minor, patchlevel)
    1132          of strings.
    1133  
    1134          Note that unlike the Python sys.version, the returned value
    1135          will always include the patchlevel (it defaults to 0).
    1136  
    1137      """
    1138      return tuple(_sys_version()[1].split('.'))
    1139  
    1140  def python_branch():
    1141  
    1142      """ Returns a string identifying the Python implementation
    1143          branch.
    1144  
    1145          For CPython this is the SCM branch from which the
    1146          Python binary was built.
    1147  
    1148          If not available, an empty string is returned.
    1149  
    1150      """
    1151  
    1152      return _sys_version()[2]
    1153  
    1154  def python_revision():
    1155  
    1156      """ Returns a string identifying the Python implementation
    1157          revision.
    1158  
    1159          For CPython this is the SCM revision from which the
    1160          Python binary was built.
    1161  
    1162          If not available, an empty string is returned.
    1163  
    1164      """
    1165      return _sys_version()[3]
    1166  
    1167  def python_build():
    1168  
    1169      """ Returns a tuple (buildno, builddate) stating the Python
    1170          build number and date as strings.
    1171  
    1172      """
    1173      return _sys_version()[4:6]
    1174  
    1175  def python_compiler():
    1176  
    1177      """ Returns a string identifying the compiler used for compiling
    1178          Python.
    1179  
    1180      """
    1181      return _sys_version()[6]
    1182  
    1183  ### The Opus Magnum of platform strings :-)
    1184  
    1185  _platform_cache = {}
    1186  
    1187  def platform(aliased=0, terse=0):
    1188  
    1189      """ Returns a single string identifying the underlying platform
    1190          with as much useful information as possible (but no more :).
    1191  
    1192          The output is intended to be human readable rather than
    1193          machine parseable. It may look different on different
    1194          platforms and this is intended.
    1195  
    1196          If "aliased" is true, the function will use aliases for
    1197          various platforms that report system names which differ from
    1198          their common names, e.g. SunOS will be reported as
    1199          Solaris. The system_alias() function is used to implement
    1200          this.
    1201  
    1202          Setting terse to true causes the function to return only the
    1203          absolute minimum information needed to identify the platform.
    1204  
    1205      """
    1206      result = _platform_cache.get((aliased, terse), None)
    1207      if result is not None:
    1208          return result
    1209  
    1210      # Get uname information and then apply platform specific cosmetics
    1211      # to it...
    1212      system, node, release, version, machine, processor = uname()
    1213      if machine == processor:
    1214          processor = ''
    1215      if aliased:
    1216          system, release, version = system_alias(system, release, version)
    1217  
    1218      if system == 'Darwin':
    1219          # macOS (darwin kernel)
    1220          macos_release = mac_ver()[0]
    1221          if macos_release:
    1222              system = 'macOS'
    1223              release = macos_release
    1224  
    1225      if system == 'Windows':
    1226          # MS platforms
    1227          rel, vers, csd, ptype = win32_ver(version)
    1228          if terse:
    1229              platform = _platform(system, release)
    1230          else:
    1231              platform = _platform(system, release, version, csd)
    1232  
    1233      elif system in ('Linux',):
    1234          # check for libc vs. glibc
    1235          libcname, libcversion = libc_ver()
    1236          platform = _platform(system, release, machine, processor,
    1237                               'with',
    1238                               libcname+libcversion)
    1239      elif system == 'Java':
    1240          # Java platforms
    1241          r, v, vminfo, (os_name, os_version, os_arch) = java_ver()
    1242          if terse or not os_name:
    1243              platform = _platform(system, release, version)
    1244          else:
    1245              platform = _platform(system, release, version,
    1246                                   'on',
    1247                                   os_name, os_version, os_arch)
    1248  
    1249      else:
    1250          # Generic handler
    1251          if terse:
    1252              platform = _platform(system, release)
    1253          else:
    1254              bits, linkage = architecture(sys.executable)
    1255              platform = _platform(system, release, machine,
    1256                                   processor, bits, linkage)
    1257  
    1258      _platform_cache[(aliased, terse)] = platform
    1259      return platform
    1260  
    1261  ### freedesktop.org os-release standard
    1262  # https://www.freedesktop.org/software/systemd/man/os-release.html
    1263  
    1264  # NAME=value with optional quotes (' or "). The regular expression is less
    1265  # strict than shell lexer, but that's ok.
    1266  _os_release_line = re.compile(
    1267      "^(?P<name>[a-zA-Z0-9_]+)=(?P<quote>[\"\']?)(?P<value>.*)(?P=quote)$"
    1268  )
    1269  # unescape five special characters mentioned in the standard
    1270  _os_release_unescape = re.compile(r"\\([\\\$\"\'`])")
    1271  # /etc takes precedence over /usr/lib
    1272  _os_release_candidates = ("/etc/os-release", "/usr/lib/os-release")
    1273  _os_release_cache = None
    1274  
    1275  
    1276  def _parse_os_release(lines):
    1277      # These fields are mandatory fields with well-known defaults
    1278      # in practice all Linux distributions override NAME, ID, and PRETTY_NAME.
    1279      info = {
    1280          "NAME": "Linux",
    1281          "ID": "linux",
    1282          "PRETTY_NAME": "Linux",
    1283      }
    1284  
    1285      for line in lines:
    1286          mo = _os_release_line.match(line)
    1287          if mo is not None:
    1288              info[mo.group('name')] = _os_release_unescape.sub(
    1289                  r"\1", mo.group('value')
    1290              )
    1291  
    1292      return info
    1293  
    1294  
    1295  def freedesktop_os_release():
    1296      """Return operation system identification from freedesktop.org os-release
    1297      """
    1298      global _os_release_cache
    1299  
    1300      if _os_release_cache is None:
    1301          errno = None
    1302          for candidate in _os_release_candidates:
    1303              try:
    1304                  with open(candidate, encoding="utf-8") as f:
    1305                      _os_release_cache = _parse_os_release(f)
    1306                  break
    1307              except OSError as e:
    1308                  errno = e.errno
    1309          else:
    1310              raise OSError(
    1311                  errno,
    1312                  f"Unable to read files {', '.join(_os_release_candidates)}"
    1313              )
    1314  
    1315      return _os_release_cache.copy()
    1316  
    1317  
    1318  ### Command line interface
    1319  
    1320  if __name__ == '__main__':
    1321      # Default is to print the aliased verbose platform string
    1322      terse = ('terse' in sys.argv or '--terse' in sys.argv)
    1323      aliased = (not 'nonaliased' in sys.argv and not '--nonaliased' in sys.argv)
    1324      print(platform(aliased, terse))
    1325      sys.exit(0)