1#!/usr/local/bin/python3.9 2 3""" This module tries to retrieve as much platform-identifying data as 4 possible. It makes this information available via function APIs. 5 6 If called from the command line, it prints the platform 7 information concatenated as single string to stdout. The output 8 format is useable as part of a filename. 9 10""" 11# This module is maintained by Marc-Andre Lemburg <mal@egenix.com>. 12# If you find problems, please submit bug reports/patches via the 13# Python bug tracker (http://bugs.python.org) and assign them to "lemburg". 14# 15# Still needed: 16# * support for MS-DOS (PythonDX ?) 17# * support for Amiga and other still unsupported platforms running Python 18# * support for additional Linux distributions 19# 20# Many thanks to all those who helped adding platform-specific 21# checks (in no particular order): 22# 23# Charles G Waldman, David Arnold, Gordon McMillan, Ben Darnell, 24# Jeff Bauer, Cliff Crawford, Ivan Van Laningham, Josef 25# Betancourt, Randall Hopper, Karl Putland, John Farrell, Greg 26# Andruk, Just van Rossum, Thomas Heller, Mark R. Levinson, Mark 27# Hammond, Bill Tutt, Hans Nowak, Uwe Zessin (OpenVMS support), 28# Colin Kong, Trent Mick, Guido van Rossum, Anthony Baxter, Steve 29# Dower 30# 31# History: 32# 33# <see CVS and SVN checkin messages for history> 34# 35# 1.0.8 - changed Windows support to read version from kernel32.dll 36# 1.0.7 - added DEV_NULL 37# 1.0.6 - added linux_distribution() 38# 1.0.5 - fixed Java support to allow running the module on Jython 39# 1.0.4 - added IronPython support 40# 1.0.3 - added normalization of Windows system name 41# 1.0.2 - added more Windows support 42# 1.0.1 - reformatted to make doc.py happy 43# 1.0.0 - reformatted a bit and checked into Python CVS 44# 0.8.0 - added sys.version parser and various new access 45# APIs (python_version(), python_compiler(), etc.) 46# 0.7.2 - fixed architecture() to use sizeof(pointer) where available 47# 0.7.1 - added support for Caldera OpenLinux 48# 0.7.0 - some fixes for WinCE; untabified the source file 49# 0.6.2 - support for OpenVMS - requires version 1.5.2-V006 or higher and 50# vms_lib.getsyi() configured 51# 0.6.1 - added code to prevent 'uname -p' on platforms which are 52# known not to support it 53# 0.6.0 - fixed win32_ver() to hopefully work on Win95,98,NT and Win2k; 54# did some cleanup of the interfaces - some APIs have changed 55# 0.5.5 - fixed another type in the MacOS code... should have 56# used more coffee today ;-) 57# 0.5.4 - fixed a few typos in the MacOS code 58# 0.5.3 - added experimental MacOS support; added better popen() 59# workarounds in _syscmd_ver() -- still not 100% elegant 60# though 61# 0.5.2 - fixed uname() to return '' instead of 'unknown' in all 62# return values (the system uname command tends to return 63# 'unknown' instead of just leaving the field empty) 64# 0.5.1 - included code for slackware dist; added exception handlers 65# to cover up situations where platforms don't have os.popen 66# (e.g. Mac) or fail on socket.gethostname(); fixed libc 67# detection RE 68# 0.5.0 - changed the API names referring to system commands to *syscmd*; 69# added java_ver(); made syscmd_ver() a private 70# API (was system_ver() in previous versions) -- use uname() 71# instead; extended the win32_ver() to also return processor 72# type information 73# 0.4.0 - added win32_ver() and modified the platform() output for WinXX 74# 0.3.4 - fixed a bug in _follow_symlinks() 75# 0.3.3 - fixed popen() and "file" command invocation bugs 76# 0.3.2 - added architecture() API and support for it in platform() 77# 0.3.1 - fixed syscmd_ver() RE to support Windows NT 78# 0.3.0 - added system alias support 79# 0.2.3 - removed 'wince' again... oh well. 80# 0.2.2 - added 'wince' to syscmd_ver() supported platforms 81# 0.2.1 - added cache logic and changed the platform string format 82# 0.2.0 - changed the API to use functions instead of module globals 83# since some action take too long to be run on module import 84# 0.1.0 - first release 85# 86# You can always get the latest version of this module at: 87# 88# http://www.egenix.com/files/python/platform.py 89# 90# If that URL should fail, try contacting the author. 91 92__copyright__ = """ 93 Copyright (c) 1999-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com 94 Copyright (c) 2000-2010, eGenix.com Software GmbH; mailto:info@egenix.com 95 96 Permission to use, copy, modify, and distribute this software and its 97 documentation for any purpose and without fee or royalty is hereby granted, 98 provided that the above copyright notice appear in all copies and that 99 both that copyright notice and this permission notice appear in 100 supporting documentation or portions thereof, including modifications, 101 that you make. 102 103 EGENIX.COM SOFTWARE GMBH DISCLAIMS ALL WARRANTIES WITH REGARD TO 104 THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 105 FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, 106 INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING 107 FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 108 NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION 109 WITH THE USE OR PERFORMANCE OF THIS SOFTWARE ! 110 111""" 112 113__version__ = '1.0.8' 114 115import collections 116import os 117import re 118import sys 119import subprocess 120import functools 121import itertools 122 123### Globals & Constants 124 125# Helper for comparing two version number strings. 126# Based on the description of the PHP's version_compare(): 127# http://php.net/manual/en/function.version-compare.php 128 129_ver_stages = { 130 # any string not found in this dict, will get 0 assigned 131 'dev': 10, 132 'alpha': 20, 'a': 20, 133 'beta': 30, 'b': 30, 134 'c': 40, 135 'RC': 50, 'rc': 50, 136 # number, will get 100 assigned 137 'pl': 200, 'p': 200, 138} 139 140_component_re = re.compile(r'([0-9]+|[._+-])') 141 142def _comparable_version(version): 143 result = [] 144 for v in _component_re.split(version): 145 if v not in '._+-': 146 try: 147 v = int(v, 10) 148 t = 100 149 except ValueError: 150 t = _ver_stages.get(v, 0) 151 result.extend((t, v)) 152 return result 153 154### Platform specific APIs 155 156_libc_search = re.compile(b'(__libc_init)' 157 b'|' 158 b'(GLIBC_([0-9.]+))' 159 b'|' 160 br'(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)', re.ASCII) 161 162def libc_ver(executable=None, lib='', version='', chunksize=16384): 163 164 """ Tries to determine the libc version that the file executable 165 (which defaults to the Python interpreter) is linked against. 166 167 Returns a tuple of strings (lib,version) which default to the 168 given parameters in case the lookup fails. 169 170 Note that the function has intimate knowledge of how different 171 libc versions add symbols to the executable and thus is probably 172 only useable for executables compiled using gcc. 173 174 The file is read and scanned in chunks of chunksize bytes. 175 176 """ 177 if executable is None: 178 try: 179 ver = os.confstr('CS_GNU_LIBC_VERSION') 180 # parse 'glibc 2.28' as ('glibc', '2.28') 181 parts = ver.split(maxsplit=1) 182 if len(parts) == 2: 183 return tuple(parts) 184 except (AttributeError, ValueError, OSError): 185 # os.confstr() or CS_GNU_LIBC_VERSION value not available 186 pass 187 188 executable = sys.executable 189 190 V = _comparable_version 191 if hasattr(os.path, 'realpath'): 192 # Python 2.2 introduced os.path.realpath(); it is used 193 # here to work around problems with Cygwin not being 194 # able to open symlinks for reading 195 executable = os.path.realpath(executable) 196 with open(executable, 'rb') as f: 197 binary = f.read(chunksize) 198 pos = 0 199 while pos < len(binary): 200 if b'libc' in binary or b'GLIBC' in binary: 201 m = _libc_search.search(binary, pos) 202 else: 203 m = None 204 if not m or m.end() == len(binary): 205 chunk = f.read(chunksize) 206 if chunk: 207 binary = binary[max(pos, len(binary) - 1000):] + chunk 208 pos = 0 209 continue 210 if not m: 211 break 212 libcinit, glibc, glibcversion, so, threads, soversion = [ 213 s.decode('latin1') if s is not None else s 214 for s in m.groups()] 215 if libcinit and not lib: 216 lib = 'libc' 217 elif glibc: 218 if lib != 'glibc': 219 lib = 'glibc' 220 version = glibcversion 221 elif V(glibcversion) > V(version): 222 version = glibcversion 223 elif so: 224 if lib != 'glibc': 225 lib = 'libc' 226 if soversion and (not version or V(soversion) > V(version)): 227 version = soversion 228 if threads and version[-len(threads):] != threads: 229 version = version + threads 230 pos = m.end() 231 return lib, version 232 233def _norm_version(version, build=''): 234 235 """ Normalize the version and build strings and return a single 236 version string using the format major.minor.build (or patchlevel). 237 """ 238 l = version.split('.') 239 if build: 240 l.append(build) 241 try: 242 strings = list(map(str, map(int, l))) 243 except ValueError: 244 strings = l 245 version = '.'.join(strings[:3]) 246 return version 247 248_ver_output = re.compile(r'(?:([\w ]+) ([\w.]+) ' 249 r'.*' 250 r'\[.* ([\d.]+)\])') 251 252# Examples of VER command output: 253# 254# Windows 2000: Microsoft Windows 2000 [Version 5.00.2195] 255# Windows XP: Microsoft Windows XP [Version 5.1.2600] 256# Windows Vista: Microsoft Windows [Version 6.0.6002] 257# 258# Note that the "Version" string gets localized on different 259# Windows versions. 260 261def _syscmd_ver(system='', release='', version='', 262 263 supported_platforms=('win32', 'win16', 'dos')): 264 265 """ Tries to figure out the OS version used and returns 266 a tuple (system, release, version). 267 268 It uses the "ver" shell command for this which is known 269 to exists on Windows, DOS. XXX Others too ? 270 271 In case this fails, the given parameters are used as 272 defaults. 273 274 """ 275 if sys.platform not in supported_platforms: 276 return system, release, version 277 278 # Try some common cmd strings 279 import subprocess 280 for cmd in ('ver', 'command /c ver', 'cmd /c ver'): 281 try: 282 info = subprocess.check_output(cmd, 283 stdin=subprocess.DEVNULL, 284 stderr=subprocess.DEVNULL, 285 text=True, 286 shell=True) 287 except (OSError, subprocess.CalledProcessError) as why: 288 #print('Command %s failed: %s' % (cmd, why)) 289 continue 290 else: 291 break 292 else: 293 return system, release, version 294 295 # Parse the output 296 info = info.strip() 297 m = _ver_output.match(info) 298 if m is not None: 299 system, release, version = m.groups() 300 # Strip trailing dots from version and release 301 if release[-1] == '.': 302 release = release[:-1] 303 if version[-1] == '.': 304 version = version[:-1] 305 # Normalize the version and build strings (eliminating additional 306 # zeros) 307 version = _norm_version(version) 308 return system, release, version 309 310_WIN32_CLIENT_RELEASES = { 311 (5, 0): "2000", 312 (5, 1): "XP", 313 # Strictly, 5.2 client is XP 64-bit, but platform.py historically 314 # has always called it 2003 Server 315 (5, 2): "2003Server", 316 (5, None): "post2003", 317 318 (6, 0): "Vista", 319 (6, 1): "7", 320 (6, 2): "8", 321 (6, 3): "8.1", 322 (6, None): "post8.1", 323 324 (10, 0): "10", 325 (10, None): "post10", 326} 327 328# Server release name lookup will default to client names if necessary 329_WIN32_SERVER_RELEASES = { 330 (5, 2): "2003Server", 331 332 (6, 0): "2008Server", 333 (6, 1): "2008ServerR2", 334 (6, 2): "2012Server", 335 (6, 3): "2012ServerR2", 336 (6, None): "post2012ServerR2", 337} 338 339def win32_is_iot(): 340 return win32_edition() in ('IoTUAP', 'NanoServer', 'WindowsCoreHeadless', 'IoTEdgeOS') 341 342def win32_edition(): 343 try: 344 try: 345 import winreg 346 except ImportError: 347 import _winreg as winreg 348 except ImportError: 349 pass 350 else: 351 try: 352 cvkey = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion' 353 with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, cvkey) as key: 354 return winreg.QueryValueEx(key, 'EditionId')[0] 355 except OSError: 356 pass 357 358 return None 359 360def win32_ver(release='', version='', csd='', ptype=''): 361 try: 362 from sys import getwindowsversion 363 except ImportError: 364 return release, version, csd, ptype 365 366 winver = getwindowsversion() 367 try: 368 major, minor, build = map(int, _syscmd_ver()[2].split('.')) 369 except ValueError: 370 major, minor, build = winver.platform_version or winver[:3] 371 version = '{0}.{1}.{2}'.format(major, minor, build) 372 373 release = (_WIN32_CLIENT_RELEASES.get((major, minor)) or 374 _WIN32_CLIENT_RELEASES.get((major, None)) or 375 release) 376 377 # getwindowsversion() reflect the compatibility mode Python is 378 # running under, and so the service pack value is only going to be 379 # valid if the versions match. 380 if winver[:2] == (major, minor): 381 try: 382 csd = 'SP{}'.format(winver.service_pack_major) 383 except AttributeError: 384 if csd[:13] == 'Service Pack ': 385 csd = 'SP' + csd[13:] 386 387 # VER_NT_SERVER = 3 388 if getattr(winver, 'product_type', None) == 3: 389 release = (_WIN32_SERVER_RELEASES.get((major, minor)) or 390 _WIN32_SERVER_RELEASES.get((major, None)) or 391 release) 392 393 try: 394 try: 395 import winreg 396 except ImportError: 397 import _winreg as winreg 398 except ImportError: 399 pass 400 else: 401 try: 402 cvkey = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion' 403 with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, cvkey) as key: 404 ptype = winreg.QueryValueEx(key, 'CurrentType')[0] 405 except OSError: 406 pass 407 408 return release, version, csd, ptype 409 410 411def _mac_ver_xml(): 412 fn = '/System/Library/CoreServices/SystemVersion.plist' 413 if not os.path.exists(fn): 414 return None 415 416 try: 417 import plistlib 418 except ImportError: 419 return None 420 421 with open(fn, 'rb') as f: 422 pl = plistlib.load(f) 423 release = pl['ProductVersion'] 424 versioninfo = ('', '', '') 425 machine = os.uname().machine 426 if machine in ('ppc', 'Power Macintosh'): 427 # Canonical name 428 machine = 'PowerPC' 429 430 return release, versioninfo, machine 431 432 433def mac_ver(release='', versioninfo=('', '', ''), machine=''): 434 435 """ Get macOS version information and return it as tuple (release, 436 versioninfo, machine) with versioninfo being a tuple (version, 437 dev_stage, non_release_version). 438 439 Entries which cannot be determined are set to the parameter values 440 which default to ''. All tuple entries are strings. 441 """ 442 443 # First try reading the information from an XML file which should 444 # always be present 445 info = _mac_ver_xml() 446 if info is not None: 447 return info 448 449 # If that also doesn't work return the default values 450 return release, versioninfo, machine 451 452def _java_getprop(name, default): 453 454 from java.lang import System 455 try: 456 value = System.getProperty(name) 457 if value is None: 458 return default 459 return value 460 except AttributeError: 461 return default 462 463def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')): 464 465 """ Version interface for Jython. 466 467 Returns a tuple (release, vendor, vminfo, osinfo) with vminfo being 468 a tuple (vm_name, vm_release, vm_vendor) and osinfo being a 469 tuple (os_name, os_version, os_arch). 470 471 Values which cannot be determined are set to the defaults 472 given as parameters (which all default to ''). 473 474 """ 475 # Import the needed APIs 476 try: 477 import java.lang 478 except ImportError: 479 return release, vendor, vminfo, osinfo 480 481 vendor = _java_getprop('java.vendor', vendor) 482 release = _java_getprop('java.version', release) 483 vm_name, vm_release, vm_vendor = vminfo 484 vm_name = _java_getprop('java.vm.name', vm_name) 485 vm_vendor = _java_getprop('java.vm.vendor', vm_vendor) 486 vm_release = _java_getprop('java.vm.version', vm_release) 487 vminfo = vm_name, vm_release, vm_vendor 488 os_name, os_version, os_arch = osinfo 489 os_arch = _java_getprop('java.os.arch', os_arch) 490 os_name = _java_getprop('java.os.name', os_name) 491 os_version = _java_getprop('java.os.version', os_version) 492 osinfo = os_name, os_version, os_arch 493 494 return release, vendor, vminfo, osinfo 495 496### System name aliasing 497 498def system_alias(system, release, version): 499 500 """ Returns (system, release, version) aliased to common 501 marketing names used for some systems. 502 503 It also does some reordering of the information in some cases 504 where it would otherwise cause confusion. 505 506 """ 507 if system == 'SunOS': 508 # Sun's OS 509 if release < '5': 510 # These releases use the old name SunOS 511 return system, release, version 512 # Modify release (marketing release = SunOS release - 3) 513 l = release.split('.') 514 if l: 515 try: 516 major = int(l[0]) 517 except ValueError: 518 pass 519 else: 520 major = major - 3 521 l[0] = str(major) 522 release = '.'.join(l) 523 if release < '6': 524 system = 'Solaris' 525 else: 526 # XXX Whatever the new SunOS marketing name is... 527 system = 'Solaris' 528 529 elif system == 'IRIX64': 530 # IRIX reports IRIX64 on platforms with 64-bit support; yet it 531 # is really a version and not a different platform, since 32-bit 532 # apps are also supported.. 533 system = 'IRIX' 534 if version: 535 version = version + ' (64bit)' 536 else: 537 version = '64bit' 538 539 elif system in ('win32', 'win16'): 540 # In case one of the other tricks 541 system = 'Windows' 542 543 # bpo-35516: Don't replace Darwin with macOS since input release and 544 # version arguments can be different than the currently running version. 545 546 return system, release, version 547 548### Various internal helpers 549 550def _platform(*args): 551 552 """ Helper to format the platform string in a filename 553 compatible format e.g. "system-version-machine". 554 """ 555 # Format the platform string 556 platform = '-'.join(x.strip() for x in filter(len, args)) 557 558 # Cleanup some possible filename obstacles... 559 platform = platform.replace(' ', '_') 560 platform = platform.replace('/', '-') 561 platform = platform.replace('\\', '-') 562 platform = platform.replace(':', '-') 563 platform = platform.replace(';', '-') 564 platform = platform.replace('"', '-') 565 platform = platform.replace('(', '-') 566 platform = platform.replace(')', '-') 567 568 # No need to report 'unknown' information... 569 platform = platform.replace('unknown', '') 570 571 # Fold '--'s and remove trailing '-' 572 while 1: 573 cleaned = platform.replace('--', '-') 574 if cleaned == platform: 575 break 576 platform = cleaned 577 while platform[-1] == '-': 578 platform = platform[:-1] 579 580 return platform 581 582def _node(default=''): 583 584 """ Helper to determine the node name of this machine. 585 """ 586 try: 587 import socket 588 except ImportError: 589 # No sockets... 590 return default 591 try: 592 return socket.gethostname() 593 except OSError: 594 # Still not working... 595 return default 596 597def _follow_symlinks(filepath): 598 599 """ In case filepath is a symlink, follow it until a 600 real file is reached. 601 """ 602 filepath = os.path.abspath(filepath) 603 while os.path.islink(filepath): 604 filepath = os.path.normpath( 605 os.path.join(os.path.dirname(filepath), os.readlink(filepath))) 606 return filepath 607 608 609def _syscmd_file(target, default=''): 610 611 """ Interface to the system's file command. 612 613 The function uses the -b option of the file command to have it 614 omit the filename in its output. Follow the symlinks. It returns 615 default in case the command should fail. 616 617 """ 618 if sys.platform in ('dos', 'win32', 'win16'): 619 # XXX Others too ? 620 return default 621 622 import subprocess 623 target = _follow_symlinks(target) 624 # "file" output is locale dependent: force the usage of the C locale 625 # to get deterministic behavior. 626 env = dict(os.environ, LC_ALL='C') 627 try: 628 # -b: do not prepend filenames to output lines (brief mode) 629 output = subprocess.check_output(['file', '-b', target], 630 stderr=subprocess.DEVNULL, 631 env=env) 632 except (OSError, subprocess.CalledProcessError): 633 return default 634 if not output: 635 return default 636 # With the C locale, the output should be mostly ASCII-compatible. 637 # Decode from Latin-1 to prevent Unicode decode error. 638 return output.decode('latin-1') 639 640### Information about the used architecture 641 642# Default values for architecture; non-empty strings override the 643# defaults given as parameters 644_default_architecture = { 645 'win32': ('', 'WindowsPE'), 646 'win16': ('', 'Windows'), 647 'dos': ('', 'MSDOS'), 648} 649 650def architecture(executable=sys.executable, bits='', linkage=''): 651 652 """ Queries the given executable (defaults to the Python interpreter 653 binary) for various architecture information. 654 655 Returns a tuple (bits, linkage) which contains information about 656 the bit architecture and the linkage format used for the 657 executable. Both values are returned as strings. 658 659 Values that cannot be determined are returned as given by the 660 parameter presets. If bits is given as '', the sizeof(pointer) 661 (or sizeof(long) on Python version < 1.5.2) is used as 662 indicator for the supported pointer size. 663 664 The function relies on the system's "file" command to do the 665 actual work. This is available on most if not all Unix 666 platforms. On some non-Unix platforms where the "file" command 667 does not exist and the executable is set to the Python interpreter 668 binary defaults from _default_architecture are used. 669 670 """ 671 # Use the sizeof(pointer) as default number of bits if nothing 672 # else is given as default. 673 if not bits: 674 import struct 675 size = struct.calcsize('P') 676 bits = str(size * 8) + 'bit' 677 678 # Get data from the 'file' system command 679 if executable: 680 fileout = _syscmd_file(executable, '') 681 else: 682 fileout = '' 683 684 if not fileout and \ 685 executable == sys.executable: 686 # "file" command did not return anything; we'll try to provide 687 # some sensible defaults then... 688 if sys.platform in _default_architecture: 689 b, l = _default_architecture[sys.platform] 690 if b: 691 bits = b 692 if l: 693 linkage = l 694 return bits, linkage 695 696 if 'executable' not in fileout and 'shared object' not in fileout: 697 # Format not supported 698 return bits, linkage 699 700 # Bits 701 if '32-bit' in fileout: 702 bits = '32bit' 703 elif 'N32' in fileout: 704 # On Irix only 705 bits = 'n32bit' 706 elif '64-bit' in fileout: 707 bits = '64bit' 708 709 # Linkage 710 if 'ELF' in fileout: 711 linkage = 'ELF' 712 elif 'PE' in fileout: 713 # E.g. Windows uses this format 714 if 'Windows' in fileout: 715 linkage = 'WindowsPE' 716 else: 717 linkage = 'PE' 718 elif 'COFF' in fileout: 719 linkage = 'COFF' 720 elif 'MS-DOS' in fileout: 721 linkage = 'MSDOS' 722 else: 723 # XXX the A.OUT format also falls under this class... 724 pass 725 726 return bits, linkage 727 728 729def _get_machine_win32(): 730 # Try to use the PROCESSOR_* environment variables 731 # available on Win XP and later; see 732 # http://support.microsoft.com/kb/888731 and 733 # http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM 734 735 # WOW64 processes mask the native architecture 736 return ( 737 os.environ.get('PROCESSOR_ARCHITEW6432', '') or 738 os.environ.get('PROCESSOR_ARCHITECTURE', '') 739 ) 740 741 742class _Processor: 743 @classmethod 744 def get(cls): 745 func = getattr(cls, f'get_{sys.platform}', cls.from_subprocess) 746 return func() or '' 747 748 def get_win32(): 749 return os.environ.get('PROCESSOR_IDENTIFIER', _get_machine_win32()) 750 751 def get_OpenVMS(): 752 try: 753 import vms_lib 754 except ImportError: 755 pass 756 else: 757 csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) 758 return 'Alpha' if cpu_number >= 128 else 'VAX' 759 760 def from_subprocess(): 761 """ 762 Fall back to `uname -p` 763 """ 764 try: 765 return subprocess.check_output( 766 ['uname', '-p'], 767 stderr=subprocess.DEVNULL, 768 text=True, 769 ).strip() 770 except (OSError, subprocess.CalledProcessError): 771 pass 772 773 774def _unknown_as_blank(val): 775 return '' if val == 'unknown' else val 776 777 778### Portable uname() interface 779 780class uname_result( 781 collections.namedtuple( 782 "uname_result_base", 783 "system node release version machine") 784 ): 785 """ 786 A uname_result that's largely compatible with a 787 simple namedtuple except that 'processor' is 788 resolved late and cached to avoid calling "uname" 789 except when needed. 790 """ 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) 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)] 820 821 822_uname_cache = None 823 824 825def 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 914def 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 923def 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 933def 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 942def 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 951def 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 960def 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 1002def _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 1106def 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 1119def 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 1129def 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 1140def 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 1154def 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 1167def 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 1175def 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 1187def 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### Command line interface 1262 1263if __name__ == '__main__': 1264 # Default is to print the aliased verbose platform string 1265 terse = ('terse' in sys.argv or '--terse' in sys.argv) 1266 aliased = (not 'nonaliased' in sys.argv and not '--nonaliased' in sys.argv) 1267 print(platform(aliased, terse)) 1268 sys.exit(0) 1269