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