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