From e2678d998aba6c9869554062d430508c6176d2b2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 7 Mar 2019 19:44:58 -0500 Subject: [PATCH 01/15] Replace flag-flip indirection with direct inspection --- Lib/platform.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 2ab68aed786138..7b437e27158f85 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -758,13 +758,9 @@ def uname(): node = _node() machine = '' - use_syscmd_ver = 1 - # Try win32_ver() on win32 platforms if system == 'win32': release, version, csd, ptype = win32_ver() - if release and version: - use_syscmd_ver = 0 # Try to use the PROCESSOR_* environment variables # available on Win XP and later; see # http://support.microsoft.com/kb/888731 and @@ -780,7 +776,7 @@ def uname(): # Try the 'ver' system command available on some # platforms - if use_syscmd_ver: + if not (release and version): system, release, version = _syscmd_ver(system) # Normalize system to what win32_ver() normally returns # (_syscmd_ver() tends to return the vendor name as well) From 5d93954c09351a8e1408e1cc783ced36208b90db Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 7 Mar 2019 19:56:49 -0500 Subject: [PATCH 02/15] Use any for simpler code --- Lib/platform.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 7b437e27158f85..08893c3302307c 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -744,11 +744,11 @@ def uname(): # Get some infos from the builtin os.uname API... try: - system, node, release, version, machine = os.uname() + system, node, release, version, machine = infos = os.uname() except AttributeError: no_os_uname = 1 - if no_os_uname or not list(filter(None, (system, node, release, version, machine))): + if no_os_uname or not any(infos): # Hmm, no there is either no uname or uname has returned #'unknowns'... we'll have to poke around the system then. if no_os_uname: From df39881bf7b5a09719f972821cff9fc7088d77a6 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 7 Mar 2019 20:45:03 -0500 Subject: [PATCH 03/15] Avoid flag flip and set results directly. --- Lib/platform.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 08893c3302307c..de756a368dbb7e 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -735,7 +735,6 @@ def uname(): """ global _uname_cache - no_os_uname = 0 if _uname_cache is not None: return _uname_cache @@ -746,17 +745,13 @@ def uname(): try: system, node, release, version, machine = infos = os.uname() except AttributeError: - no_os_uname = 1 - - if no_os_uname or not any(infos): - # Hmm, no there is either no uname or uname has returned - #'unknowns'... we'll have to poke around the system then. - if no_os_uname: - system = sys.platform - release = '' - version = '' - node = _node() - machine = '' + system = sys.platform + node = _node() + release = version = machine = '' + infos = () + + if not any(infos): + # uname is not available # Try win32_ver() on win32 platforms if system == 'win32': From da9976a83210d2b0db12379bb819cdb860e67bbc Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 7 Mar 2019 21:23:54 -0500 Subject: [PATCH 04/15] Resolve processor in a single function. --- Lib/platform.py | 81 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index de756a368dbb7e..b41743f039936a 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -116,6 +116,8 @@ import os import re import sys +import subprocess +import contextlib ### Globals & Constants @@ -722,6 +724,53 @@ def architecture(executable=sys.executable, bits='', linkage=''): _uname_cache = None + +def _get_processor_from_cpu_info(): + pattern = re.compile(r'model name\s+: (.*)') + with contextlib.suppress(Exception): + with open('/proc/cpuinfo') as lines: + matched = next(filter(pattern.match, lines)) + return pattern.match(matched).group(1) + + +def _get_machine_win32(): + # Try to use the PROCESSOR_* environment variables + # available on Win XP and later; see + # http://support.microsoft.com/kb/888731 and + # http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM + + # WOW64 processes mask the native architecture + return ( + os.environ.get('PROCESSOR_ARCHITEW6432', '') or + os.environ.get('PROCESSOR_ARCHITECTURE', '') + ) + + +def _get_processor_win32(): + return os.environ.get('PROCESSOR_IDENTIFIER', _get_machine_win32()) + + +def _get_processor_darwin(): + cmd = 'sysctl -n machdep.cpu.brand_string'.split() + return subprocess.check_output(cmd, text=True).strip() + + +def _get_processor(): + func_name = f'_get_processor_{sys.platform}' + func = globals().get(func_name, _get_processor_from_cpu_info) + return func() or '' + + +def _get_processor_OpenVMS(): + try: + import vms_lib + except ImportError: + pass + else: + csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) + return 'Alpha' if cpu_number >= 128 else 'VAX' + + def uname(): """ Fairly portable uname interface. Returns a tuple @@ -739,8 +788,6 @@ def uname(): if _uname_cache is not None: return _uname_cache - processor = '' - # Get some infos from the builtin os.uname API... try: system, node, release, version, machine = infos = os.uname() @@ -756,18 +803,7 @@ def uname(): # Try win32_ver() on win32 platforms if system == 'win32': release, version, csd, ptype = win32_ver() - # Try to use the PROCESSOR_* environment variables - # available on Win XP and later; see - # http://support.microsoft.com/kb/888731 and - # http://www.geocities.com/rick_lively/MANUALS/ENV/MSWIN/PROCESSI.HTM - if not machine: - # WOW64 processes mask the native architecture - if "PROCESSOR_ARCHITEW6432" in os.environ: - machine = os.environ.get("PROCESSOR_ARCHITEW6432", '') - else: - machine = os.environ.get('PROCESSOR_ARCHITECTURE', '') - if not processor: - processor = os.environ.get('PROCESSOR_IDENTIFIER', machine) + machine = machine or _get_machine_win32() # Try the 'ver' system command available on some # platforms @@ -811,20 +847,9 @@ def uname(): if not release or release == '0': release = version version = '' - # Get processor information - try: - import vms_lib - except ImportError: - pass - else: - csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) - if (cpu_number >= 128): - processor = 'Alpha' - else: - processor = 'VAX' - if not processor: - # Get processor information from the uname system command - processor = _syscmd_uname('-p', '') + + # Get processor information + processor = _get_processor() #If any unknowns still exist, replace them with ''s, which are more portable if system == 'unknown': From ba99fb5fb36c0ad53d4523d936ced55778168980 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 7 Mar 2019 21:26:54 -0500 Subject: [PATCH 05/15] Extract processor handling into a namespace (class) --- Lib/platform.py | 64 ++++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index b41743f039936a..b8fdd3f6938f30 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -717,21 +717,6 @@ def architecture(executable=sys.executable, bits='', linkage=''): return bits, linkage -### Portable uname() interface - -uname_result = collections.namedtuple("uname_result", - "system node release version machine processor") - -_uname_cache = None - - -def _get_processor_from_cpu_info(): - pattern = re.compile(r'model name\s+: (.*)') - with contextlib.suppress(Exception): - with open('/proc/cpuinfo') as lines: - matched = next(filter(pattern.match, lines)) - return pattern.match(matched).group(1) - def _get_machine_win32(): # Try to use the PROCESSOR_* environment variables @@ -746,29 +731,42 @@ def _get_machine_win32(): ) -def _get_processor_win32(): - return os.environ.get('PROCESSOR_IDENTIFIER', _get_machine_win32()) +class _Processor: + @classmethod + def get(cls): + func = getattr(cls, f'get_{sys.platform}', cls.from_cpu_info) + return func() or '' + + def get_win32(): + return os.environ.get('PROCESSOR_IDENTIFIER', _get_machine_win32()) + + def get_darwin(): + cmd = 'sysctl -n machdep.cpu.brand_string'.split() + return subprocess.check_output(cmd, text=True).strip() + def get_OpenVMS(): + try: + import vms_lib + except ImportError: + pass + else: + csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) + return 'Alpha' if cpu_number >= 128 else 'VAX' -def _get_processor_darwin(): - cmd = 'sysctl -n machdep.cpu.brand_string'.split() - return subprocess.check_output(cmd, text=True).strip() + def from_cpu_info(): + pattern = re.compile(r'model name\s+: (.*)') + with contextlib.suppress(Exception): + with open('/proc/cpuinfo') as lines: + matched = next(filter(pattern.match, lines)) + return pattern.match(matched).group(1) -def _get_processor(): - func_name = f'_get_processor_{sys.platform}' - func = globals().get(func_name, _get_processor_from_cpu_info) - return func() or '' +### Portable uname() interface +uname_result = collections.namedtuple("uname_result", + "system node release version machine processor") -def _get_processor_OpenVMS(): - try: - import vms_lib - except ImportError: - pass - else: - csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) - return 'Alpha' if cpu_number >= 128 else 'VAX' +_uname_cache = None def uname(): @@ -849,7 +847,7 @@ def uname(): version = '' # Get processor information - processor = _get_processor() + processor = _Processor.get() #If any unknowns still exist, replace them with ''s, which are more portable if system == 'unknown': From 1385129d25acd398e384a1d32a12077ed7bcd3a8 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 7 Mar 2019 21:38:09 -0500 Subject: [PATCH 06/15] Remove _syscmd_uname, unused --- Lib/platform.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index b8fdd3f6938f30..81d9a00006b767 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -581,22 +581,6 @@ def _follow_symlinks(filepath): os.path.join(os.path.dirname(filepath), os.readlink(filepath))) return filepath -def _syscmd_uname(option, default=''): - - """ Interface to the system's uname command. - """ - if sys.platform in ('dos', 'win32', 'win16'): - # XXX Others too ? - return default - - import subprocess - try: - output = subprocess.check_output(('uname', option), - stderr=subprocess.DEVNULL, - text=True) - except (OSError, subprocess.CalledProcessError): - return default - return (output.strip() or default) def _syscmd_file(target, default=''): From 9a628c4aeba7c174f3dbf829e7c84354880101f7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 Mar 2019 12:07:17 -0500 Subject: [PATCH 07/15] Restore platform.processor behavior to match prior expectation (reliant on uname -p in a subprocess). --- Lib/platform.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 81d9a00006b767..2e76a81d9ab4bf 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -718,16 +718,12 @@ def _get_machine_win32(): class _Processor: @classmethod def get(cls): - func = getattr(cls, f'get_{sys.platform}', cls.from_cpu_info) + func = getattr(cls, f'get_{sys.platform}', cls.from_subprocess) return func() or '' def get_win32(): return os.environ.get('PROCESSOR_IDENTIFIER', _get_machine_win32()) - def get_darwin(): - cmd = 'sysctl -n machdep.cpu.brand_string'.split() - return subprocess.check_output(cmd, text=True).strip() - def get_OpenVMS(): try: import vms_lib @@ -737,12 +733,18 @@ def get_OpenVMS(): csid, cpu_number = vms_lib.getsyi('SYI$_CPU', 0) return 'Alpha' if cpu_number >= 128 else 'VAX' - def from_cpu_info(): - pattern = re.compile(r'model name\s+: (.*)') - with contextlib.suppress(Exception): - with open('/proc/cpuinfo') as lines: - matched = next(filter(pattern.match, lines)) - return pattern.match(matched).group(1) + def from_subprocess(): + """ + Fall back to `uname -p` + """ + try: + return subprocess.check_output( + ['uname', '-p'], + stderr=subprocess.DEVNULL, + text=True, + ).strip() + except (OSError, subprocess.CalledProcessError): + pass ### Portable uname() interface From 3977f638be7c2e124c3716dcd7a8101605af2906 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 Mar 2019 12:29:59 -0500 Subject: [PATCH 08/15] Extract '_unknown_as_blank' function. --- Lib/platform.py | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index 2e76a81d9ab4bf..d353e6462bb742 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -747,6 +747,10 @@ def from_subprocess(): pass +def _unknown_as_blank(val): + return '' if val == 'unknown' else val + + ### Portable uname() interface uname_result = collections.namedtuple("uname_result", @@ -835,27 +839,14 @@ def uname(): # Get processor information processor = _Processor.get() - #If any unknowns still exist, replace them with ''s, which are more portable - if system == 'unknown': - system = '' - if node == 'unknown': - node = '' - if release == 'unknown': - release = '' - if version == 'unknown': - version = '' - if machine == 'unknown': - machine = '' - if processor == 'unknown': - processor = '' - # normalize name if system == 'Microsoft' and release == 'Windows': system = 'Windows' release = 'Vista' - _uname_cache = uname_result(system, node, release, version, - machine, processor) + vals = system, node, release, version, machine, processor + # Replace 'unknown' values with the more portable '' + _uname_cache = uname_result(*map(_unknown_as_blank, vals)) return _uname_cache ### Direct interfaces to some of the uname() return values From acd024e2d4aa56f13d7bc165d10a35510e83a12b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 8 Mar 2019 12:24:17 -0500 Subject: [PATCH 09/15] Override uname_result to resolve the processor late. --- Lib/platform.py | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/Lib/platform.py b/Lib/platform.py index d353e6462bb742..33bbfbf52bf1a7 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -117,7 +117,8 @@ import re import sys import subprocess -import contextlib +import functools +import itertools ### Globals & Constants @@ -753,8 +754,33 @@ def _unknown_as_blank(val): ### Portable uname() interface -uname_result = collections.namedtuple("uname_result", - "system node release version machine processor") +class uname_result( + collections.namedtuple( + "uname_result_base", + "system node release version machine") + ): + """ + A uname_result that's largely compatible with a + simple namedtuple except that 'platform' is + resolved late and cached to avoid calling "uname" + except when needed. + """ + + @functools.cached_property + def processor(self): + return _unknown_as_blank(_Processor.get()) + + def __iter__(self): + return itertools.chain( + super().__iter__(), + (self.processor,) + ) + + def __getitem__(self, key): + if key == 5: + return self.processor + return super().__getitem__(key) + _uname_cache = None @@ -836,15 +862,12 @@ def uname(): release = version version = '' - # Get processor information - processor = _Processor.get() - # normalize name if system == 'Microsoft' and release == 'Windows': system = 'Windows' release = 'Vista' - vals = system, node, release, version, machine, processor + vals = system, node, release, version, machine # Replace 'unknown' values with the more portable '' _uname_cache = uname_result(*map(_unknown_as_blank, vals)) return _uname_cache From 9aacda974a322d76de9139f86eced861fca7bde0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 13 Apr 2019 21:14:53 -0400 Subject: [PATCH 10/15] Add a test intended to capture the expected values from 'uname -p' --- Lib/test/test_platform.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 9cf17726d92e0d..7e9acad2df5ce4 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -5,6 +5,7 @@ import sysconfig import tempfile import unittest +import collections from unittest import mock from test import support @@ -189,6 +190,13 @@ def test_uname(self): self.assertEqual(res[4], res.machine) self.assertEqual(res[5], res.processor) + def test_uname_processor(self): + expected = collections.defaultdict( + set, + Darwin={'i386'}, + )[platform.system()] + self.assertIn(platform.uname().processor, expected) + @unittest.skipUnless(sys.platform.startswith('win'), "windows only test") def test_uname_win32_ARCHITEW6432(self): # Issue 7860: make sure we get architecture from the correct variable From aa297014e2e88d0c1296ebc10d2209b9ae6ca2b3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 14 Apr 2019 09:42:18 -0400 Subject: [PATCH 11/15] Instead of trying to keep track of all of the possible outputs on different systems (probably a fool's errand), simply assert that except for the known platform variance, uname().processor matches the output of 'uname -p' --- Lib/test/test_platform.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 7e9acad2df5ce4..feec75bd86fb95 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -191,11 +191,20 @@ def test_uname(self): self.assertEqual(res[5], res.processor) def test_uname_processor(self): - expected = collections.defaultdict( - set, - Darwin={'i386'}, - )[platform.system()] - self.assertIn(platform.uname().processor, expected) + """ + On some systems, the processor must match the output + of 'uname -p'. + """ + if sys.platform in ['win32', 'OpenVMS']: + return + + try: + output = subprocess.check_output(['uname', '-p'], text=True) + except subprocess.CalledProcessError: + return + + expected = output.strip() + self.assertEqual(platform.uname().processor, expected) @unittest.skipUnless(sys.platform.startswith('win'), "windows only test") def test_uname_win32_ARCHITEW6432(self): From 01ac684f72eb409337ef838489f75dd08990aef0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 14 Apr 2019 09:48:51 -0400 Subject: [PATCH 12/15] Use a skipIf directive --- Lib/test/test_platform.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index feec75bd86fb95..7e480aa09d7418 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -190,14 +190,12 @@ def test_uname(self): self.assertEqual(res[4], res.machine) self.assertEqual(res[5], res.processor) + @unittest.skipIf(sys.platform in ['win32', 'OpenVMS'], "uname -p not used") def test_uname_processor(self): """ On some systems, the processor must match the output - of 'uname -p'. + of 'uname -p'. See Issue 35967 for rationale. """ - if sys.platform in ['win32', 'OpenVMS']: - return - try: output = subprocess.check_output(['uname', '-p'], text=True) except subprocess.CalledProcessError: From 9df8602a562a2a30510d6451ff469635b350167d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 14 Apr 2019 09:52:39 -0400 Subject: [PATCH 13/15] Use contextlib.suppress to suppress the error. Inline strip call. --- Lib/test/test_platform.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 7e480aa09d7418..8541655e2d0bd0 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -6,6 +6,7 @@ import tempfile import unittest import collections +import contextlib from unittest import mock from test import support @@ -196,13 +197,11 @@ def test_uname_processor(self): On some systems, the processor must match the output of 'uname -p'. See Issue 35967 for rationale. """ - try: - output = subprocess.check_output(['uname', '-p'], text=True) - except subprocess.CalledProcessError: - return - - expected = output.strip() - self.assertEqual(platform.uname().processor, expected) + with contextlib.suppress(subprocess.CalledProcessError): + self.assertEqual( + platform.uname().processor, + subprocess.check_output(['uname', '-p'], text=True).strip(), + ) @unittest.skipUnless(sys.platform.startswith('win'), "windows only test") def test_uname_win32_ARCHITEW6432(self): From ef4662d9ae6e2408401c9a86ecff5ac23bbca274 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" Date: Sun, 14 Apr 2019 14:11:09 +0000 Subject: [PATCH 14/15] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NEWS.d/next/Library/2019-04-14-14-11-07.bpo-35967.KUMT9E.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2019-04-14-14-11-07.bpo-35967.KUMT9E.rst diff --git a/Misc/NEWS.d/next/Library/2019-04-14-14-11-07.bpo-35967.KUMT9E.rst b/Misc/NEWS.d/next/Library/2019-04-14-14-11-07.bpo-35967.KUMT9E.rst new file mode 100644 index 00000000000000..38bec77313ac04 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-04-14-14-11-07.bpo-35967.KUMT9E.rst @@ -0,0 +1 @@ +In platform, delay the invocation of 'uname -p' until the processor attribute is requested. \ No newline at end of file From d57e3abcf0f923b88776337f9651c29428f2e16d Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Wed, 15 Apr 2020 20:37:24 -0400 Subject: [PATCH 15/15] Remove use of contextlib.suppress (it would fail with NameError if it had any effect). Rely on _unknown_as_blank to replace unknown with blank. --- Lib/test/test_platform.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 63215a06358b61..855304a68c2040 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -4,7 +4,6 @@ import sys import unittest import collections -import contextlib from unittest import mock from test import support @@ -168,12 +167,8 @@ def test_uname_processor(self): On some systems, the processor must match the output of 'uname -p'. See Issue 35967 for rationale. """ - with contextlib.suppress(subprocess.CalledProcessError): - expect = subprocess.check_output(['uname', '-p'], text=True).strip() - - if expect == 'unknown': - expect = '' - + proc_res = subprocess.check_output(['uname', '-p'], text=True).strip() + expect = platform._unknown_as_blank(proc_res) self.assertEqual(platform.uname().processor, expect) @unittest.skipUnless(sys.platform.startswith('win'), "windows only test")