blob: e9f50ab622d3169d1c46e04d4c27f1ae275f4c33 [file] [log] [blame]
Olivier Deprezf4ef2d02021-04-20 13:36:24 +02001#!/usr/bin/env python3
2
3""" This module tries to retrieve as much platform-identifying data as
4 possible. It makes this information available via function APIs.
5
6 If called from the command line, it prints the platform
7 information concatenated as single string to stdout. The output
8 format is useable as part of a filename.
9
10"""
11# This module is maintained by Marc-Andre Lemburg <mal@egenix.com>.
12# If you find problems, please submit bug reports/patches via the
13# Python bug tracker (http://bugs.python.org) and assign them to "lemburg".
14#
15# Still needed:
16# * support for MS-DOS (PythonDX ?)
17# * support for Amiga and other still unsupported platforms running Python
18# * support for additional Linux distributions
19#
20# Many thanks to all those who helped adding platform-specific
21# checks (in no particular order):
22#
23# Charles G Waldman, David Arnold, Gordon McMillan, Ben Darnell,
24# Jeff Bauer, Cliff Crawford, Ivan Van Laningham, Josef
25# Betancourt, Randall Hopper, Karl Putland, John Farrell, Greg
26# Andruk, Just van Rossum, Thomas Heller, Mark R. Levinson, Mark
27# Hammond, Bill Tutt, Hans Nowak, Uwe Zessin (OpenVMS support),
28# Colin Kong, Trent Mick, Guido van Rossum, Anthony Baxter, Steve
29# Dower
30#
31# History:
32#
33# <see CVS and SVN checkin messages for history>
34#
35# 1.0.8 - changed Windows support to read version from kernel32.dll
36# 1.0.7 - added DEV_NULL
37# 1.0.6 - added linux_distribution()
38# 1.0.5 - fixed Java support to allow running the module on Jython
39# 1.0.4 - added IronPython support
40# 1.0.3 - added normalization of Windows system name
41# 1.0.2 - added more Windows support
42# 1.0.1 - reformatted to make doc.py happy
43# 1.0.0 - reformatted a bit and checked into Python CVS
44# 0.8.0 - added sys.version parser and various new access
45# APIs (python_version(), python_compiler(), etc.)
46# 0.7.2 - fixed architecture() to use sizeof(pointer) where available
47# 0.7.1 - added support for Caldera OpenLinux
48# 0.7.0 - some fixes for WinCE; untabified the source file
49# 0.6.2 - support for OpenVMS - requires version 1.5.2-V006 or higher and
50# vms_lib.getsyi() configured
51# 0.6.1 - added code to prevent 'uname -p' on platforms which are
52# known not to support it
53# 0.6.0 - fixed win32_ver() to hopefully work on Win95,98,NT and Win2k;
54# did some cleanup of the interfaces - some APIs have changed
55# 0.5.5 - fixed another type in the MacOS code... should have
56# used more coffee today ;-)
57# 0.5.4 - fixed a few typos in the MacOS code
58# 0.5.3 - added experimental MacOS support; added better popen()
59# workarounds in _syscmd_ver() -- still not 100% elegant
60# though
61# 0.5.2 - fixed uname() to return '' instead of 'unknown' in all
62# return values (the system uname command tends to return
63# 'unknown' instead of just leaving the field empty)
64# 0.5.1 - included code for slackware dist; added exception handlers
65# to cover up situations where platforms don't have os.popen
66# (e.g. Mac) or fail on socket.gethostname(); fixed libc
67# detection RE
68# 0.5.0 - changed the API names referring to system commands to *syscmd*;
69# added java_ver(); made syscmd_ver() a private
70# API (was system_ver() in previous versions) -- use uname()
71# instead; extended the win32_ver() to also return processor
72# type information
73# 0.4.0 - added win32_ver() and modified the platform() output for WinXX
74# 0.3.4 - fixed a bug in _follow_symlinks()
75# 0.3.3 - fixed popen() and "file" command invocation bugs
76# 0.3.2 - added architecture() API and support for it in platform()
77# 0.3.1 - fixed syscmd_ver() RE to support Windows NT
78# 0.3.0 - added system alias support
79# 0.2.3 - removed 'wince' again... oh well.
80# 0.2.2 - added 'wince' to syscmd_ver() supported platforms
81# 0.2.1 - added cache logic and changed the platform string format
82# 0.2.0 - changed the API to use functions instead of module globals
83# since some action take too long to be run on module import
84# 0.1.0 - first release
85#
86# You can always get the latest version of this module at:
87#
88# http://www.egenix.com/files/python/platform.py
89#
90# If that URL should fail, try contacting the author.
91
92__copyright__ = """
93 Copyright (c) 1999-2000, Marc-Andre Lemburg; mailto:mal@lemburg.com
94 Copyright (c) 2000-2010, eGenix.com Software GmbH; mailto:info@egenix.com
95
96 Permission to use, copy, modify, and distribute this software and its
97 documentation for any purpose and without fee or royalty is hereby granted,
98 provided that the above copyright notice appear in all copies and that
99 both that copyright notice and this permission notice appear in
100 supporting documentation or portions thereof, including modifications,
101 that you make.
102
103 EGENIX.COM SOFTWARE GMBH DISCLAIMS ALL WARRANTIES WITH REGARD TO
104 THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
105 FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
106 INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
107 FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
108 NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
109 WITH THE USE OR PERFORMANCE OF THIS SOFTWARE !
110
111"""
112
113__version__ = '1.0.8'
114
115import collections
116import os
117import re
118import sys
119import subprocess
120import functools
121import itertools
122
123### Globals & Constants
124
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
128
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}
139
140_component_re = re.compile(r'([0-9]+|[._+-])')
141
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
153
154### Platform specific APIs
155
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)
161
162def libc_ver(executable=None, lib='', version='', chunksize=16384):
163
164 """ Tries to determine the libc version that the file executable
165 (which defaults to the Python interpreter) is linked against.
166
167 Returns a tuple of strings (lib,version) which default to the
168 given parameters in case the lookup fails.
169
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.
173
174 The file is read and scanned in chunks of chunksize bytes.
175
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
187
188 executable = sys.executable
189
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
232
233def _norm_version(version, build=''):
234
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 ints = map(int, l)
243 except ValueError:
244 strings = l
245 else:
246 strings = list(map(str, ints))
247 version = '.'.join(strings[:3])
248 return version
249
250_ver_output = re.compile(r'(?:([\w ]+) ([\w.]+) '
251 r'.*'
252 r'\[.* ([\d.]+)\])')
253
254# Examples of VER command output:
255#
256# Windows 2000: Microsoft Windows 2000 [Version 5.00.2195]
257# Windows XP: Microsoft Windows XP [Version 5.1.2600]
258# Windows Vista: Microsoft Windows [Version 6.0.6002]
259#
260# Note that the "Version" string gets localized on different
261# Windows versions.
262
263def _syscmd_ver(system='', release='', version='',
264
265 supported_platforms=('win32', 'win16', 'dos')):
266
267 """ Tries to figure out the OS version used and returns
268 a tuple (system, release, version).
269
270 It uses the "ver" shell command for this which is known
271 to exists on Windows, DOS. XXX Others too ?
272
273 In case this fails, the given parameters are used as
274 defaults.
275
276 """
277 if sys.platform not in supported_platforms:
278 return system, release, version
279
280 # Try some common cmd strings
281 import subprocess
282 for cmd in ('ver', 'command /c ver', 'cmd /c ver'):
283 try:
284 info = subprocess.check_output(cmd,
285 stderr=subprocess.DEVNULL,
286 text=True,
287 shell=True)
288 except (OSError, subprocess.CalledProcessError) as why:
289 #print('Command %s failed: %s' % (cmd, why))
290 continue
291 else:
292 break
293 else:
294 return system, release, version
295
296 # Parse the output
297 info = info.strip()
298 m = _ver_output.match(info)
299 if m is not None:
300 system, release, version = m.groups()
301 # Strip trailing dots from version and release
302 if release[-1] == '.':
303 release = release[:-1]
304 if version[-1] == '.':
305 version = version[:-1]
306 # Normalize the version and build strings (eliminating additional
307 # zeros)
308 version = _norm_version(version)
309 return system, release, version
310
311_WIN32_CLIENT_RELEASES = {
312 (5, 0): "2000",
313 (5, 1): "XP",
314 # Strictly, 5.2 client is XP 64-bit, but platform.py historically
315 # has always called it 2003 Server
316 (5, 2): "2003Server",
317 (5, None): "post2003",
318
319 (6, 0): "Vista",
320 (6, 1): "7",
321 (6, 2): "8",
322 (6, 3): "8.1",
323 (6, None): "post8.1",
324
325 (10, 0): "10",
326 (10, None): "post10",
327}
328
329# Server release name lookup will default to client names if necessary
330_WIN32_SERVER_RELEASES = {
331 (5, 2): "2003Server",
332
333 (6, 0): "2008Server",
334 (6, 1): "2008ServerR2",
335 (6, 2): "2012Server",
336 (6, 3): "2012ServerR2",
337 (6, None): "post2012ServerR2",
338}
339
340def win32_is_iot():
341 return win32_edition() in ('IoTUAP', 'NanoServer', 'WindowsCoreHeadless', 'IoTEdgeOS')
342
343def win32_edition():
344 try:
345 try:
346 import winreg
347 except ImportError:
348 import _winreg as winreg
349 except ImportError:
350 pass
351 else:
352 try:
353 cvkey = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion'
354 with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, cvkey) as key:
355 return winreg.QueryValueEx(key, 'EditionId')[0]
356 except OSError:
357 pass
358
359 return None
360
361def win32_ver(release='', version='', csd='', ptype=''):
362 try:
363 from sys import getwindowsversion
364 except ImportError:
365 return release, version, csd, ptype
366
367 winver = getwindowsversion()
368 maj, min, build = winver.platform_version or winver[:3]
369 version = '{0}.{1}.{2}'.format(maj, min, build)
370
371 release = (_WIN32_CLIENT_RELEASES.get((maj, min)) or
372 _WIN32_CLIENT_RELEASES.get((maj, None)) or
373 release)
374
375 # getwindowsversion() reflect the compatibility mode Python is
376 # running under, and so the service pack value is only going to be
377 # valid if the versions match.
378 if winver[:2] == (maj, min):
379 try:
380 csd = 'SP{}'.format(winver.service_pack_major)
381 except AttributeError:
382 if csd[:13] == 'Service Pack ':
383 csd = 'SP' + csd[13:]
384
385 # VER_NT_SERVER = 3
386 if getattr(winver, 'product_type', None) == 3:
387 release = (_WIN32_SERVER_RELEASES.get((maj, min)) or
388 _WIN32_SERVER_RELEASES.get((maj, None)) or
389 release)
390
391 try:
392 try:
393 import winreg
394 except ImportError:
395 import _winreg as winreg
396 except ImportError:
397 pass
398 else:
399 try:
400 cvkey = r'SOFTWARE\Microsoft\Windows NT\CurrentVersion'
401 with winreg.OpenKeyEx(winreg.HKEY_LOCAL_MACHINE, cvkey) as key:
402 ptype = winreg.QueryValueEx(key, 'CurrentType')[0]
403 except OSError:
404 pass
405
406 return release, version, csd, ptype
407
408
409def _mac_ver_xml():
410 fn = '/System/Library/CoreServices/SystemVersion.plist'
411 if not os.path.exists(fn):
412 return None
413
414 try:
415 import plistlib
416 except ImportError:
417 return None
418
419 with open(fn, 'rb') as f:
420 pl = plistlib.load(f)
421 release = pl['ProductVersion']
422 versioninfo = ('', '', '')
423 machine = os.uname().machine
424 if machine in ('ppc', 'Power Macintosh'):
425 # Canonical name
426 machine = 'PowerPC'
427
428 return release, versioninfo, machine
429
430
431def mac_ver(release='', versioninfo=('', '', ''), machine=''):
432
433 """ Get macOS version information and return it as tuple (release,
434 versioninfo, machine) with versioninfo being a tuple (version,
435 dev_stage, non_release_version).
436
437 Entries which cannot be determined are set to the parameter values
438 which default to ''. All tuple entries are strings.
439 """
440
441 # First try reading the information from an XML file which should
442 # always be present
443 info = _mac_ver_xml()
444 if info is not None:
445 return info
446
447 # If that also doesn't work return the default values
448 return release, versioninfo, machine
449
450def _java_getprop(name, default):
451
452 from java.lang import System
453 try:
454 value = System.getProperty(name)
455 if value is None:
456 return default
457 return value
458 except AttributeError:
459 return default
460
461def java_ver(release='', vendor='', vminfo=('', '', ''), osinfo=('', '', '')):
462
463 """ Version interface for Jython.
464
465 Returns a tuple (release, vendor, vminfo, osinfo) with vminfo being
466 a tuple (vm_name, vm_release, vm_vendor) and osinfo being a
467 tuple (os_name, os_version, os_arch).
468
469 Values which cannot be determined are set to the defaults
470 given as parameters (which all default to '').
471
472 """
473 # Import the needed APIs
474 try:
475 import java.lang
476 except ImportError:
477 return release, vendor, vminfo, osinfo
478
479 vendor = _java_getprop('java.vendor', vendor)
480 release = _java_getprop('java.version', release)
481 vm_name, vm_release, vm_vendor = vminfo
482 vm_name = _java_getprop('java.vm.name', vm_name)
483 vm_vendor = _java_getprop('java.vm.vendor', vm_vendor)
484 vm_release = _java_getprop('java.vm.version', vm_release)
485 vminfo = vm_name, vm_release, vm_vendor
486 os_name, os_version, os_arch = osinfo
487 os_arch = _java_getprop('java.os.arch', os_arch)
488 os_name = _java_getprop('java.os.name', os_name)
489 os_version = _java_getprop('java.os.version', os_version)
490 osinfo = os_name, os_version, os_arch
491
492 return release, vendor, vminfo, osinfo
493
494### System name aliasing
495
496def system_alias(system, release, version):
497
498 """ Returns (system, release, version) aliased to common
499 marketing names used for some systems.
500
501 It also does some reordering of the information in some cases
502 where it would otherwise cause confusion.
503
504 """
505 if system == 'SunOS':
506 # Sun's OS
507 if release < '5':
508 # These releases use the old name SunOS
509 return system, release, version
510 # Modify release (marketing release = SunOS release - 3)
511 l = release.split('.')
512 if l:
513 try:
514 major = int(l[0])
515 except ValueError:
516 pass
517 else:
518 major = major - 3
519 l[0] = str(major)
520 release = '.'.join(l)
521 if release < '6':
522 system = 'Solaris'
523 else:
524 # XXX Whatever the new SunOS marketing name is...
525 system = 'Solaris'
526
527 elif system == 'IRIX64':
528 # IRIX reports IRIX64 on platforms with 64-bit support; yet it
529 # is really a version and not a different platform, since 32-bit
530 # apps are also supported..
531 system = 'IRIX'
532 if version:
533 version = version + ' (64bit)'
534 else:
535 version = '64bit'
536
537 elif system in ('win32', 'win16'):
538 # In case one of the other tricks
539 system = 'Windows'
540
541 # bpo-35516: Don't replace Darwin with macOS since input release and
542 # version arguments can be different than the currently running version.
543
544 return system, release, version
545
546### Various internal helpers
547
548def _platform(*args):
549
550 """ Helper to format the platform string in a filename
551 compatible format e.g. "system-version-machine".
552 """
553 # Format the platform string
554 platform = '-'.join(x.strip() for x in filter(len, args))
555
556 # Cleanup some possible filename obstacles...
557 platform = platform.replace(' ', '_')
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
566 # No need to report 'unknown' information...
567 platform = platform.replace('unknown', '')
568
569 # Fold '--'s and remove trailing '-'
570 while 1:
571 cleaned = platform.replace('--', '-')
572 if cleaned == platform:
573 break
574 platform = cleaned
575 while platform[-1] == '-':
576 platform = platform[:-1]
577
578 return platform
579
580def _node(default=''):
581
582 """ Helper to determine the node name of this machine.
583 """
584 try:
585 import socket
586 except ImportError:
587 # No sockets...
588 return default
589 try:
590 return socket.gethostname()
591 except OSError:
592 # Still not working...
593 return default
594
595def _follow_symlinks(filepath):
596
597 """ In case filepath is a symlink, follow it until a
598 real file is reached.
599 """
600 filepath = os.path.abspath(filepath)
601 while os.path.islink(filepath):
602 filepath = os.path.normpath(
603 os.path.join(os.path.dirname(filepath), os.readlink(filepath)))
604 return filepath
605
606
607def _syscmd_file(target, default=''):
608
609 """ Interface to the system's file command.
610
611 The function uses the -b option of the file command to have it
612 omit the filename in its output. Follow the symlinks. It returns
613 default in case the command should fail.
614
615 """
616 if sys.platform in ('dos', 'win32', 'win16'):
617 # XXX Others too ?
618 return default
619
620 import subprocess
621 target = _follow_symlinks(target)
622 # "file" output is locale dependent: force the usage of the C locale
623 # to get deterministic behavior.
624 env = dict(os.environ, LC_ALL='C')
625 try:
626 # -b: do not prepend filenames to output lines (brief mode)
627 output = subprocess.check_output(['file', '-b', target],
628 stderr=subprocess.DEVNULL,
629 env=env)
630 except (OSError, subprocess.CalledProcessError):
631 return default
632 if not output:
633 return default
634 # With the C locale, the output should be mostly ASCII-compatible.
635 # Decode from Latin-1 to prevent Unicode decode error.
636 return output.decode('latin-1')
637
638### Information about the used architecture
639
640# Default values for architecture; non-empty strings override the
641# defaults given as parameters
642_default_architecture = {
643 'win32': ('', 'WindowsPE'),
644 'win16': ('', 'Windows'),
645 'dos': ('', 'MSDOS'),
646}
647
648def architecture(executable=sys.executable, bits='', linkage=''):
649
650 """ Queries the given executable (defaults to the Python interpreter
651 binary) for various architecture information.
652
653 Returns a tuple (bits, linkage) which contains information about
654 the bit architecture and the linkage format used for the
655 executable. Both values are returned as strings.
656
657 Values that cannot be determined are returned as given by the
658 parameter presets. If bits is given as '', the sizeof(pointer)
659 (or sizeof(long) on Python version < 1.5.2) is used as
660 indicator for the supported pointer size.
661
662 The function relies on the system's "file" command to do the
663 actual work. This is available on most if not all Unix
664 platforms. On some non-Unix platforms where the "file" command
665 does not exist and the executable is set to the Python interpreter
666 binary defaults from _default_architecture are used.
667
668 """
669 # Use the sizeof(pointer) as default number of bits if nothing
670 # else is given as default.
671 if not bits:
672 import struct
673 size = struct.calcsize('P')
674 bits = str(size * 8) + 'bit'
675
676 # Get data from the 'file' system command
677 if executable:
678 fileout = _syscmd_file(executable, '')
679 else:
680 fileout = ''
681
682 if not fileout and \
683 executable == sys.executable:
684 # "file" command did not return anything; we'll try to provide
685 # some sensible defaults then...
686 if sys.platform in _default_architecture:
687 b, l = _default_architecture[sys.platform]
688 if b:
689 bits = b
690 if l:
691 linkage = l
692 return bits, linkage
693
694 if 'executable' not in fileout and 'shared object' not in fileout:
695 # Format not supported
696 return bits, linkage
697
698 # Bits
699 if '32-bit' in fileout:
700 bits = '32bit'
701 elif 'N32' in fileout:
702 # On Irix only
703 bits = 'n32bit'
704 elif '64-bit' in fileout:
705 bits = '64bit'
706
707 # Linkage
708 if 'ELF' in fileout:
709 linkage = 'ELF'
710 elif 'PE' in fileout:
711 # E.g. Windows uses this format
712 if 'Windows' in fileout:
713 linkage = 'WindowsPE'
714 else:
715 linkage = 'PE'
716 elif 'COFF' in fileout:
717 linkage = 'COFF'
718 elif 'MS-DOS' in fileout:
719 linkage = 'MSDOS'
720 else:
721 # XXX the A.OUT format also falls under this class...
722 pass
723
724 return bits, linkage
725
726
727def _get_machine_win32():
728 # Try to use the PROCESSOR_* environment variables
729 # available on Win XP and later; see
730 # http://support.microsoft.com/kb/888731 and
731 # http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM
732
733 # WOW64 processes mask the native architecture
734 return (
735 os.environ.get('PROCESSOR_ARCHITEW6432', '') or
736 os.environ.get('PROCESSOR_ARCHITECTURE', '')
737 )
738
739
740class _Processor:
741 @classmethod
742 def get(cls):
743 func = getattr(cls, f'get_{sys.platform}', cls.from_subprocess)
744 return func() or ''
745
746 def get_win32():
747 return os.environ.get('PROCESSOR_IDENTIFIER', _get_machine_win32())
748
749 def get_OpenVMS():
750 try:
751 import vms_lib
752 except ImportError:
753 pass
754 else:
755 csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0)
756 return 'Alpha' if cpu_number >= 128 else 'VAX'
757
758 def from_subprocess():
759 """
760 Fall back to `uname -p`
761 """
762 try:
763 return subprocess.check_output(
764 ['uname', '-p'],
765 stderr=subprocess.DEVNULL,
766 text=True,
767 ).strip()
768 except (OSError, subprocess.CalledProcessError):
769 pass
770
771
772def _unknown_as_blank(val):
773 return '' if val == 'unknown' else val
774
775
776### Portable uname() interface
777
778class uname_result(
779 collections.namedtuple(
780 "uname_result_base",
781 "system node release version machine")
782 ):
783 """
784 A uname_result that's largely compatible with a
785 simple namedtuple except that 'platform' is
786 resolved late and cached to avoid calling "uname"
787 except when needed.
788 """
789
790 @functools.cached_property
791 def processor(self):
792 return _unknown_as_blank(_Processor.get())
793
794 def __iter__(self):
795 return itertools.chain(
796 super().__iter__(),
797 (self.processor,)
798 )
799
800 def __getitem__(self, key):
801 return tuple(iter(self))[key]
802
803 def __len__(self):
804 return len(tuple(iter(self)))
805
806
807_uname_cache = None
808
809
810def uname():
811
812 """ Fairly portable uname interface. Returns a tuple
813 of strings (system, node, release, version, machine, processor)
814 identifying the underlying platform.
815
816 Note that unlike the os.uname function this also returns
817 possible processor information as an additional tuple entry.
818
819 Entries which cannot be determined are set to ''.
820
821 """
822 global _uname_cache
823
824 if _uname_cache is not None:
825 return _uname_cache
826
827 # Get some infos from the builtin os.uname API...
828 try:
829 system, node, release, version, machine = infos = os.uname()
830 except AttributeError:
831 system = sys.platform
832 node = _node()
833 release = version = machine = ''
834 infos = ()
835
836 if not any(infos):
837 # uname is not available
838
839 # Try win32_ver() on win32 platforms
840 if system == 'win32':
841 release, version, csd, ptype = win32_ver()
842 machine = machine or _get_machine_win32()
843
844 # Try the 'ver' system command available on some
845 # platforms
846 if not (release and version):
847 system, release, version = _syscmd_ver(system)
848 # Normalize system to what win32_ver() normally returns
849 # (_syscmd_ver() tends to return the vendor name as well)
850 if system == 'Microsoft Windows':
851 system = 'Windows'
852 elif system == 'Microsoft' and release == 'Windows':
853 # Under Windows Vista and Windows Server 2008,
854 # Microsoft changed the output of the ver command. The
855 # release is no longer printed. This causes the
856 # system and release to be misidentified.
857 system = 'Windows'
858 if '6.0' == version[:3]:
859 release = 'Vista'
860 else:
861 release = ''
862
863 # In case we still don't know anything useful, we'll try to
864 # help ourselves
865 if system in ('win32', 'win16'):
866 if not version:
867 if system == 'win32':
868 version = '32bit'
869 else:
870 version = '16bit'
871 system = 'Windows'
872
873 elif system[:4] == 'java':
874 release, vendor, vminfo, osinfo = java_ver()
875 system = 'Java'
876 version = ', '.join(vminfo)
877 if not version:
878 version = vendor
879
880 # System specific extensions
881 if system == 'OpenVMS':
882 # OpenVMS seems to have release and version mixed up
883 if not release or release == '0':
884 release = version
885 version = ''
886
887 # normalize name
888 if system == 'Microsoft' and release == 'Windows':
889 system = 'Windows'
890 release = 'Vista'
891
892 vals = system, node, release, version, machine
893 # Replace 'unknown' values with the more portable ''
894 _uname_cache = uname_result(*map(_unknown_as_blank, vals))
895 return _uname_cache
896
897### Direct interfaces to some of the uname() return values
898
899def system():
900
901 """ Returns the system/OS name, e.g. 'Linux', 'Windows' or 'Java'.
902
903 An empty string is returned if the value cannot be determined.
904
905 """
906 return uname().system
907
908def node():
909
910 """ Returns the computer's network name (which may not be fully
911 qualified)
912
913 An empty string is returned if the value cannot be determined.
914
915 """
916 return uname().node
917
918def release():
919
920 """ Returns the system's release, e.g. '2.2.0' or 'NT'
921
922 An empty string is returned if the value cannot be determined.
923
924 """
925 return uname().release
926
927def version():
928
929 """ Returns the system's release version, e.g. '#3 on degas'
930
931 An empty string is returned if the value cannot be determined.
932
933 """
934 return uname().version
935
936def machine():
937
938 """ Returns the machine type, e.g. 'i386'
939
940 An empty string is returned if the value cannot be determined.
941
942 """
943 return uname().machine
944
945def processor():
946
947 """ Returns the (true) processor name, e.g. 'amdk6'
948
949 An empty string is returned if the value cannot be
950 determined. Note that many platforms do not provide this
951 information or simply return the same value as for machine(),
952 e.g. NetBSD does this.
953
954 """
955 return uname().processor
956
957### Various APIs for extracting information from sys.version
958
959_sys_version_parser = re.compile(
960 r'([\w.+]+)\s*' # "version<space>"
961 r'\(#?([^,]+)' # "(#buildno"
962 r'(?:,\s*([\w ]*)' # ", builddate"
963 r'(?:,\s*([\w :]*))?)?\)\s*' # ", buildtime)<space>"
964 r'\[([^\]]+)\]?', re.ASCII) # "[compiler]"
965
966_ironpython_sys_version_parser = re.compile(
967 r'IronPython\s*'
968 r'([\d\.]+)'
969 r'(?: \(([\d\.]+)\))?'
970 r' on (.NET [\d\.]+)', re.ASCII)
971
972# IronPython covering 2.6 and 2.7
973_ironpython26_sys_version_parser = re.compile(
974 r'([\d.]+)\s*'
975 r'\(IronPython\s*'
976 r'[\d.]+\s*'
977 r'\(([\d.]+)\) on ([\w.]+ [\d.]+(?: \(\d+-bit\))?)\)'
978)
979
980_pypy_sys_version_parser = re.compile(
981 r'([\w.+]+)\s*'
982 r'\(#?([^,]+),\s*([\w ]+),\s*([\w :]+)\)\s*'
983 r'\[PyPy [^\]]+\]?')
984
985_sys_version_cache = {}
986
987def _sys_version(sys_version=None):
988
989 """ Returns a parsed version of Python's sys.version as tuple
990 (name, version, branch, revision, buildno, builddate, compiler)
991 referring to the Python implementation name, version, branch,
992 revision, build number, build date/time as string and the compiler
993 identification string.
994
995 Note that unlike the Python sys.version, the returned value
996 for the Python version will always include the patchlevel (it
997 defaults to '.0').
998
999 The function returns empty strings for tuple entries that
1000 cannot be determined.
1001
1002 sys_version may be given to parse an alternative version
1003 string, e.g. if the version was read from a different Python
1004 interpreter.
1005
1006 """
1007 # Get the Python version
1008 if sys_version is None:
1009 sys_version = sys.version
1010
1011 # Try the cache first
1012 result = _sys_version_cache.get(sys_version, None)
1013 if result is not None:
1014 return result
1015
1016 # Parse it
1017 if 'IronPython' in sys_version:
1018 # IronPython
1019 name = 'IronPython'
1020 if sys_version.startswith('IronPython'):
1021 match = _ironpython_sys_version_parser.match(sys_version)
1022 else:
1023 match = _ironpython26_sys_version_parser.match(sys_version)
1024
1025 if match is None:
1026 raise ValueError(
1027 'failed to parse IronPython sys.version: %s' %
1028 repr(sys_version))
1029
1030 version, alt_version, compiler = match.groups()
1031 buildno = ''
1032 builddate = ''
1033
1034 elif sys.platform.startswith('java'):
1035 # Jython
1036 name = 'Jython'
1037 match = _sys_version_parser.match(sys_version)
1038 if match is None:
1039 raise ValueError(
1040 'failed to parse Jython sys.version: %s' %
1041 repr(sys_version))
1042 version, buildno, builddate, buildtime, _ = match.groups()
1043 if builddate is None:
1044 builddate = ''
1045 compiler = sys.platform
1046
1047 elif "PyPy" in sys_version:
1048 # PyPy
1049 name = "PyPy"
1050 match = _pypy_sys_version_parser.match(sys_version)
1051 if match is None:
1052 raise ValueError("failed to parse PyPy sys.version: %s" %
1053 repr(sys_version))
1054 version, buildno, builddate, buildtime = match.groups()
1055 compiler = ""
1056
1057 else:
1058 # CPython
1059 match = _sys_version_parser.match(sys_version)
1060 if match is None:
1061 raise ValueError(
1062 'failed to parse CPython sys.version: %s' %
1063 repr(sys_version))
1064 version, buildno, builddate, buildtime, compiler = \
1065 match.groups()
1066 name = 'CPython'
1067 if builddate is None:
1068 builddate = ''
1069 elif buildtime:
1070 builddate = builddate + ' ' + buildtime
1071
1072 if hasattr(sys, '_git'):
1073 _, branch, revision = sys._git
1074 elif hasattr(sys, '_mercurial'):
1075 _, branch, revision = sys._mercurial
1076 else:
1077 branch = ''
1078 revision = ''
1079
1080 # Add the patchlevel version if missing
1081 l = version.split('.')
1082 if len(l) == 2:
1083 l.append('0')
1084 version = '.'.join(l)
1085
1086 # Build and cache the result
1087 result = (name, version, branch, revision, buildno, builddate, compiler)
1088 _sys_version_cache[sys_version] = result
1089 return result
1090
1091def python_implementation():
1092
1093 """ Returns a string identifying the Python implementation.
1094
1095 Currently, the following implementations are identified:
1096 'CPython' (C implementation of Python),
1097 'IronPython' (.NET implementation of Python),
1098 'Jython' (Java implementation of Python),
1099 'PyPy' (Python implementation of Python).
1100
1101 """
1102 return _sys_version()[0]
1103
1104def python_version():
1105
1106 """ Returns the Python version as string 'major.minor.patchlevel'
1107
1108 Note that unlike the Python sys.version, the returned value
1109 will always include the patchlevel (it defaults to 0).
1110
1111 """
1112 return _sys_version()[1]
1113
1114def python_version_tuple():
1115
1116 """ Returns the Python version as tuple (major, minor, patchlevel)
1117 of strings.
1118
1119 Note that unlike the Python sys.version, the returned value
1120 will always include the patchlevel (it defaults to 0).
1121
1122 """
1123 return tuple(_sys_version()[1].split('.'))
1124
1125def python_branch():
1126
1127 """ Returns a string identifying the Python implementation
1128 branch.
1129
1130 For CPython this is the SCM branch from which the
1131 Python binary was built.
1132
1133 If not available, an empty string is returned.
1134
1135 """
1136
1137 return _sys_version()[2]
1138
1139def python_revision():
1140
1141 """ Returns a string identifying the Python implementation
1142 revision.
1143
1144 For CPython this is the SCM revision from which the
1145 Python binary was built.
1146
1147 If not available, an empty string is returned.
1148
1149 """
1150 return _sys_version()[3]
1151
1152def python_build():
1153
1154 """ Returns a tuple (buildno, builddate) stating the Python
1155 build number and date as strings.
1156
1157 """
1158 return _sys_version()[4:6]
1159
1160def python_compiler():
1161
1162 """ Returns a string identifying the compiler used for compiling
1163 Python.
1164
1165 """
1166 return _sys_version()[6]
1167
1168### The Opus Magnum of platform strings :-)
1169
1170_platform_cache = {}
1171
1172def platform(aliased=0, terse=0):
1173
1174 """ Returns a single string identifying the underlying platform
1175 with as much useful information as possible (but no more :).
1176
1177 The output is intended to be human readable rather than
1178 machine parseable. It may look different on different
1179 platforms and this is intended.
1180
1181 If "aliased" is true, the function will use aliases for
1182 various platforms that report system names which differ from
1183 their common names, e.g. SunOS will be reported as
1184 Solaris. The system_alias() function is used to implement
1185 this.
1186
1187 Setting terse to true causes the function to return only the
1188 absolute minimum information needed to identify the platform.
1189
1190 """
1191 result = _platform_cache.get((aliased, terse), None)
1192 if result is not None:
1193 return result
1194
1195 # Get uname information and then apply platform specific cosmetics
1196 # to it...
1197 system, node, release, version, machine, processor = uname()
1198 if machine == processor:
1199 processor = ''
1200 if aliased:
1201 system, release, version = system_alias(system, release, version)
1202
1203 if system == 'Darwin':
1204 # macOS (darwin kernel)
1205 macos_release = mac_ver()[0]
1206 if macos_release:
1207 system = 'macOS'
1208 release = macos_release
1209
1210 if system == 'Windows':
1211 # MS platforms
1212 rel, vers, csd, ptype = win32_ver(version)
1213 if terse:
1214 platform = _platform(system, release)
1215 else:
1216 platform = _platform(system, release, version, csd)
1217
1218 elif system in ('Linux',):
1219 # check for libc vs. glibc
1220 libcname, libcversion = libc_ver()
1221 platform = _platform(system, release, machine, processor,
1222 'with',
1223 libcname+libcversion)
1224 elif system == 'Java':
1225 # Java platforms
1226 r, v, vminfo, (os_name, os_version, os_arch) = java_ver()
1227 if terse or not os_name:
1228 platform = _platform(system, release, version)
1229 else:
1230 platform = _platform(system, release, version,
1231 'on',
1232 os_name, os_version, os_arch)
1233
1234 else:
1235 # Generic handler
1236 if terse:
1237 platform = _platform(system, release)
1238 else:
1239 bits, linkage = architecture(sys.executable)
1240 platform = _platform(system, release, machine,
1241 processor, bits, linkage)
1242
1243 _platform_cache[(aliased, terse)] = platform
1244 return platform
1245
1246### Command line interface
1247
1248if __name__ == '__main__':
1249 # Default is to print the aliased verbose platform string
1250 terse = ('terse' in sys.argv or '--terse' in sys.argv)
1251 aliased = (not 'nonaliased' in sys.argv and not '--nonaliased' in sys.argv)
1252 print(platform(aliased, terse))
1253 sys.exit(0)