From 518835f3354d6672e61c9f52348c1e4a2533ea00 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 16 Apr 2020 08:28:09 -0400 Subject: [PATCH] bpo-35967 resolve platform.processor late (GH-12239) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Replace flag-flip indirection with direct inspection * Use any for simpler code * Avoid flag flip and set results directly. * Resolve processor in a single function. * Extract processor handling into a namespace (class) * Remove _syscmd_uname, unused * Restore platform.processor behavior to match prior expectation (reliant on uname -p in a subprocess). * Extract '_unknown_as_blank' function. * Override uname_result to resolve the processor late. * Add a test intended to capture the expected values from 'uname -p' * 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' * Use a skipIf directive * Use contextlib.suppress to suppress the error. Inline strip call. * 📜🤖 Added by blurb_it. * 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. Co-authored-by: blurb-it[bot] --- Lib/platform.py | 172 ++++++++++-------- Lib/test/test_platform.py | 9 +- .../2019-04-14-14-11-07.bpo-35967.KUMT9E.rst | 1 + 3 files changed, 96 insertions(+), 86 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-04-14-14-11-07.bpo-35967.KUMT9E.rst diff --git a/Lib/platform.py b/Lib/platform.py index ed41edc98fe..3f442ef0fbb 100755 --- a/Lib/platform.py +++ b/Lib/platform.py @@ -116,6 +116,9 @@ import collections import os import re import sys +import subprocess +import functools +import itertools ### Globals & Constants @@ -600,22 +603,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=''): @@ -736,13 +723,89 @@ def architecture(executable=sys.executable, bits='', linkage=''): return bits, linkage + +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', '') + ) + + +class _Processor: + @classmethod + def get(cls): + 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_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 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 + + +def _unknown_as_blank(val): + return '' if val == 'unknown' else 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 + def uname(): """ Fairly portable uname interface. Returns a tuple @@ -756,52 +819,30 @@ def uname(): """ global _uname_cache - no_os_uname = 0 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 = os.uname() + system, node, release, version, machine = infos = os.uname() except AttributeError: - no_os_uname = 1 + system = sys.platform + node = _node() + release = version = machine = '' + infos = () - if no_os_uname or not list(filter(None, (system, node, release, version, machine))): - # 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 = '' - - use_syscmd_ver = 1 + if not any(infos): + # uname is not available # 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 - # 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 - 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) @@ -841,42 +882,15 @@ 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', '') - - #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 + # 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 diff --git a/Lib/test/test_platform.py b/Lib/test/test_platform.py index 63215a06358..855304a68c2 100644 --- a/Lib/test/test_platform.py +++ b/Lib/test/test_platform.py @@ -4,7 +4,6 @@ import subprocess import sys import unittest import collections -import contextlib from unittest import mock from test import support @@ -168,12 +167,8 @@ class PlatformTest(unittest.TestCase): 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") 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 00000000000..38bec77313a --- /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