Skip to content

bpo-35967 resolve platform.processor late #12239

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Apr 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 93 additions & 79 deletions Lib/platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@
import os
import re
import sys
import subprocess
import functools
import itertools

### Globals & Constants

Expand Down Expand Up @@ -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=''):

Expand Down Expand Up @@ -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
Expand All @@ -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

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 = ''
system = sys.platform
node = _node()
release = version = machine = ''
infos = ()

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)
Expand Down Expand Up @@ -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
Expand Down
9 changes: 2 additions & 7 deletions Lib/test/test_platform.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import sys
import unittest
import collections
import contextlib
from unittest import mock

from test import support
Expand Down Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
In platform, delay the invocation of 'uname -p' until the processor attribute is requested.