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