8000 Makes uninstall --purge also remove PATH entry. (#112) · python/pymanager@f44e097 · GitHub
[go: up one dir, main page]

Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f44e097

Browse files
authored
Makes uninstall --purge also remove PATH entry. (#112)
Fixes #95
1 parent a2ccd85 commit f44e097

File tree

3 files changed

+107
-25
lines changed

3 files changed

+107
-25
lines changed

src/manage/uninstall_command.py

Lines changed: 80 additions & 25 deletions
10000
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,75 @@
33
from .installs import get_matching_install_tags
44
from .install_command import SHORTCUT_HANDLERS, update_all_shortcuts
55
from .logging import LOGGER
6-
from .pathutils import PurePath
6+
from .pathutils import Path, PurePath
77
from .tagutils import tag_or_range
88

99

1010
def _iterdir(p, only_files=False):
1111
try:
1212
if only_files:
13-
return [f for f in p.iterdir() if p.is_file()]
14-
return list(p.iterdir())
13+
return [f for f in Path(p).iterdir() if f.is_file()]
14+
return list(Path(p).iterdir())
1515
except FileNotFoundError:
1616
LOGGER.debug("Skipping %s because it does not exist", p)
1717
return []
1818

1919

20+
def _do_purge_global_dir(global_dir, warn_msg, *, hive=None, subkey="Environment"):
21+
import os
22+
import winreg
23+
24+
if hive is None:
25+
hive = winreg.HKEY_CURRENT_USER
26+
try:
27+
with winreg.OpenKeyEx(hive, subkey) as key:
28+
path, kind = winreg.QueryValueEx(key, "Path")
29+
if kind not in (winreg.REG_SZ, winreg.REG_EXPAND_SZ):
30+
raise ValueError("Value kind is not a string")
31+
except (OSError, ValueError):
32+
LOGGER.debug("Not removing global commands directory from PATH", exc_info=True)
33+
else:
34+
LOGGER.debug("Current PATH contains %s", path)
35+
paths = path.split(";")
36+
newpaths = []
37+
for p in paths:
38+
# We should expand entries here, but we only want to remove those
39+
# that we added ourselves (during firstrun), and we never use
40+
# environment variables. So even if the kind is REG_EXPAND_SZ, we
41+
# don't need to expand to find our own entry.
42+
#ep = os.path.expandvars(p) if kind == winreg.REG_EXPAND_SZ else p
43+
ep = p
44+
if PurePath(ep).match(global_dir):
45+
LOGGER.debug("Removing from PATH: %s", p)
46+
else:
47+
newpaths.append(p)
48+
if len(newpaths) < len(paths):
49+
newpath = ";".join(newpaths)
50+
with winreg.CreateKeyEx(hive, subkey, access=winreg.KEY_READ|winreg.KEY_WRITE) as key:
51+
path2, kind2 = winreg.QueryValueEx(key, "Path")
52+
if path2 == path and kind2 == kind:
53+
LOGGER.info("Removing global commands directory from PATH")
54+
LOGGER.debug("New PATH contains %s", newpath)
55+
winreg.SetValueEx(key, "Path", 0, kind, newpath)
56+
else:
57+
LOGGER.debug("Not removing global commands directory from PATH "
58+
"because the registry changed while processing.")
59+
60+
try:
61+
from _native import broadcast_settings_change
62+
broadcast_settings_change()
63+
except (ImportError, OSError):
64+
LOGGER.debug("Did not broadcast settings change notification",
65+
exc_info=True)
66+
67+
if not global_dir.is_dir():
68+
return
69+
LOGGER.info("Purging global commands from %s", global_dir)
70+
for f in _iterdir(global_dir):
71+
LOGGER.debug("Purging %s", f)
72+
rmtree(f, after_5s_warning=warn_msg)
73+
74+
2075
def execute(cmd):
2176
LOGGER.debug("BEGIN uninstall_command.execute: %r", cmd.args)
2277

@@ -31,28 +86,28 @@ def execute(cmd):
3186
cmd.tags = []
3287

3388
if cmd.purge:
34-
if cmd.ask_yn("Uninstall all runtimes?"):
35-
for i in installed:
36-
LOGGER.info("Purging %s from %s", i["display-name"], i["prefix"])
37-
try:
38-
rmtree(
39-
i["prefix"],
40-
after_5s_warning=warn_msg.format(i["display-name"]),
41-
remove_ext_first=("exe", "dll", "json")
42-
)
43-
except FilesInUseError:
44-
LOGGER.warn("Unable to purge %s because it is still in use.",
45-
i["display-name"])
46-
continue
47-
LOGGER.info("Purging saved downloads from %s", cmd.download_dir)
48-
rmtree(cmd.download_dir, after_5s_warning=warn_msg.format("cached downloads"))
49-
LOGGER.info("Purging global commands from %s", cmd.global_dir)
50-
for f in _iterdir(cmd.global_dir):
51-
LOGGER.debug("Purging %s", f)
52-
rmtree(f, after_5s_warning=warn_msg.format("global commands"))
53-
LOGGER.info("Purging all shortcuts")
54-
for _, cleanup in SHORTCUT_HANDLERS.values():
55-
cleanup(cmd, [])
89+
if not cmd.ask_yn("Uninstall all runtimes?"):
90+
LOGGER.debug("END uninstall_command.execute")
91+
return
92+
for i in installed:
93+
LOGGER.info("Purging %s from %s", i["display-name"], i["prefix"])
94+
try:
95+
rmtree(
96+
i["prefix"],
97+
after_5s_warning=warn_msg.format(i["display-name"]),
98+
remove_ext_first=("exe", "dll", "json")
99+
)
100+
except FilesInUseError:
101+
LOGGER.warn("Unable to purge %s because it is still in use.",
102+
i["display-name"])
103+
continue
104+
LOGGER.info("Purging saved downloads from %s", cmd.download_dir)
105+
rmtree(cmd.download_dir, after_5s_warning=warn_msg.format("cached downloads"))
106+
# Purge global commands directory
107+
_do_purge_global_dir(cmd.global_dir, warn_msg.format("global commands"))
108+
LOGGER.info("Purging all shortcuts")
109+
for _, cleanup in SHORTCUT_HANDLERS.values():
110+
cleanup(cmd, [])
56111
LOGGER.debug("END uninstall_command.execute")
57112
return
58113

tests/conftest.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,14 @@ def setup(self, _subkey=None, **keys):
205205
else:
206206
raise TypeError("unsupported type in registry")
207207

208+
def getvalue(self, subkey, valuename):
209+
with winreg.OpenKeyEx(self.key, subkey) as key:
210+
return winreg.QueryValueEx(key, valuename)[0]
211+
212+
def getvalueandkind(self, subkey, valuename):
213+
with winreg.OpenKeyEx(self.key, subkey) as key:
214+
return winreg.QueryValueEx(key, valuename)
215+
208216

209217
@pytest.fixture(scope='function')
210218
def registry():

tests/test_uninstall_command.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import os
2+
import pytest
3+
import winreg
4+
5+
from pathlib import Path
6+
7+
from manage import uninstall_command as UC
8+
9+
10+
def test_purge_global_dir(monkeypatch, registry, tmp_path):
11+
registry.setup(Path=rf"C:\A;{tmp_path}\X;{tmp_path};C:\B;%PTH%;C:\%D%\E")
12+
(tmp_path / "test.txt").write_bytes(b"")
13+
(tmp_path / "test2.txt").write_bytes(b"")
14+
15+
monkeypatch.setitem(os.environ, "PTH", str(tmp_path))
16+
UC._do_purge_global_dir(tmp_path, "SLOW WARNING", hive=registry.hive, subkey=registry.root)
17+
assert registry.getvalueandkind("", "Path") == (
18+
rf"C:\A;{tmp_path}\X;C:\B;%PTH%;C:\%D%\E", winreg.REG_SZ)
19+
assert not list(tmp_path.iterdir())

0 commit comments

Comments
 (0)
0