1#!/usr/bin/env python3 2 3""" This module tries to retrieve as much platform-identifying data as 4 possible. It makes this information available via function APIs. 5 6 If called from the command line, it prints the platform 7 information concatenated as single string to stdout. The output 8 format is useable as part of a filename. 9 10""" 11# This module is maintained by Marc-Andre Lemburg <mal@egenix.com>. 12# If you find problems, please submit bug reports/patches via the 13# Python bug tracker (http://bugs.python.org) and assign them to "lemburg". 14# 15# Still needed: 16# * support for MS-DOS (PythonDX ?) 17# * support for Amiga and other still unsupported platforms running Python 18# * support for additional Linux distributions 19# 20# Many thanks to all those who helped adding platform-specific 21# checks (in no particular order): 22# 23# Charles G Waldman, David Arnold, Gordon McMillan, Ben Darnell, 24# Jeff Bauer, Cliff Crawford, Ivan Van Laningham, Josef 25# Betancourt, Randall Hopper, Karl Putland, John Farrell, Greg 26# Andruk, Just van Rossum, Thomas Heller, Mark R. Levinson, Mark 27# Hammond, Bill Tutt, Hans Nowak, Uwe Zessin (OpenVMS support), 28# Colin Kong, Trent Mick, Guido van Rossum, Anthony Baxter, Steve 29# Dower 30# 31# History: 32# 33# <see CVS and SVN checkin messages for history> 34# 35# 1.0.8 - changed Windows support to read version from kernel32.dll 36# 1.0.7 - added DEV_NULL 37# 1.0.6 - added linux_distribution() 38# 1.0.5 - fixed Java support to allow running the module on Jython 39# 1.0.4 - added IronPython support 40# 1.0.3 - added normalization of Windows system name 41# 1.0.2 - added more Windows support 42# 1.0.1 - reformatted to make doc.py happy 43# 1.0.0 - reformatted a bit and checked into Python CVS 44# 0.8.0 - added sys.version parser and various new access 45# APIs (python_version(), python_compiler(), etc.) 46# 0.7.2 - fixed architecture() to use sizeof(pointer) where available 47# 0.7.1 - added support for Caldera OpenLinux 48# 0.7.0 - some fixes for WinCE; untabified the source file 49# 0.6.2 - support for OpenVMS - requires version 1.5.2-V006 or higher and 50# vms_lib.getsyi() configured 51# 0.6.1 - added code to prevent 'uname -p' on platforms which are 52# known not to support it 53# 0.6.0 - fixed win32_ver() to hopefully work on Win95,98,NT and Win2k; 54# did some cleanup of the interfaces - some APIs have changed 55# 0.5.5 - fixed another type in the MacOS code... should have 56# used more coffee today ;-) 57# 0.5.4 - fixed a few typos in the MacOS code 58# 0.5.3 - added experimental MacOS support; added better popen() 59# workarounds in _syscmd_ver() -- still not 100% elegant 60# though 61# 0.5.2 - fixed uname() to return '' instead of 'unknown' in all 62# return values (the system uname command tends to return 63# 'unknown' instead of just leaving the field empty) 64# 0.5.1 - included code for slackware dist; added exception handlers 65# to cover up situations where platforms don't have os.popen 66# (e.g. Mac) or fail on socket.gethostname(); fixed libc 67# detection RE 68# 0.5.0 - changed the API names referring to system commands to *syscmd*; 69# added java_ver(); made syscmd_ver() a private 70# API (was system_ver() in previous versions) -- use uname() 71# instead; extended the win32_ver() to also return processor 72# type information 73# 0.4.0 - added win32_ver() and modified the platform() output for WinXX 74# 0.3.4 - fixed a bug in _follow_symlinks() 75# 0.3.3 - fixed popen() and "file" command 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 ints = map(int, l) 243 except ValueError: 244 strings = l 245 else: 246 strings = list(map(str, ints)) 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 263def _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 stderr=subprocess.DEVNULL, 286 text=True, 287 shell=True) 288 except (OSError, subprocess.CalledProcessError) as why: 289 #print('Command %s failed: %s' % (cmd, why)) 290 continue 291 else: 292 break 293 else: 294 return system, release, version 295 296 # Parse the output 297 info = info.strip() 298 m = _ver_output.match(info) 299 if m is not None: 300 system, release, version = m.groups() 301 # Strip trailing dots from version and release 302 if release[-1] == '.': 303 release = release[:-1] 304 if version[-1] == '.': 305 version = version[:-1] 306 # Normalize the version and build strings (eliminating additional 307 # zeros) 308 version = _norm_version(version) 309 return system, release, version 310 311_WIN32_CLIENT_RELEASES = { 312 (5, 0): "2000", 313 (5, 1): "XP", 314 # Strictly, 5.2 client is XP 64-bit, but platform.py historically 315 # has always called it 2003 Server 316 (5, 2): "2003Server", 317 (5, None): "post2003", 318 319 (6, 0): "Vista", 320 (6, 1): "7", 321 (6, 2): "8", 322 (6, 3): "8.1", 323 (6, None): "post8.1", 324 325 (10, 0): "10", 326 (10, None): "post10", 327} 328 329# Server release name lookup will default to client names if necessary 330_WIN32_SERVER_RELEASES = { 331 (5, 2): "2003Server", 332 333 (6, 0): "2008Server", 334 (6, 1): "2008ServerR2", 335 (6, 2): "2012Server", 336 (6, 3): "2012ServerR2", 337 (6, None): "post2012ServerR2", 338} 339 340def win32_is_iot(): 341 return win32_edition() in ('IoTUAP', 'NanoServer', 'WindowsCoreHeadless', 'IoTEdgeOS') 342 343def win32_edition(): 344 try: 345 try: 346 import winreg 347 except ImportError: 348 import _winreg as winreg 349 except ImportError: 350 pass 351 else: 352 try: 353 cvkey = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion' 354 with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, cvkey) as key: 355 return winreg.QueryValueEx(key, 'EditionId')[0] 356 except OSError: 357 pass 358 359 return None 360 361def win32_ver(release='', version='', csd='', ptype=''): 362 try: 363 from sys import getwindowsversion 364 except ImportError: 365 return release, version, csd, ptype 366 367 winver = getwindowsversion() 368 maj, min, build = winver.platform_version or winver[:3] 369 version = '{0}.{1}.{2}'.format(maj, min, build) 370 371 release = (_WIN32_CLIENT_RELEASES.get((maj, min)) or 372 _WIN32_CLIENT_RELEASES.get((maj, None)) or 373 release) 374 375 # getwindowsversion() reflect the compatibility mode Python is 376 # running under, and so the service pack value is only going to be 377 # valid if the versions match. 378 if winver[:2] == (maj, min): 379 try: 380 csd = 'SP{}'.format(winver.service_pack_major) 381 except AttributeError: 382 if csd[:13] == 'Service Pack ': 383 csd = 'SP' + csd[13:] 384 385 # VER_NT_SERVER = 3 386 if getattr(winver, 'product_type', None) == 3: 387 release = (_WIN32_SERVER_RELEASES.get((maj, min)) or 388 _WIN32_SERVER_RELEASES.get((maj, None)) or 389 release) 390 391 try: 392 try: 393 import winreg 394 except ImportError: 395 import _winreg as winreg 396 except ImportError: 397 pass 398 else: 399 try: 400 cvkey = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion' 401 with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, cvkey) as key: 402 ptype = winreg.QueryValueEx(key, 'CurrentType')[0] 403 except OSError: 404 pass 405 406 return release, version, csd, ptype 407 408 409def _mac_ver_xml(): 410 fn = '/System/Library/CoreServices/SystemVersion.plist' 411 if not os.path.exists(fn): 412 return None 413 414 try: 415 import plistlib 416 except ImportError: 417 return None 418 419 with open(fn, 'rb') as f: 420 pl = plistlib.load(f) 421 release = pl['ProductVersion'] 422 versioninfo = ('', '', '') 423 machine = os.uname().machine 424 if machine in ('ppc', 'Power Macintosh'): 425 # Canonical name 426 machine = 'PowerPC' 427 428 return release, versioninfo, machine 429 430 431def mac_ver(release='', versioninfo=('', '', ''), machine=''): 432 433 """ Get macOS version information and return it as tuple (release, 434 versioninfo, machine) with versioninfo being a tuple (version, 435 dev_stage, non_release_version). 436 437 Entries which cannot be determined are set to the parameter values 438 which default to ''. All tuple entries are strings. 439 """ 440 441 # First try reading the information from an XML file which should 442 # always be present 443 info = _mac_ver_xml() 444 if info is not None: 445 return info 446 447 # If that also doesn't work return the default values 448 return release, versioninfo, machine 449 450def _java_getprop(name, default): 451 452 from java.lang import System 453 try: 454 value = System.getProperty(name) 455 if value is None: 456 return default 457 return value 458 except AttributeError: 459 return default 460 461def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')): 462 463 """ Version interface for Jython. 464 465 Returns a tuple (release, vendor, vminfo, osinfo) with vminfo being 466 a tuple (vm_name, vm_release, vm_vendor) and osinfo being a 467 tuple (os_name, os_version, os_arch). 468 469 Values which cannot be determined are set to the defaults 470 given as parameters (which all default to ''). 471 472 """ 473 # Import the needed APIs 474 try: 475 import java.lang 476 except ImportError: 477 return release, vendor, vminfo, osinfo 478 479 vendor = _java_getprop('java.vendor', vendor) 480 release = _java_getprop('java.version', release) 481 vm_name, vm_release, vm_vendor = vminfo 482 vm_name = _java_getprop('java.vm.name', vm_name) 483 vm_vendor = _java_getprop('java.vm.vendor', vm_vendor) 484 vm_release = _java_getprop('java.vm.version', vm_release) 485 vminfo = vm_name, vm_release, vm_vendor 486 os_name, os_version, os_arch = osinfo 487 os_arch = _java_getprop('java.os.arch', os_arch) 488 os_name = _java_getprop('java.os.name', os_name) 489 os_version = _java_getprop('java.os.version', os_version) 490 osinfo = os_name, os_version, os_arch 491 492 return release, vendor, vminfo, osinfo 493 494### System name aliasing 495 496def system_alias(system, release, version): 497 498 """ Returns (system, release, version) aliased to common 499 marketing names used for some systems. 500 501 It also does some reordering of the information in some cases 502 where it would otherwise cause confusion. 503 504 """ 505 if system == 'SunOS': 506 # Sun's OS 507 if release < '5': 508 # These releases use the old name SunOS 509 return system, release, version 510 # Modify release (marketing release = SunOS release - 3) 511 l = release.split('.') 512 if l: 513 try: 514 major = int(l[0]) 515 except ValueError: 516 pass 517 else: 518 major = major - 3 519 l[0] = str(major) 520 release = '.'.join(l) 521 if release < '6': 522 system = 'Solaris' 523 else: 524 # XXX Whatever the new SunOS marketing name is... 525 system = 'Solaris' 526 527 elif system == 'IRIX64': 528 # IRIX reports IRIX64 on platforms with 64-bit support; yet it 529 # is really a version and not a different platform, since 32-bit 530 # apps are also supported.. 531 system = 'IRIX' 532 if version: 533 version = version + ' (64bit)' 534 else: 535 version = '64bit' 536 537 elif system in ('win32', 'win16'): 538 # In case one of the other tricks 539 system = 'Windows' 540 541 # bpo-35516: Don't replace Darwin with macOS since input release and 542 # version arguments can be different than the currently running version. 543 544 return system, release, version 545 546### Various internal helpers 547 548def _platform(*args): 549 550 """ Helper to format the platform string in a filename 551 compatible format e.g. "system-version-machine". 552 """ 553 # Format the platform string 554 platform = '-'.join(x.strip() for x in filter(len, args)) 555 556 # Cleanup some possible filename obstacles... 557 platform = platform.replace(' ', '_') 558 platform = platform.replace('/', '-') 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 566 # No need to report 'unknown' information... 567 platform = platform.replace('unknown', '') 568 569 # Fold '--'s and remove trailing '-' 570 while 1: 571 cleaned = platform.replace('--', '-') 572 if cleaned == platform: 573 break 574 platform = cleaned 575 while platform[-1] == '-': 576 platform = platform[:-1] 577 578 return platform 579 580def _node(default=''): 581 582 """ Helper to determine the node name of this machine. 583 """ 584 try: 585 import socket 586 except ImportError: 587 # No sockets... 588 return default 589 try: 590 return socket.gethostname() 591 except OSError: 592 # Still not working... 593 return default 594 595def _follow_symlinks(filepath): 596 597 """ In case filepath is a symlink, follow it until a 598 real file is reached. 599 """ 600 filepath = os.path.abspath(filepath) 601 while os.path.islink(filepath): 602 filepath = os.path.normpath( 603 os.path.join(os.path.dirname(filepath), os.readlink(filepath))) 604 return filepath 605 606 607def _syscmd_file(target, default=''): 608 609 """ Interface to the system's file command. 610 611 The function uses the -b option of the file command to have it 612 omit the filename in its output. Follow the symlinks. It returns 613 default in case the command should fail. 614 615 """ 616 if sys.platform in ('dos', 'win32', 'win16'): 617 # XXX Others too ? 618 return default 619 620 import subprocess 621 target = _follow_symlinks(target) 622 # "file" output is locale dependent: force the usage of the C locale 623 # to get deterministic behavior. 624 env = dict(os.environ, LC_ALL='C') 625 try: 626 # -b: do not prepend filenames to output lines (brief mode) 627 output = subprocess.check_output(['file', '-b', target], 628 stderr=subprocess.DEVNULL, 629 env=env) 630 except (OSError, subprocess.CalledProcessError): 631 return default 632 if not output: 633 return default 634 # With the C locale, the output should be mostly ASCII-compatible. 635 # Decode from Latin-1 to prevent Unicode decode error. 636 return output.decode('latin-1') 637 638### Information about the used architecture 639 640# Default values for architecture; non-empty strings override the 641# defaults given as parameters 642_default_architecture = { 643 'win32': ('', 'WindowsPE'), 644 'win16': ('', 'Windows'), 645 'dos': ('', 'MSDOS'), 646} 647 648def architecture(executable=sys.executable, bits='', linkage=''): 649 650 """ Queries the given executable (defaults to the Python interpreter 651 binary) for various architecture information. 652 653 Returns a tuple (bits, linkage) which contains information about 654 the bit architecture and the linkage format used for the 655 executable. Both values are returned as strings. 656 657 Values that cannot be determined are returned as given by the 658 parameter presets. If bits is given as '', the sizeof(pointer) 659 (or sizeof(long) on Python version < 1.5.2) is used as 660 indicator for the supported pointer size. 661 662 The function relies on the system's "file" command to do the 663 actual work. This is available on most if not all Unix 664 platforms. On some non-Unix platforms where the "file" command 665 does not exist and the executable is set to the Python interpreter 666 binary defaults from _default_architecture are used. 667 668 """ 669 # Use the sizeof(pointer) as default number of bits if nothing 670 # else is given as default. 671 if not bits: 672 import struct 673 size = struct.calcsize('P') 674 bits = str(size * 8) + 'bit' 675 676 # Get data from the 'file' system command 677 if executable: 678 fileout = _syscmd_file(executable, '') 679 else: 680 fileout = '' 681 682 if not fileout and \ 683 executable == sys.executable: 684 # "file" command did not return anything; we'll try to provide 685 # some sensible defaults then... 686 if sys.platform in _default_architecture: 687 b, l = _default_architecture[sys.platform] 688 if b: 689 bits = b 690 if l: 691 linkage = l 692 return bits, linkage 693 694 if 'executable' not in fileout and 'shared object' not in fileout: 695 # Format not supported 696 return bits, linkage 697 698 # Bits 699 if '32-bit' in fileout: 700 bits = '32bit' 701 elif 'N32' in fileout: 702 # On Irix only 703 bits = 'n32bit' 704 elif '64-bit' in fileout: 705 bits = '64bit' 706 707 # Linkage 708 if 'ELF' in fileout: 709 linkage = 'ELF' 710 elif 'PE' in fileout: 711 # E.g. Windows uses this format 712 if 'Windows' in fileout: 713 linkage = 'WindowsPE' 714 else: 715 linkage = 'PE' 716 elif 'COFF' in fileout: 717 linkage = 'COFF' 718 elif 'MS-DOS' in fileout: 719 linkage = 'MSDOS' 720 else: 721 # XXX the A.OUT format also falls under this class... 722 pass 723 724 return bits, linkage 725 726 727def _get_machine_win32(): 728 # Try to use the PROCESSOR_* environment variables 729 # available on Win XP and later; see 730 # http://support.microsoft.com/kb/888731 and 731 # http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM 732 733 # WOW64 processes mask the native architecture 734 return ( 735 os.environ.get('PROCESSOR_ARCHITEW6432', '') or 736 os.environ.get('PROCESSOR_ARCHITECTURE', '') 737 ) 738 739 740class _Processor: 741 @classmethod 742 def get(cls): 743 func = getattr(cls, f'get_{sys.platform}', cls.from_subprocess) 744 return func() or '' 745 746 def get_win32(): 747 return os.environ.get('PROCESSOR_IDENTIFIER', _get_machine_win32()) 748 749 def get_OpenVMS(): 750 try: 751 import vms_lib 752 except ImportError: 753 pass 754 else: 755 csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) 756 return 'Alpha' if cpu_number >= 128 else 'VAX' 757 758 def from_subprocess(): 759 """ 760 Fall back to `uname -p` 761 """ 762 try: 763 return subprocess.check_output( 764 ['uname', '-p'], 765 stderr=subprocess.DEVNULL, 766 text=True, 767 ).strip() 768 except (OSError, subprocess.CalledProcessError): 769 pass 770 771 772def _unknown_as_blank(val): 773 return '' if val == 'unknown' else val 774 775 776### Portable uname() interface 777 778class uname_result( 779 collections.namedtuple( 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 'platform' is 786 resolved late and cached to avoid calling "uname" 787 except when needed. 788 """ 789 790 @functools.cached_property 791 def processor(self): 792 return _unknown_as_blank(_Processor.get()) 793 794 def __iter__(self): 795 return itertools.chain( 796 super().__iter__(), 797 (self.processor,) 798 ) 799 800 def __getitem__(self, key): 801 return tuple(iter(self))[key] 802 803 def __len__(self): 804 return len(tuple(iter(self))) 805 806 807_uname_cache = None 808 809 810def uname(): 811 812 """ Fairly portable uname interface. Returns a tuple 813 of strings (system, node, release, version, machine, processor) 814 identifying the underlying platform. 815 816 Note that unlike the os.uname function this also returns 817 possible processor information as an additional tuple entry. 818 819 Entries which cannot be determined are set to ''. 820 821 """ 822 global _uname_cache 823 824 if _uname_cache is not None: 825 return _uname_cache 826 827 # Get some infos from the builtin os.uname API... 828 try: 829 system, node, release, version, machine = infos = os.uname() 830 except AttributeError: 831 system = sys.platform 832 node = _node() 833 release = version = machine = '' 834 infos = () 835 836 if not any(infos): 837 # uname is not available 838 839 # Try win32_ver() on win32 platforms 840 if system == 'win32': 841 release, version, csd, ptype = win32_ver() 842 machine = machine or _get_machine_win32() 843 844 # Try the 'ver' system command available on some 845 # platforms 846 if not (release and version): 847 system, release, version = _syscmd_ver(system) 848 # Normalize system to what win32_ver() normally returns 849 # (_syscmd_ver() tends to return the vendor name as well) 850 if system == 'Microsoft Windows': 851 system = 'Windows' 852 elif system == 'Microsoft' and release == 'Windows': 853 # Under Windows Vista and Windows Server 2008, 854 # Microsoft changed the output of the ver command. The 855 # release is no longer printed. This causes the 856 # system and release to be misidentified. 857 system = 'Windows' 858 if '6.0' == version[:3]: 859 release = 'Vista' 860 else: 861 release = '' 862 863 # In case we still don't know anything useful, we'll try to 864 # help ourselves 865 if system in ('win32', 'win16'): 866 if not version: 867 if system == 'win32': 868 version = '32bit' 869 else: 870 version = '16bit' 871 system = 'Windows' 872 873 elif system[:4] == 'java': 874 release, vendor, vminfo, osinfo = java_ver() 875 system = 'Java' 876 version = ', '.join(vminfo) 877 if not version: 878 version = vendor 879 880 # System specific extensions 881 if system == 'OpenVMS': 882 # OpenVMS seems to have release and version mixed up 883 if not release or release == '0': 884 release = version 885 version = '' 886 887 # normalize name 888 if system == 'Microsoft' and release == 'Windows': 889 system = 'Windows' 890 release = 'Vista' 891 892 vals = system, node, release, version, machine 893 # Replace 'unknown' values with the more portable '' 894 _uname_cache = uname_result(*map(_unknown_as_blank, vals)) 895 return _uname_cache 896 897### Direct interfaces to some of the uname() return values 898 899def system(): 900 901 """ Returns the system/OS name, e.g. 'Linux', 'Windows' or 'Java'. 902 903 An empty string is returned if the value cannot be determined. 904 905 """ 906 return uname().system 907 908def node(): 909 910 """ Returns the computer's network name (which may not be fully 911 qualified) 912 913 An empty string is returned if the value cannot be determined. 914 915 """ 916 return uname().node 917 918def release(): 919 920 """ Returns the system's release, e.g. '2.2.0' or 'NT' 921 922 An empty string is returned if the value cannot be determined. 923 924 """ 925 return uname().release 926 927def version(): 928 929 """ Returns the system's release version, e.g. '#3 on degas' 930 931 An empty string is returned if the value cannot be determined. 932 933 """ 934 return uname().version 935 936def machine(): 937 938 """ Returns the machine type, e.g. 'i386' 939 940 An empty string is returned if the value cannot be determined. 941 942 """ 943 return uname().machine 944 945def processor(): 946 947 """ Returns the (true) processor name, e.g. 'amdk6' 948 949 An empty string is returned if the value cannot be 950 determined. Note that many platforms do not provide this 951 information or simply return the same value as for machine(), 952 e.g. NetBSD does this. 953 954 """ 955 return uname().processor 956 957### Various APIs for extracting information from sys.version 958 959_sys_version_parser = re.compile( 960 r'([\w.+]+)\s*' # "version<space>" 961 r'\(#?([^,]+)' # "(#buildno" 962 r'(?:,\s*([\w ]*)' # ", builddate" 963 r'(?:,\s*([\w :]*))?)?\)\s*' # ", buildtime)<space>" 964 r'\[([^\]]+)\]?', re.ASCII) # "[compiler]" 965 966_ironpython_sys_version_parser = re.compile( 967 r'IronPython\s*' 968 r'([\d\.]+)' 969 r'(?: \(([\d\.]+)\))?' 970 r' on (.NET [\d\.]+)', re.ASCII) 971 972# IronPython covering 2.6 and 2.7 973_ironpython26_sys_version_parser = re.compile( 974 r'([\d.]+)\s*' 975 r'\(IronPython\s*' 976 r'[\d.]+\s*' 977 r'\(([\d.]+)\) on ([\w.]+ [\d.]+(?: \(\d+-bit\))?)\)' 978) 979 980_pypy_sys_version_parser = re.compile( 981 r'([\w.+]+)\s*' 982 r'\(#?([^,]+),\s*([\w ]+),\s*([\w :]+)\)\s*' 983 r'\[PyPy [^\]]+\]?') 984 985_sys_version_cache = {} 986 987def _sys_version(sys_version=None): 988 989 """ Returns a parsed version of Python's sys.version as tuple 990 (name, version, branch, revision, buildno, builddate, compiler) 991 referring to the Python implementation name, version, branch, 992 revision, build number, build date/time as string and the compiler 993 identification string. 994 995 Note that unlike the Python sys.version, the returned value 996 for the Python version will always include the patchlevel (it 997 defaults to '.0'). 998 999 The function returns empty strings for tuple entries that 1000 cannot be determined. 1001 1002 sys_version may be given to parse an alternative version 1003 string, e.g. if the version was read from a different Python 1004 interpreter. 1005 1006 """ 1007 # Get the Python version 1008 if sys_version is None: 1009 sys_version = sys.version 1010 1011 # Try the cache first 1012 result = _sys_version_cache.get(sys_version, None) 1013 if result is not None: 1014 return result 1015 1016 # Parse it 1017 if 'IronPython' in sys_version: 1018 # IronPython 1019 name = 'IronPython' 1020 if sys_version.startswith('IronPython'): 1021 match = _ironpython_sys_version_parser.match(sys_version) 1022 else: 1023 match = _ironpython26_sys_version_parser.match(sys_version) 1024 1025 if match is None: 1026 raise ValueError( 1027 'failed to parse IronPython sys.version: %s' % 1028 repr(sys_version)) 1029 1030 version, alt_version, compiler = match.groups() 1031 buildno = '' 1032 builddate = '' 1033 1034 elif sys.platform.startswith('java'): 1035 # Jython 1036 name = 'Jython' 1037 match = _sys_version_parser.match(sys_version) 1038 if match is None: 1039 raise ValueError( 1040 'failed to parse Jython sys.version: %s' % 1041 repr(sys_version)) 1042 version, buildno, builddate, buildtime, _ = match.groups() 1043 if builddate is None: 1044 builddate = '' 1045 compiler = sys.platform 1046 1047 elif "PyPy" in sys_version: 1048 # PyPy 1049 name = "PyPy" 1050 match = _pypy_sys_version_parser.match(sys_version) 1051 if match is None: 1052 raise ValueError("failed to parse PyPy sys.version: %s" % 1053 repr(sys_version)) 1054 version, buildno, builddate, buildtime = match.groups() 1055 compiler = "" 1056 1057 else: 1058 # CPython 1059 match = _sys_version_parser.match(sys_version) 1060 if match is None: 1061 raise ValueError( 1062 'failed to parse CPython sys.version: %s' % 1063 repr(sys_version)) 1064 version, buildno, builddate, buildtime, compiler = \ 1065 match.groups() 1066 name = 'CPython' 1067 if builddate is None: 1068 builddate = '' 1069 elif buildtime: 1070 builddate = builddate + ' ' + buildtime 1071 1072 if hasattr(sys, '_git'): 1073 _, branch, revision = sys._git 1074 elif hasattr(sys, '_mercurial'): 1075 _, branch, revision = sys._mercurial 1076 else: 1077 branch = '' 1078 revision = '' 1079 1080 # Add the patchlevel version if missing 1081 l = version.split('.') 1082 if len(l) == 2: 1083 l.append('0') 1084 version = '.'.join(l) 1085 1086 # Build and cache the result 1087 result = (name, version, branch, revision, buildno, builddate, compiler) 1088 _sys_version_cache[sys_version] = result 1089 return result 1090 1091def python_implementation(): 1092 1093 """ Returns a string identifying the Python implementation. 1094 1095 Currently, the following implementations are identified: 1096 'CPython' (C implementation of Python), 1097 'IronPython' (.NET implementation of Python), 1098 'Jython' (Java implementation of Python), 1099 'PyPy' (Python implementation of Python). 1100 1101 """ 1102 return _sys_version()[0] 1103 1104def python_version(): 1105 1106 """ Returns the Python version as string 'major.minor.patchlevel' 1107 1108 Note that unlike the Python sys.version, the returned value 1109 will always include the patchlevel (it defaults to 0). 1110 1111 """ 1112 return _sys_version()[1] 1113 1114def python_version_tuple(): 1115 1116 """ Returns the Python version as tuple (major, minor, patchlevel) 1117 of strings. 1118 1119 Note that unlike the Python sys.version, the returned value 1120 will always include the patchlevel (it defaults to 0). 1121 1122 """ 1123 return tuple(_sys_version()[1].split('.')) 1124 1125def python_branch(): 1126 1127 """ Returns a string identifying the Python implementation 1128 branch. 1129 1130 For CPython this is the SCM branch from which the 1131 Python binary was built. 1132 1133 If not available, an empty string is returned. 1134 1135 """ 1136 1137 return _sys_version()[2] 1138 1139def python_revision(): 1140 1141 """ Returns a string identifying the Python implementation 1142 revision. 1143 1144 For CPython this is the SCM revision from which the 1145 Python binary was built. 1146 1147 If not available, an empty string is returned. 1148 1149 """ 1150 return _sys_version()[3] 1151 1152def python_build(): 1153 1154 """ Returns a tuple (buildno, builddate) stating the Python 1155 build number and date as strings. 1156 1157 """ 1158 return _sys_version()[4:6] 1159 1160def python_compiler(): 1161 1162 """ Returns a string identifying the compiler used for compiling 1163 Python. 1164 1165 """ 1166 return _sys_version()[6] 1167 1168### The Opus Magnum of platform strings :-) 1169 1170_platform_cache = {} 1171 1172def platform(aliased=0, terse=0): 1173 1174 """ Returns a single string identifying the underlying platform 1175 with as much useful information as possible (but no more :). 1176 1177 The output is intended to be human readable rather than 1178 machine parseable. It may look different on different 1179 platforms and this is intended. 1180 1181 If "aliased" is true, the function will use aliases for 1182 various platforms that report system names which differ from 1183 their common names, e.g. SunOS will be reported as 1184 Solaris. The system_alias() function is used to implement 1185 this. 1186 1187 Setting terse to true causes the function to return only the 1188 absolute minimum information needed to identify the platform. 1189 1190 """ 1191 result = _platform_cache.get((aliased, terse), None) 1192 if result is not None: 1193 return result 1194 1195 # Get uname information and then apply platform specific cosmetics 1196 # to it... 1197 system, node, release, version, machine, processor = uname() 1198 if machine == processor: 1199 processor = '' 1200 if aliased: 1201 system, release, version = system_alias(system, release, version) 1202 1203 if system == 'Darwin': 1204 # macOS (darwin kernel) 1205 macos_release = mac_ver()[0] 1206 if macos_release: 1207 system = 'macOS' 1208 release = macos_release 1209 1210 if system == 'Windows': 1211 # MS platforms 1212 rel, vers, csd, ptype = win32_ver(version) 1213 if terse: 1214 platform = _platform(system, release) 1215 else: 1216 platform = _platform(system, release, version, csd) 1217 1218 elif system in ('Linux',): 1219 # check for libc vs. glibc 1220 libcname, libcversion = libc_ver() 1221 platform = _platform(system, release, machine, processor, 1222 'with', 1223 libcname+libcversion) 1224 elif system == 'Java': 1225 # Java platforms 1226 r, v, vminfo, (os_name, os_version, os_arch) = java_ver() 1227 if terse or not os_name: 1228 platform = _platform(system, release, version) 1229 else: 1230 platform = _platform(system, release, version, 1231 'on', 1232 os_name, os_version, os_arch) 1233 1234 else: 1235 # Generic handler 1236 if terse: 1237 platform = _platform(system, release) 1238 else: 1239 bits, linkage = architecture(sys.executable) 1240 platform = _platform(system, release, machine, 1241 processor, bits, linkage) 1242 1243 _platform_cache[(aliased, terse)] = platform 1244 return platform 1245 1246### Command line interface 1247 1248if __name__ == '__main__': 1249 # Default is to print the aliased verbose platform string 1250 terse = ('terse' in sys.argv or '--terse' in sys.argv) 1251 aliased = (not 'nonaliased' in sys.argv and not '--nonaliased' in sys.argv) 1252 print(platform(aliased, terse)) 1253 sys.exit(0) 1254