Coverage for /pythoncovmergedfiles/medio/medio/usr/lib/python3.9/platform.py: 2%
521 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-10-20 07:00 +0000
1#! /usr/bin/python3.9
3""" This module tries to retrieve as much platform-identifying data as
4 possible. It makes this information available via function APIs.
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.
10"""
11# This module is maintained by Marc-Andre Lemburg <mal@egenix.com>.
12# If you find problems, please submit bug reports/patches via the
13# Python bug tracker (http://bugs.python.org) and assign them to "lemburg".
14#
15# Still needed:
16# * support for MS-DOS (PythonDX ?)
17# * support for Amiga and other still unsupported platforms running Python
18# * support for additional Linux distributions
19#
20# Many thanks to all those who helped adding platform-specific
21# checks (in no particular order):
22#
23# Charles G Waldman, David Arnold, Gordon McMillan, Ben Darnell,
24# Jeff Bauer, Cliff Crawford, Ivan Van Laningham, Josef
25# Betancourt, Randall Hopper, Karl Putland, John Farrell, Greg
26# Andruk, Just van Rossum, Thomas Heller, Mark R. Levinson, Mark
27# Hammond, Bill Tutt, Hans Nowak, Uwe Zessin (OpenVMS support),
28# Colin Kong, Trent Mick, Guido van Rossum, Anthony Baxter, Steve
29# Dower
30#
31# History:
32#
33# <see CVS and SVN checkin messages for history>
34#
35# 1.0.8 - changed Windows support to read version from kernel32.dll
36# 1.0.7 - added DEV_NULL
37# 1.0.6 - added linux_distribution()
38# 1.0.5 - fixed Java support to allow running the module on Jython
39# 1.0.4 - added IronPython support
40# 1.0.3 - added normalization of Windows system name
41# 1.0.2 - added more Windows support
42# 1.0.1 - reformatted to make doc.py happy
43# 1.0.0 - reformatted a bit and checked into Python CVS
44# 0.8.0 - added sys.version parser and various new access
45# APIs (python_version(), python_compiler(), etc.)
46# 0.7.2 - fixed architecture() to use sizeof(pointer) where available
47# 0.7.1 - added support for Caldera OpenLinux
48# 0.7.0 - some fixes for WinCE; untabified the source file
49# 0.6.2 - support for OpenVMS - requires version 1.5.2-V006 or higher and
50# vms_lib.getsyi() configured
51# 0.6.1 - added code to prevent 'uname -p' on platforms which are
52# known not to support it
53# 0.6.0 - fixed win32_ver() to hopefully work on Win95,98,NT and Win2k;
54# did some cleanup of the interfaces - some APIs have changed
55# 0.5.5 - fixed another type in the MacOS code... should have
56# used more coffee today ;-)
57# 0.5.4 - fixed a few typos in the MacOS code
58# 0.5.3 - added experimental MacOS support; added better popen()
59# workarounds in _syscmd_ver() -- still not 100% elegant
60# though
61# 0.5.2 - fixed uname() to return '' instead of 'unknown' in all
62# return values (the system uname command tends to return
63# 'unknown' instead of just leaving the field empty)
64# 0.5.1 - included code for slackware dist; added exception handlers
65# to cover up situations where platforms don't have os.popen
66# (e.g. Mac) or fail on socket.gethostname(); fixed libc
67# detection RE
68# 0.5.0 - changed the API names referring to system commands to *syscmd*;
69# added java_ver(); made syscmd_ver() a private
70# API (was system_ver() in previous versions) -- use uname()
71# instead; extended the win32_ver() to also return processor
72# type information
73# 0.4.0 - added win32_ver() and modified the platform() output for WinXX
74# 0.3.4 - fixed a bug in _follow_symlinks()
75# 0.3.3 - fixed popen() and "file" command invocation bugs
76# 0.3.2 - added architecture() API and support for it in platform()
77# 0.3.1 - fixed syscmd_ver() RE to support Windows NT
78# 0.3.0 - added system alias support
79# 0.2.3 - removed 'wince' again... oh well.
80# 0.2.2 - added 'wince' to syscmd_ver() supported platforms
81# 0.2.1 - added cache logic and changed the platform string format
82# 0.2.0 - changed the API to use functions instead of module globals
83# since some action take too long to be run on module import
84# 0.1.0 - first release
85#
86# You can always get the latest version of this module at:
87#
88# http://www.egenix.com/files/python/platform.py
89#
90# If that URL should fail, try contacting the author.
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
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.
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 !
111"""
113__version__ = '1.0.8'
115import collections
116import os
117import re
118import sys
119import subprocess
120import functools
121import itertools
123### Globals & Constants
125# Helper for comparing two version number strings.
126# Based on the description of the PHP's version_compare():
127# http://php.net/manual/en/function.version-compare.php
129_ver_stages = {
130 # any string not found in this dict, will get 0 assigned
131 'dev': 10,
132 'alpha': 20, 'a': 20,
133 'beta': 30, 'b': 30,
134 'c': 40,
135 'RC': 50, 'rc': 50,
136 # number, will get 100 assigned
137 'pl': 200, 'p': 200,
138}
140_component_re = re.compile(r'([0-9]+|[._+-])')
142def _comparable_version(version):
143 result = []
144 for v in _component_re.split(version):
145 if v not in '._+-':
146 try:
147 v = int(v, 10)
148 t = 100
149 except ValueError:
150 t = _ver_stages.get(v, 0)
151 result.extend((t, v))
152 return result
154### Platform specific APIs
156_libc_search = re.compile(b'(__libc_init)'
157 b'|'
158 b'(GLIBC_([0-9.]+))'
159 b'|'
160 br'(libc(_\w+)?\.so(?:\.(\d[0-9.]*))?)', re.ASCII)
162def libc_ver(executable=None, lib='', version='', chunksize=16384):
164 """ Tries to determine the libc version that the file executable
165 (which defaults to the Python interpreter) is linked against.
167 Returns a tuple of strings (lib,version) which default to the
168 given parameters in case the lookup fails.
170 Note that the function has intimate knowledge of how different
171 libc versions add symbols to the executable and thus is probably
172 only useable for executables compiled using gcc.
174 The file is read and scanned in chunks of chunksize bytes.
176 """
177 if executable is None:
178 try:
179 ver = os.confstr('CS_GNU_LIBC_VERSION')
180 # parse 'glibc 2.28' as ('glibc', '2.28')
181 parts = ver.split(maxsplit=1)
182 if len(parts) == 2:
183 return tuple(parts)
184 except (AttributeError, ValueError, OSError):
185 # os.confstr() or CS_GNU_LIBC_VERSION value not available
186 pass
188 executable = sys.executable
190 V = _comparable_version
191 if hasattr(os.path, 'realpath'):
192 # Python 2.2 introduced os.path.realpath(); it is used
193 # here to work around problems with Cygwin not being
194 # able to open symlinks for reading
195 executable = os.path.realpath(executable)
196 with open(executable, 'rb') as f:
197 binary = f.read(chunksize)
198 pos = 0
199 while pos < len(binary):
200 if b'libc' in binary or b'GLIBC' in binary:
201 m = _libc_search.search(binary, pos)
202 else:
203 m = None
204 if not m or m.end() == len(binary):
205 chunk = f.read(chunksize)
206 if chunk:
207 binary = binary[max(pos, len(binary) - 1000):] + chunk
208 pos = 0
209 continue
210 if not m:
211 break
212 libcinit, glibc, glibcversion, so, threads, soversion = [
213 s.decode('latin1') if s is not None else s
214 for s in m.groups()]
215 if libcinit and not lib:
216 lib = 'libc'
217 elif glibc:
218 if lib != 'glibc':
219 lib = 'glibc'
220 version = glibcversion
221 elif V(glibcversion) > V(version):
222 version = glibcversion
223 elif so:
224 if lib != 'glibc':
225 lib = 'libc'
226 if soversion and (not version or V(soversion) > V(version)):
227 version = soversion
228 if threads and version[-len(threads):] != threads:
229 version = version + threads
230 pos = m.end()
231 return lib, version
233def _norm_version(version, build=''):
235 """ Normalize the version and build strings and return a single
236 version string using the format major.minor.build (or patchlevel).
237 """
238 l = version.split('.')
239 if build:
240 l.append(build)
241 try:
242 strings = list(map(str, map(int, l)))
243 except ValueError:
244 strings = l
245 version = '.'.join(strings[:3])
246 return version
248_ver_output = re.compile(r'(?:([\w ]+) ([\w.]+) '
249 r'.*'
250 r'\[.* ([\d.]+)\])')
252# Examples of VER command output:
253#
254# Windows 2000: Microsoft Windows 2000 [Version 5.00.2195]
255# Windows XP: Microsoft Windows XP [Version 5.1.2600]
256# Windows Vista: Microsoft Windows [Version 6.0.6002]
257#
258# Note that the "Version" string gets localized on different
259# Windows versions.
261def _syscmd_ver(system='', release='', version='',
263 supported_platforms=('win32', 'win16', 'dos')):
265 """ Tries to figure out the OS version used and returns
266 a tuple (system, release, version).
268 It uses the "ver" shell command for this which is known
269 to exists on Windows, DOS. XXX Others too ?
271 In case this fails, the given parameters are used as
272 defaults.
274 """
275 if sys.platform not in supported_platforms:
276 return system, release, version
278 # Try some common cmd strings
279 import subprocess
280 for cmd in ('ver', 'command /c ver', 'cmd /c ver'):
281 try:
282 info = subprocess.check_output(cmd,
283 stderr=subprocess.DEVNULL,
284 text=True,
285 shell=True)
286 except (OSError, subprocess.CalledProcessError) as why:
287 #print('Command %s failed: %s' % (cmd, why))
288 continue
289 else:
290 break
291 else:
292 return system, release, version
294 # Parse the output
295 info = info.strip()
296 m = _ver_output.match(info)
297 if m is not None:
298 system, release, version = m.groups()
299 # Strip trailing dots from version and release
300 if release[-1] == '.':
301 release = release[:-1]
302 if version[-1] == '.':
303 version = version[:-1]
304 # Normalize the version and build strings (eliminating additional
305 # zeros)
306 version = _norm_version(version)
307 return system, release, version
309_WIN32_CLIENT_RELEASES = {
310 (5, 0): "2000",
311 (5, 1): "XP",
312 # Strictly, 5.2 client is XP 64-bit, but platform.py historically
313 # has always called it 2003 Server
314 (5, 2): "2003Server",
315 (5, None): "post2003",
317 (6, 0): "Vista",
318 (6, 1): "7",
319 (6, 2): "8",
320 (6, 3): "8.1",
321 (6, None): "post8.1",
323 (10, 0): "10",
324 (10, None): "post10",
325}
327# Server release name lookup will default to client names if necessary
328_WIN32_SERVER_RELEASES = {
329 (5, 2): "2003Server",
331 (6, 0): "2008Server",
332 (6, 1): "2008ServerR2",
333 (6, 2): "2012Server",
334 (6, 3): "2012ServerR2",
335 (6, None): "post2012ServerR2",
336}
338def win32_is_iot():
339 return win32_edition() in ('IoTUAP', 'NanoServer', 'WindowsCoreHeadless', 'IoTEdgeOS')
341def win32_edition():
342 try:
343 try:
344 import winreg
345 except ImportError:
346 import _winreg as winreg
347 except ImportError:
348 pass
349 else:
350 try:
351 cvkey = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion'
352 with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, cvkey) as key:
353 return winreg.QueryValueEx(key, 'EditionId')[0]
354 except OSError:
355 pass
357 return None
359def win32_ver(release='', version='', csd='', ptype=''):
360 try:
361 from sys import getwindowsversion
362 except ImportError:
363 return release, version, csd, ptype
365 winver = getwindowsversion()
366 try:
367 major, minor, build = map(int, _syscmd_ver()[2].split('.'))
368 except ValueError:
369 major, minor, build = winver.platform_version or winver[:3]
370 version = '{0}.{1}.{2}'.format(major, minor, build)
372 release = (_WIN32_CLIENT_RELEASES.get((major, minor)) or
373 _WIN32_CLIENT_RELEASES.get((major, None)) or
374 release)
376 # getwindowsversion() reflect the compatibility mode Python is
377 # running under, and so the service pack value is only going to be
378 # valid if the versions match.
379 if winver[:2] == (major, minor):
380 try:
381 csd = 'SP{}'.format(winver.service_pack_major)
382 except AttributeError:
383 if csd[:13] == 'Service Pack ':
384 csd = 'SP' + csd[13:]
386 # VER_NT_SERVER = 3
387 if getattr(winver, 'product_type', None) == 3:
388 release = (_WIN32_SERVER_RELEASES.get((major, minor)) or
389 _WIN32_SERVER_RELEASES.get((major, None)) or
390 release)
392 try:
393 try:
394 import winreg
395 except ImportError:
396 import _winreg as winreg
397 except ImportError:
398 pass
399 else:
400 try:
401 cvkey = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion'
402 with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, cvkey) as key:
403 ptype = winreg.QueryValueEx(key, 'CurrentType')[0]
404 except OSError:
405 pass
407 return release, version, csd, ptype
410def _mac_ver_xml():
411 fn = '/System/Library/CoreServices/SystemVersion.plist'
412 if not os.path.exists(fn):
413 return None
415 try:
416 import plistlib
417 except ImportError:
418 return None
420 with open(fn, 'rb') as f:
421 pl = plistlib.load(f)
422 release = pl['ProductVersion']
423 versioninfo = ('', '', '')
424 machine = os.uname().machine
425 if machine in ('ppc', 'Power Macintosh'):
426 # Canonical name
427 machine = 'PowerPC'
429 return release, versioninfo, machine
432def mac_ver(release='', versioninfo=('', '', ''), machine=''):
434 """ Get macOS version information and return it as tuple (release,
435 versioninfo, machine) with versioninfo being a tuple (version,
436 dev_stage, non_release_version).
438 Entries which cannot be determined are set to the parameter values
439 which default to ''. All tuple entries are strings.
440 """
442 # First try reading the information from an XML file which should
443 # always be present
444 info = _mac_ver_xml()
445 if info is not None:
446 return info
448 # If that also doesn't work return the default values
449 return release, versioninfo, machine
451def _java_getprop(name, default):
453 from java.lang import System
454 try:
455 value = System.getProperty(name)
456 if value is None:
457 return default
458 return value
459 except AttributeError:
460 return default
462def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')):
464 """ Version interface for Jython.
466 Returns a tuple (release, vendor, vminfo, osinfo) with vminfo being
467 a tuple (vm_name, vm_release, vm_vendor) and osinfo being a
468 tuple (os_name, os_version, os_arch).
470 Values which cannot be determined are set to the defaults
471 given as parameters (which all default to '').
473 """
474 # Import the needed APIs
475 try:
476 import java.lang
477 except ImportError:
478 return release, vendor, vminfo, osinfo
480 vendor = _java_getprop('java.vendor', vendor)
481 release = _java_getprop('java.version', release)
482 vm_name, vm_release, vm_vendor = vminfo
483 vm_name = _java_getprop('java.vm.name', vm_name)
484 vm_vendor = _java_getprop('java.vm.vendor', vm_vendor)
485 vm_release = _java_getprop('java.vm.version', vm_release)
486 vminfo = vm_name, vm_release, vm_vendor
487 os_name, os_version, os_arch = osinfo
488 os_arch = _java_getprop('java.os.arch', os_arch)
489 os_name = _java_getprop('java.os.name', os_name)
490 os_version = _java_getprop('java.os.version', os_version)
491 osinfo = os_name, os_version, os_arch
493 return release, vendor, vminfo, osinfo
495### System name aliasing
497def system_alias(system, release, version):
499 """ Returns (system, release, version) aliased to common
500 marketing names used for some systems.
502 It also does some reordering of the information in some cases
503 where it would otherwise cause confusion.
505 """
506 if system == 'SunOS':
507 # Sun's OS
508 if release < '5':
509 # These releases use the old name SunOS
510 return system, release, version
511 # Modify release (marketing release = SunOS release - 3)
512 l = release.split('.')
513 if l:
514 try:
515 major = int(l[0])
516 except ValueError:
517 pass
518 else:
519 major = major - 3
520 l[0] = str(major)
521 release = '.'.join(l)
522 if release < '6':
523 system = 'Solaris'
524 else:
525 # XXX Whatever the new SunOS marketing name is...
526 system = 'Solaris'
528 elif system == 'IRIX64':
529 # IRIX reports IRIX64 on platforms with 64-bit support; yet it
530 # is really a version and not a different platform, since 32-bit
531 # apps are also supported..
532 system = 'IRIX'
533 if version:
534 version = version + ' (64bit)'
535 else:
536 version = '64bit'
538 elif system in ('win32', 'win16'):
539 # In case one of the other tricks
540 system = 'Windows'
542 # bpo-35516: Don't replace Darwin with macOS since input release and
543 # version arguments can be different than the currently running version.
545 return system, release, version
547### Various internal helpers
549def _platform(*args):
551 """ Helper to format the platform string in a filename
552 compatible format e.g. "system-version-machine".
553 """
554 # Format the platform string
555 platform = '-'.join(x.strip() for x in filter(len, args))
557 # Cleanup some possible filename obstacles...
558 platform = platform.replace(' ', '_')
559 platform = platform.replace('/', '-')
560 platform = platform.replace('\\', '-')
561 platform = platform.replace(':', '-')
562 platform = platform.replace(';', '-')
563 platform = platform.replace('"', '-')
564 platform = platform.replace('(', '-')
565 platform = platform.replace(')', '-')
567 # No need to report 'unknown' information...
568 platform = platform.replace('unknown', '')
570 # Fold '--'s and remove trailing '-'
571 while 1:
572 cleaned = platform.replace('--', '-')
573 if cleaned == platform:
574 break
575 platform = cleaned
576 while platform[-1] == '-':
577 platform = platform[:-1]
579 return platform
581def _node(default=''):
583 """ Helper to determine the node name of this machine.
584 """
585 try:
586 import socket
587 except ImportError:
588 # No sockets...
589 return default
590 try:
591 return socket.gethostname()
592 except OSError:
593 # Still not working...
594 return default
596def _follow_symlinks(filepath):
598 """ In case filepath is a symlink, follow it until a
599 real file is reached.
600 """
601 filepath = os.path.abspath(filepath)
602 while os.path.islink(filepath):
603 filepath = os.path.normpath(
604 os.path.join(os.path.dirname(filepath), os.readlink(filepath)))
605 return filepath
608def _syscmd_file(target, default=''):
610 """ Interface to the system's file command.
612 The function uses the -b option of the file command to have it
613 omit the filename in its output. Follow the symlinks. It returns
614 default in case the command should fail.
616 """
617 if sys.platform in ('dos', 'win32', 'win16'):
618 # XXX Others too ?
619 return default
621 import subprocess
622 target = _follow_symlinks(target)
623 # "file" output is locale dependent: force the usage of the C locale
624 # to get deterministic behavior.
625 env = dict(os.environ, LC_ALL='C')
626 try:
627 # -b: do not prepend filenames to output lines (brief mode)
628 output = subprocess.check_output(['file', '-b', target],
629 stderr=subprocess.DEVNULL,
630 env=env)
631 except (OSError, subprocess.CalledProcessError):
632 return default
633 if not output:
634 return default
635 # With the C locale, the output should be mostly ASCII-compatible.
636 # Decode from Latin-1 to prevent Unicode decode error.
637 return output.decode('latin-1')
639### Information about the used architecture
641# Default values for architecture; non-empty strings override the
642# defaults given as parameters
643_default_architecture = {
644 'win32': ('', 'WindowsPE'),
645 'win16': ('', 'Windows'),
646 'dos': ('', 'MSDOS'),
647}
649def architecture(executable=sys.executable, bits='', linkage=''):
651 """ Queries the given executable (defaults to the Python interpreter
652 binary) for various architecture information.
654 Returns a tuple (bits, linkage) which contains information about
655 the bit architecture and the linkage format used for the
656 executable. Both values are returned as strings.
658 Values that cannot be determined are returned as given by the
659 parameter presets. If bits is given as '', the sizeof(pointer)
660 (or sizeof(long) on Python version < 1.5.2) is used as
661 indicator for the supported pointer size.
663 The function relies on the system's "file" command to do the
664 actual work. This is available on most if not all Unix
665 platforms. On some non-Unix platforms where the "file" command
666 does not exist and the executable is set to the Python interpreter
667 binary defaults from _default_architecture are used.
669 """
670 # Use the sizeof(pointer) as default number of bits if nothing
671 # else is given as default.
672 if not bits:
673 import struct
674 size = struct.calcsize('P')
675 bits = str(size * 8) + 'bit'
677 # Get data from the 'file' system command
678 if executable:
679 fileout = _syscmd_file(executable, '')
680 else:
681 fileout = ''
683 if not fileout and \
684 executable == sys.executable:
685 # "file" command did not return anything; we'll try to provide
686 # some sensible defaults then...
687 if sys.platform in _default_architecture:
688 b, l = _default_architecture[sys.platform]
689 if b:
690 bits = b
691 if l:
692 linkage = l
693 return bits, linkage
695 if 'executable' not in fileout and 'shared object' not in fileout:
696 # Format not supported
697 return bits, linkage
699 # Bits
700 if '32-bit' in fileout:
701 bits = '32bit'
702 elif 'N32' in fileout:
703 # On Irix only
704 bits = 'n32bit'
705 elif '64-bit' in fileout:
706 bits = '64bit'
708 # Linkage
709 if 'ELF' in fileout:
710 linkage = 'ELF'
711 elif 'PE' in fileout:
712 # E.g. Windows uses this format
713 if 'Windows' in fileout:
714 linkage = 'WindowsPE'
715 else:
716 linkage = 'PE'
717 elif 'COFF' in fileout:
718 linkage = 'COFF'
719 elif 'MS-DOS' in fileout:
720 linkage = 'MSDOS'
721 else:
722 # XXX the A.OUT format also falls under this class...
723 pass
725 return bits, linkage
728def _get_machine_win32():
729 # Try to use the PROCESSOR_* environment variables
730 # available on Win XP and later; see
731 # http://support.microsoft.com/kb/888731 and
732 # http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM
734 # WOW64 processes mask the native architecture
735 return (
736 os.environ.get('PROCESSOR_ARCHITEW6432', '') or
737 os.environ.get('PROCESSOR_ARCHITECTURE', '')
738 )
741class _Processor:
742 @classmethod
743 def get(cls):
744 func = getattr(cls, f'get_{sys.platform}', cls.from_subprocess)
745 return func() or ''
747 def get_win32():
748 return os.environ.get('PROCESSOR_IDENTIFIER', _get_machine_win32())
750 def get_OpenVMS():
751 try:
752 import vms_lib
753 except ImportError:
754 pass
755 else:
756 csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0)
757 return 'Alpha' if cpu_number >= 128 else 'VAX'
759 def from_subprocess():
760 """
761 Fall back to `uname -p`
762 """
763 try:
764 return subprocess.check_output(
765 ['uname', '-p'],
766 stderr=subprocess.DEVNULL,
767 text=True,
768 ).strip()
769 except (OSError, subprocess.CalledProcessError):
770 pass
773def _unknown_as_blank(val):
774 return '' if val == 'unknown' else val
777### Portable uname() interface
779class uname_result(
780 collections.namedtuple(
781 "uname_result_base",
782 "system node release version machine")
783 ):
784 """
785 A uname_result that's largely compatible with a
786 simple namedtuple except that 'processor' is
787 resolved late and cached to avoid calling "uname"
788 except when needed.
789 """
791 @functools.cached_property
792 def processor(self):
793 return _unknown_as_blank(_Processor.get())
795 def __iter__(self):
796 return itertools.chain(
797 super().__iter__(),
798 (self.processor,)
799 )
801 @classmethod
802 def _make(cls, iterable):
803 # override factory to affect length check
804 num_fields = len(cls._fields)
805 result = cls.__new__(cls, *iterable)
806 if len(result) != num_fields + 1:
807 msg = f'Expected {num_fields} arguments, got {len(result)}'
808 raise TypeError(msg)
809 return result
811 def __getitem__(self, key):
812 return tuple(self)[key]
814 def __len__(self):
815 return len(tuple(iter(self)))
817 def __reduce__(self):
818 return uname_result, tuple(self)[:len(self._fields)]
821_uname_cache = None
824def uname():
826 """ Fairly portable uname interface. Returns a tuple
827 of strings (system, node, release, version, machine, processor)
828 identifying the underlying platform.
830 Note that unlike the os.uname function this also returns
831 possible processor information as an additional tuple entry.
833 Entries which cannot be determined are set to ''.
835 """
836 global _uname_cache
838 if _uname_cache is not None:
839 return _uname_cache
841 # Get some infos from the builtin os.uname API...
842 try:
843 system, node, release, version, machine = infos = os.uname()
844 except AttributeError:
845 system = sys.platform
846 node = _node()
847 release = version = machine = ''
848 infos = ()
850 if not any(infos):
851 # uname is not available
853 # Try win32_ver() on win32 platforms
854 if system == 'win32':
855 release, version, csd, ptype = win32_ver()
856 machine = machine or _get_machine_win32()
858 # Try the 'ver' system command available on some
859 # platforms
860 if not (release and version):
861 system, release, version = _syscmd_ver(system)
862 # Normalize system to what win32_ver() normally returns
863 # (_syscmd_ver() tends to return the vendor name as well)
864 if system == 'Microsoft Windows':
865 system = 'Windows'
866 elif system == 'Microsoft' and release == 'Windows':
867 # Under Windows Vista and Windows Server 2008,
868 # Microsoft changed the output of the ver command. The
869 # release is no longer printed. This causes the
870 # system and release to be misidentified.
871 system = 'Windows'
872 if '6.0' == version[:3]:
873 release = 'Vista'
874 else:
875 release = ''
877 # In case we still don't know anything useful, we'll try to
878 # help ourselves
879 if system in ('win32', 'win16'):
880 if not version:
881 if system == 'win32':
882 version = '32bit'
883 else:
884 version = '16bit'
885 system = 'Windows'
887 elif system[:4] == 'java':
888 release, vendor, vminfo, osinfo = java_ver()
889 system = 'Java'
890 version = ', '.join(vminfo)
891 if not version:
892 version = vendor
894 # System specific extensions
895 if system == 'OpenVMS':
896 # OpenVMS seems to have release and version mixed up
897 if not release or release == '0':
898 release = version
899 version = ''
901 # normalize name
902 if system == 'Microsoft' and release == 'Windows':
903 system = 'Windows'
904 release = 'Vista'
906 vals = system, node, release, version, machine
907 # Replace 'unknown' values with the more portable ''
908 _uname_cache = uname_result(*map(_unknown_as_blank, vals))
909 return _uname_cache
911### Direct interfaces to some of the uname() return values
913def system():
915 """ Returns the system/OS name, e.g. 'Linux', 'Windows' or 'Java'.
917 An empty string is returned if the value cannot be determined.
919 """
920 return uname().system
922def node():
924 """ Returns the computer's network name (which may not be fully
925 qualified)
927 An empty string is returned if the value cannot be determined.
929 """
930 return uname().node
932def release():
934 """ Returns the system's release, e.g. '2.2.0' or 'NT'
936 An empty string is returned if the value cannot be determined.
938 """
939 return uname().release
941def version():
943 """ Returns the system's release version, e.g. '#3 on degas'
945 An empty string is returned if the value cannot be determined.
947 """
948 return uname().version
950def machine():
952 """ Returns the machine type, e.g. 'i386'
954 An empty string is returned if the value cannot be determined.
956 """
957 return uname().machine
959def processor():
961 """ Returns the (true) processor name, e.g. 'amdk6'
963 An empty string is returned if the value cannot be
964 determined. Note that many platforms do not provide this
965 information or simply return the same value as for machine(),
966 e.g. NetBSD does this.
968 """
969 return uname().processor
971### Various APIs for extracting information from sys.version
973_sys_version_parser = re.compile(
974 r'([\w.+]+)\s*' # "version<space>"
975 r'\(#?([^,]+)' # "(#buildno"
976 r'(?:,\s*([\w ]*)' # ", builddate"
977 r'(?:,\s*([\w :]*))?)?\)\s*' # ", buildtime)<space>"
978 r'\[([^\]]+)\]?', re.ASCII) # "[compiler]"
980_ironpython_sys_version_parser = re.compile(
981 r'IronPython\s*'
982 r'([\d\.]+)'
983 r'(?: \(([\d\.]+)\))?'
984 r' on (.NET [\d\.]+)', re.ASCII)
986# IronPython covering 2.6 and 2.7
987_ironpython26_sys_version_parser = re.compile(
988 r'([\d.]+)\s*'
989 r'\(IronPython\s*'
990 r'[\d.]+\s*'
991 r'\(([\d.]+)\) on ([\w.]+ [\d.]+(?: \(\d+-bit\))?)\)'
992)
994_pypy_sys_version_parser = re.compile(
995 r'([\w.+]+)\s*'
996 r'\(#?([^,]+),\s*([\w ]+),\s*([\w :]+)\)\s*'
997 r'\[PyPy [^\]]+\]?')
999_sys_version_cache = {}
1001def _sys_version(sys_version=None):
1003 """ Returns a parsed version of Python's sys.version as tuple
1004 (name, version, branch, revision, buildno, builddate, compiler)
1005 referring to the Python implementation name, version, branch,
1006 revision, build number, build date/time as string and the compiler
1007 identification string.
1009 Note that unlike the Python sys.version, the returned value
1010 for the Python version will always include the patchlevel (it
1011 defaults to '.0').
1013 The function returns empty strings for tuple entries that
1014 cannot be determined.
1016 sys_version may be given to parse an alternative version
1017 string, e.g. if the version was read from a different Python
1018 interpreter.
1020 """
1021 # Get the Python version
1022 if sys_version is None:
1023 sys_version = sys.version
1025 # Try the cache first
1026 result = _sys_version_cache.get(sys_version, None)
1027 if result is not None:
1028 return result
1030 # Parse it
1031 if 'IronPython' in sys_version:
1032 # IronPython
1033 name = 'IronPython'
1034 if sys_version.startswith('IronPython'):
1035 match = _ironpython_sys_version_parser.match(sys_version)
1036 else:
1037 match = _ironpython26_sys_version_parser.match(sys_version)
1039 if match is None:
1040 raise ValueError(
1041 'failed to parse IronPython sys.version: %s' %
1042 repr(sys_version))
1044 version, alt_version, compiler = match.groups()
1045 buildno = ''
1046 builddate = ''
1048 elif sys.platform.startswith('java'):
1049 # Jython
1050 name = 'Jython'
1051 match = _sys_version_parser.match(sys_version)
1052 if match is None:
1053 raise ValueError(
1054 'failed to parse Jython sys.version: %s' %
1055 repr(sys_version))
1056 version, buildno, builddate, buildtime, _ = match.groups()
1057 if builddate is None:
1058 builddate = ''
1059 compiler = sys.platform
1061 elif "PyPy" in sys_version:
1062 # PyPy
1063 name = "PyPy"
1064 match = _pypy_sys_version_parser.match(sys_version)
1065 if match is None:
1066 raise ValueError("failed to parse PyPy sys.version: %s" %
1067 repr(sys_version))
1068 version, buildno, builddate, buildtime = match.groups()
1069 compiler = ""
1071 else:
1072 # CPython
1073 match = _sys_version_parser.match(sys_version)
1074 if match is None:
1075 raise ValueError(
1076 'failed to parse CPython sys.version: %s' %
1077 repr(sys_version))
1078 version, buildno, builddate, buildtime, compiler = \
1079 match.groups()
1080 name = 'CPython'
1081 if builddate is None:
1082 builddate = ''
1083 elif buildtime:
1084 builddate = builddate + ' ' + buildtime
1086 if hasattr(sys, '_git'):
1087 _, branch, revision = sys._git
1088 elif hasattr(sys, '_mercurial'):
1089 _, branch, revision = sys._mercurial
1090 else:
1091 branch = ''
1092 revision = ''
1094 # Add the patchlevel version if missing
1095 l = version.split('.')
1096 if len(l) == 2:
1097 l.append('0')
1098 version = '.'.join(l)
1100 # Build and cache the result
1101 result = (name, version, branch, revision, buildno, builddate, compiler)
1102 _sys_version_cache[sys_version] = result
1103 return result
1105def python_implementation():
1107 """ Returns a string identifying the Python implementation.
1109 Currently, the following implementations are identified:
1110 'CPython' (C implementation of Python),
1111 'IronPython' (.NET implementation of Python),
1112 'Jython' (Java implementation of Python),
1113 'PyPy' (Python implementation of Python).
1115 """
1116 return _sys_version()[0]
1118def python_version():
1120 """ Returns the Python version as string 'major.minor.patchlevel'
1122 Note that unlike the Python sys.version, the returned value
1123 will always include the patchlevel (it defaults to 0).
1125 """
1126 return _sys_version()[1]
1128def python_version_tuple():
1130 """ Returns the Python version as tuple (major, minor, patchlevel)
1131 of strings.
1133 Note that unlike the Python sys.version, the returned value
1134 will always include the patchlevel (it defaults to 0).
1136 """
1137 return tuple(_sys_version()[1].split('.'))
1139def python_branch():
1141 """ Returns a string identifying the Python implementation
1142 branch.
1144 For CPython this is the SCM branch from which the
1145 Python binary was built.
1147 If not available, an empty string is returned.
1149 """
1151 return _sys_version()[2]
1153def python_revision():
1155 """ Returns a string identifying the Python implementation
1156 revision.
1158 For CPython this is the SCM revision from which the
1159 Python binary was built.
1161 If not available, an empty string is returned.
1163 """
1164 return _sys_version()[3]
1166def python_build():
1168 """ Returns a tuple (buildno, builddate) stating the Python
1169 build number and date as strings.
1171 """
1172 return _sys_version()[4:6]
1174def python_compiler():
1176 """ Returns a string identifying the compiler used for compiling
1177 Python.
1179 """
1180 return _sys_version()[6]
1182### The Opus Magnum of platform strings :-)
1184_platform_cache = {}
1186def platform(aliased=0, terse=0):
1188 """ Returns a single string identifying the underlying platform
1189 with as much useful information as possible (but no more :).
1191 The output is intended to be human readable rather than
1192 machine parseable. It may look different on different
1193 platforms and this is intended.
1195 If "aliased" is true, the function will use aliases for
1196 various platforms that report system names which differ from
1197 their common names, e.g. SunOS will be reported as
1198 Solaris. The system_alias() function is used to implement
1199 this.
1201 Setting terse to true causes the function to return only the
1202 absolute minimum information needed to identify the platform.
1204 """
1205 result = _platform_cache.get((aliased, terse), None)
1206 if result is not None:
1207 return result
1209 # Get uname information and then apply platform specific cosmetics
1210 # to it...
1211 system, node, release, version, machine, processor = uname()
1212 if machine == processor:
1213 processor = ''
1214 if aliased:
1215 system, release, version = system_alias(system, release, version)
1217 if system == 'Darwin':
1218 # macOS (darwin kernel)
1219 macos_release = mac_ver()[0]
1220 if macos_release:
1221 system = 'macOS'
1222 release = macos_release
1224 if system == 'Windows':
1225 # MS platforms
1226 rel, vers, csd, ptype = win32_ver(version)
1227 if terse:
1228 platform = _platform(system, release)
1229 else:
1230 platform = _platform(system, release, version, csd)
1232 elif system in ('Linux',):
1233 # check for libc vs. glibc
1234 libcname, libcversion = libc_ver()
1235 platform = _platform(system, release, machine, processor,
1236 'with',
1237 libcname+libcversion)
1238 elif system == 'Java':
1239 # Java platforms
1240 r, v, vminfo, (os_name, os_version, os_arch) = java_ver()
1241 if terse or not os_name:
1242 platform = _platform(system, release, version)
1243 else:
1244 platform = _platform(system, release, version,
1245 'on',
1246 os_name, os_version, os_arch)
1248 else:
1249 # Generic handler
1250 if terse:
1251 platform = _platform(system, release)
1252 else:
1253 bits, linkage = architecture(sys.executable)
1254 platform = _platform(system, release, machine,
1255 processor, bits, linkage)
1257 _platform_cache[(aliased, terse)] = platform
1258 return platform
1260### Command line interface
1262if __name__ == '__main__':
1263 # Default is to print the aliased verbose platform string
1264 terse = ('terse' in sys.argv or '--terse' in sys.argv)
1265 aliased = (not 'nonaliased' in sys.argv and not '--nonaliased' in sys.argv)
1266 print(platform(aliased, terse))
1267 sys.exit(0)