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