Skip to content

Commit c7a8e5c

Browse files
committed
Remove dependence on PSUtil
- Use native ctypes for parent process traversal - Addresses issues raised in pypa/pipenv#1587 and microsoft/vscode-python#978 - Improves speed on windows - Allows pipenv to remove vendored psutil which sometimes fails to find linked python dlls
1 parent d795447 commit c7a8e5c

File tree

5 files changed

+110
-8
lines changed

5 files changed

+110
-8
lines changed

Pipfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ name = "pypi"
88
virtualenv = "==1.11.6"
99
"virtualenv-clone" = "==0.2.5"
1010
"pythonz-bd" = {"version" = "*", "sys_platform" = "!='win32'" }
11-
"psutil" = {"version" = "*", "sys_platform" = "=='win32'" }
1211

1312
[dev-packages]
1413

pew/_win_utils.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# -*- coding=utf-8 -*-
2+
# psutil is painfully slow in win32. So to avoid adding big
3+
# dependencies like pywin32 a ctypes based solution is preferred
4+
5+
# Code based on the winappdbg project http://winappdbg.sourceforge.net/
6+
# (BSD License) - adapted from Celery
7+
# https://github.com/celery/celery/blob/2.5-archived/celery/concurrency/processes/_win.py
8+
import os
9+
from ctypes import (
10+
byref, sizeof, windll, Structure, WinError, POINTER,
11+
c_size_t, c_char, c_void_p
12+
)
13+
from ctypes.wintypes import DWORD, LONG
14+
15+
ERROR_NO_MORE_FILES = 18
16+
INVALID_HANDLE_VALUE = c_void_p(-1).value
17+
18+
19+
class PROCESSENTRY32(Structure):
20+
_fields_ = [
21+
('dwSize', DWORD),
22+
('cntUsage', DWORD),
23+
('th32ProcessID', DWORD),
24+
('th32DefaultHeapID', c_size_t),
25+
('th32ModuleID', DWORD),
26+
('cntThreads', DWORD),
27+
('th32ParentProcessID', DWORD),
28+
('pcPriClassBase', LONG),
29+
('dwFlags', DWORD),
30+
('szExeFile', c_char * 260),
31+
]
32+
33+
34+
LPPROCESSENTRY32 = POINTER(PROCESSENTRY32)
35+
36+
37+
def CreateToolhelp32Snapshot(dwFlags=2, th32ProcessID=0):
38+
hSnapshot = windll.kernel32.CreateToolhelp32Snapshot(
39+
dwFlags,
40+
th32ProcessID
41+
)
42+
if hSnapshot == INVALID_HANDLE_VALUE:
43+
raise WinError()
44+
return hSnapshot
45+
46+
47+
def Process32First(hSnapshot):
48+
pe = PROCESSENTRY32()
49+
pe.dwSize = sizeof(PROCESSENTRY32)
50+
success = windll.kernel32.Process32First(hSnapshot, byref(pe))
51+
if not success:
52+
if windll.kernel32.GetLastError() == ERROR_NO_MORE_FILES:
53+
return
54+
raise WinError()
55+
return pe
56+
57+
58+
def Process32Next(hSnapshot, pe=None):
59+
if pe is None:
60+
pe = PROCESSENTRY32()
61+
pe.dwSize = sizeof(PROCESSENTRY32)
62+
success = windll.kernel32.Process32Next(hSnapshot, byref(pe))
63+
if not success:
64+
if windll.kernel32.GetLastError() == ERROR_NO_MORE_FILES:
65+
return
66+
raise WinError()
67+
return pe
68+
69+
70+
def get_all_processes():
71+
"""Return a dictionary of properties about all processes.
72+
73+
>>> get_all_processes()
74+
{
75+
1509: {
76+
'parent_pid': 1201,
77+
'executable': 'C:\\Program\\\\ Files\\Python36\\python.exe'
78+
}
79+
}
80+
"""
81+
h_process = CreateToolhelp32Snapshot()
82+
pids = {}
83+
pe = Process32First(h_process)
84+
while pe:
85+
pids[pe.th32ProcessID] = {
86+
'executable': '{0}'.format(pe.szExeFile)
87+
}
88+
if pe.th32ParentProcessID:
89+
pids[pe.th32ProcessID]['parent_pid'] = pe.th32ParentProcessID
90+
pe = Process32Next(h_process, pe)
91+
92+
return pids
93+
94+
95+
def get_grandparent_process(pid=None):
96+
"""Get grandparent process name of the supplied pid or os.getpid().
97+
98+
:param int pid: The pid to track.
99+
:return: Name of the grandparent process.
100+
"""
101+
if not pid:
102+
pid = os.getpid()
103+
processes = get_all_processes()
104+
ppid = processes[pid]['parent_pid']
105+
parent = processes[ppid]
106+
grandparent = processes[parent['parent_pid']]
107+
return grandparent['executable']

pew/pew.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
InstallCommand = ListPythons = LocatePython = UninstallCommand = \
3737
lambda : sys.exit('Command not supported on this platform')
3838

39-
import psutil
39+
from ._win_utils import get_grandparent_process
4040

4141
from pew._utils import (check_call, invoke, expandpath, own, env_bin_dir,
4242
check_path, temp_environ, NamedTemporaryFile, to_unicode)
@@ -184,7 +184,7 @@ def _detect_shell():
184184
if 'CMDER_ROOT' in os.environ:
185185
shell = 'Cmder'
186186
elif windows:
187-
shell = psutil.Process(os.getpid()).parent().parent().name()
187+
shell = get_grandparent_process(os.getpid())
188188
else:
189189
shell = 'sh'
190190
return shell

requirements.txt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,4 @@ virtualenv==1.11.6
33
virtualenv-clone==0.2.5
44
pytest==2.6.2
55
pythonz-bd==1.11.2 ; sys_platform != 'win32'
6-
psutil==5.3.1 ; sys_platform == 'win32'
7-
stdeb ; sys_platform == 'linux'
6+
stdeb ; sys_platform == 'linux'

setup.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,6 @@ def run(self):
7575
':python_version=="3.3"': [
7676
'pathlib'
7777
],
78-
':sys_platform=="win32"': [
79-
'psutil==5.3.1'
80-
],
8178
'pythonz': [
8279
'pythonz-bd>=1.10.2'
8380
]

0 commit comments

Comments
 (0)