Skip to content

Commit 4c1976e

Browse files
authored
Pass through list of installs to warn about to shortcut implementations. (#99)
Fixes #85
1 parent 1ddf8e9 commit 4c1976e

File tree

9 files changed

+119
-38
lines changed

9 files changed

+119
-38
lines changed

.github/workflows/build.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,13 @@ jobs:
149149

150150
- name: 'Launch default runtime'
151151
run: pymanager exec -m site
152+
env:
153+
PYMANAGER_DEBUG: true
154+
155+
- name: 'Uninstall runtime'
156+
run: pymanager uninstall -y default
157+
env:
158+
PYMANAGER_DEBUG: true
152159

153160
- name: 'Emulate first launch'
154161
run: |

ci/release.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,14 @@ stages:
256256
- powershell: |
257257
pymanager exec -m site
258258
displayName: 'Launch default runtime'
259+
env:
260+
PYMANAGER_DEBUG: true
261+
262+
- powershell: |
263+
pymanager uninstall -y default
264+
displayName: 'Uninstall runtime'
265+
env:
266+
PYMANAGER_DEBUG: true
259267
260268
- powershell: |
261269
$i = (mkdir -force test_installs)

src/manage/arputils.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from .fsutils import rglob
66
from .logging import LOGGER
77
from .pathutils import Path
8+
from .tagutils import install_matches_any
9+
810

911
def _root():
1012
return winreg.CreateKey(
@@ -59,7 +61,7 @@ def _set_value(key, name, value):
5961
winreg.SetValueEx(key, name, None, winreg.REG_SZ, str(value))
6062

6163

62-
def _make(key, item, shortcut):
64+
def _make(key, item, shortcut, *, allow_warn=True):
6365
prefix = Path(item["prefix"])
6466

6567
for value, from_dict, value_name, relative_to in [
@@ -122,21 +124,22 @@ def _iter_keys(key):
122124
return
123125

124126

125-
def create_one(install, shortcut):
127+
def create_one(install, shortcut, warn_for=[]):
128+
allow_warn = install_matches_any(install, warn_for)
126129
with _root() as root:
127130
install_id = f"pymanager-{install['id']}"
128131
LOGGER.debug("Creating ARP entry for %s", install_id)
129132
try:
130133
with winreg.CreateKey(root, install_id) as key:
131-
_make(key, install, shortcut)
134+
_make(key, install, shortcut, allow_warn=allow_warn)
132135
except OSError:
133136
LOGGER.debug("Failed to create entry for %s", install_id)
134137
LOGGER.debug("TRACEBACK:", exc_info=True)
135138
_delete_key(root, install_id)
136139
raise
137140

138141

139-
def cleanup(preserve_installs):
142+
def cleanup(preserve_installs, warn_for=[]):
140143
keep = {f"pymanager-{i['id']}".casefold() for i in preserve_installs}
141144
to_delete = []
142145
with _root() as root:

src/manage/install_command.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -242,41 +242,44 @@ def _write_alias(cmd, install, alias, target):
242242
launcher = _if_exists(launcher, "-64")
243243
LOGGER.debug("Create %s linking to %s using %s", alias["name"], target, launcher)
244244
if not launcher or not launcher.is_file():
245-
LOGGER.warn("Skipping %s alias because the launcher template was not found.", alias["name"])
245+
if install_matches_any(install, getattr(cmd, "tags", None)):
246+
LOGGER.warn("Skipping %s alias because the launcher template was not found.", alias["name"])
247+
else:
248+
LOGGER.debug("Skipping %s alias because the launcher template was not found.", alias["name"])
246249
return
247250
p.write_bytes(launcher.read_bytes())
248251
p.with_name(p.name + ".__target__").write_text(str(target), encoding="utf-8")
249252

250253

251254
def _create_shortcut_pep514(cmd, install, shortcut):
252255
from .pep514utils import update_registry
253-
update_registry(cmd.pep514_root, install, shortcut)
256+
update_registry(cmd.pep514_root, install, shortcut, cmd.tags)
254257

255258

256259
def _cleanup_shortcut_pep514(cmd, install_shortcut_pairs):
257260
from .pep514utils import cleanup_registry
258-
cleanup_registry(cmd.pep514_root, {s["Key"] for i, s in install_shortcut_pairs})
261+
cleanup_registry(cmd.pep514_root, {s["Key"] for i, s in install_shortcut_pairs}, cmd.tags)
259262

260263

261264
def _create_start_shortcut(cmd, install, shortcut):
262265
from .startutils import create_one
263-
create_one(cmd.start_folder, install, shortcut)
266+
create_one(cmd.start_folder, install, shortcut, cmd.tags)
264267

265268

266269
def _cleanup_start_shortcut(cmd, install_shortcut_pairs):
267270
from .startutils import cleanup
268-
cleanup(cmd.start_folder, [s for i, s in install_shortcut_pairs])
271+
cleanup(cmd.start_folder, [s for i, s in install_shortcut_pairs], cmd.tags)
269272

270273

271274
def _create_arp_entry(cmd, install, shortcut):
272275
# ARP = Add/Remove Programs
273276
from .arputils import create_one
274-
create_one(install, shortcut)
277+
create_one(install, shortcut, cmd.tags)
275278

276279

277280
def _cleanup_arp_entries(cmd, install_shortcut_pairs):
278281
from .arputils import cleanup
279-
cleanup([i for i, s in install_shortcut_pairs])
282+
cleanup([i for i, s in install_shortcut_pairs], cmd.tags)
280283

281284

282285
SHORTCUT_HANDLERS = {
@@ -359,7 +362,7 @@ def print_cli_shortcuts(cmd):
359362
names = get_install_alias_names(aliases, windowed=True)
360363
LOGGER.debug("%s will be launched by %s", i["display-name"], ", ".join(names))
361364

362-
if tags and not install_matches_any(i, cmd.tags):
365+
if not install_matches_any(i, tags):
363366
continue
364367

365368
names = get_install_alias_names(aliases, windowed=False)

src/manage/pep514utils.py

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from .logging import LOGGER
66
from .pathutils import Path
7+
from .tagutils import install_matches_any
78
from .verutils import Version
89

910

@@ -136,7 +137,7 @@ def _update_reg_values(key, data, install, exclude=set()):
136137
winreg.SetValueEx(key, k, None, v_kind, v)
137138

138139

139-
def _is_tag_managed(company_key, tag_name, *, creating=False):
140+
def _is_tag_managed(company_key, tag_name, *, creating=False, allow_warn=True):
140141
try:
141142
tag = winreg.OpenKey(company_key, tag_name)
142143
except FileNotFoundError:
@@ -188,12 +189,15 @@ def _is_tag_managed(company_key, tag_name, *, creating=False):
188189
new_name = f"{orig_name}.{i}"
189190
# Raises standard PermissionError (5) if new_name exists
190191
reg_rename_key(tag.handle, orig_name, new_name)
191-
LOGGER.warn("An existing registry key for %s was renamed to %s "
192-
"because it appeared to be invalid. If this is "
193-
"correct, the registry key can be safely deleted. "
194-
"To avoid this in future, ensure that the "
195-
"InstallPath key refers to a valid path.",
196-
tag_name, new_name)
192+
if allow_warn:
193+
LOGGER.warn("An existing registry key for %s was renamed to %s "
194+
"because it appeared to be invalid. If this is "
195+
"correct, the registry key can be safely deleted. "
196+
"To avoid this in future, ensure that the "
197+
"InstallPath key refers to a valid path.",
198+
tag_name, new_name)
199+
else:
200+
LOGGER.debug("Renamed %s to %s", tag_name, new_name)
197201
break
198202
except FileNotFoundError:
199203
LOGGER.debug("Original key disappeared, so we will claim it")
@@ -207,8 +211,12 @@ def _is_tag_managed(company_key, tag_name, *, creating=False):
207211
orig_name, new_name, exc_info=True)
208212
raise
209213
else:
210-
LOGGER.warn("Attempted to clean up invalid registry key %s but "
211-
"failed after too many attempts.", tag_name)
214+
if allow_warn:
215+
LOGGER.warn("Attempted to clean up invalid registry key %s but "
216+
"failed after too many attempts.", tag_name)
217+
else:
218+
LOGGER.debug("Attempted to clean up invalid registry key %s but "
219+
"failed after too many attempts.", tag_name)
212220
return False
213221
return True
214222

@@ -226,31 +234,40 @@ def _split_root(root_name):
226234
return hive, name
227235

228236

229-
def update_registry(root_name, install, data):
237+
def update_registry(root_name, install, data, warn_for=[]):
230238
hive, name = _split_root(root_name)
231239
with winreg.CreateKey(hive, name) as root:
232-
if _is_tag_managed(root, data["Key"], creating=True):
240+
allow_warn = install_matches_any(install, warn_for)
241+
if _is_tag_managed(root, data["Key"], creating=True, allow_warn=allow_warn):
233242
with winreg.CreateKey(root, data["Key"]) as tag:
234243
LOGGER.debug("Creating/updating %s\\%s", root_name, data["Key"])
235244
winreg.SetValueEx(tag, "ManagedByPyManager", None, winreg.REG_DWORD, 1)
236245
_update_reg_values(tag, data, install, {"kind", "Key", "ManagedByPyManager"})
237-
else:
246+
elif allow_warn:
238247
LOGGER.warn("An existing runtime is registered at %s in the registry, "
239248
"and so the new one has not been registered.", data["Key"])
240249
LOGGER.info("This may prevent some other applications from detecting "
241250
"the new installation, although 'py -V:...' will work. "
242251
"To register the new installation, remove the existing "
243252
"runtime and then run 'py install --refresh'")
253+
else:
254+
LOGGER.debug("An existing runtime is registered at %s and so the new "
255+
"install has not been registered.", data["Key"])
244256

245257

246-
def cleanup_registry(root_name, keep):
258+
def cleanup_registry(root_name, keep, warn_for=[]):
247259
hive, name = _split_root(root_name)
248260
with _reg_open(hive, name, writable=True) as root:
249261
for company_name in _iter_keys(root):
250262
any_left = False
251263
with winreg.OpenKey(root, company_name, access=winreg.KEY_ALL_ACCESS) as company:
252264
for tag_name in _iter_keys(company):
253-
if f"{company_name}\\{tag_name}" in keep or not _is_tag_managed(company, tag_name):
265+
# Calculate whether to show warnings or not
266+
install = {"company": company_name, "tag": tag_name}
267+
allow_warn = install_matches_any(install, warn_for)
268+
269+
if (f"{company_name}\\{tag_name}" in keep
270+
or not _is_tag_managed(company, tag_name, allow_warn=allow_warn)):
254271
any_left = True
255272
else:
256273
_reg_rmtree(company, tag_name)

src/manage/startutils.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from .fsutils import rmtree, unlink
44
from .logging import LOGGER
55
from .pathutils import Path
6+
from .tagutils import install_matches_any
67

78

89
def _unprefix(p, prefix):
@@ -25,7 +26,7 @@ def _unprefix(p, prefix):
2526
return p
2627

2728

28-
def _make(root, prefix, item):
29+
def _make(root, prefix, item, allow_warn=True):
2930
n = item["Name"]
3031
try:
3132
_make_directory(root, n, prefix, item["Items"])
@@ -39,7 +40,10 @@ def _make(root, prefix, item):
3940
try:
4041
lnk.relative_to(root)
4142
except ValueError:
42-
LOGGER.warn("Package attempted to create shortcut outside of its directory")
43+
if allow_warn:
44+
LOGGER.warn("Package attempted to create shortcut outside of its directory")
45+
else:
46+
LOGGER.debug("Package attempted to create shortcut outside of its directory")
4347
LOGGER.debug("Path: %s", lnk)
4448
LOGGER.debug("Directory: %s", root)
4549
return None
@@ -136,12 +140,12 @@ def _get_to_keep(keep, root, item):
136140
pass
137141

138142

139-
def create_one(root, install, shortcut):
143+
def create_one(root, install, shortcut, warn_for=[]):
140144
root = Path(_native.shortcut_get_start_programs()) / root
141-
_make(root, install["prefix"], shortcut)
145+
_make(root, install["prefix"], shortcut, allow_warn=install_matches_any(install, warn_for))
142146

143147

144-
def cleanup(root, preserve):
148+
def cleanup(root, preserve, warn_for=[]):
145149
root = Path(_native.shortcut_get_start_programs()) / root
146150

147151
if not root.is_dir():

src/manage/uninstall_command.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ def execute(cmd):
2626
cmd.virtual_env = None
2727
installed = list(cmd.get_installs())
2828

29+
cmd.tags = []
30+
2931
if cmd.purge:
3032
if cmd.ask_yn("Uninstall all runtimes?"):
3133
for i in installed:
@@ -61,16 +63,18 @@ def execute(cmd):
6163
to_uninstall = []
6264
if not cmd.by_id:
6365
for tag in cmd.args:
64-
if tag.casefold() == "default".casefold():
65-
tag = cmd.default_tag
6666
try:
67-
t_or_r = tag_or_range(tag)
67+
if tag.casefold() == "default".casefold():
68+
cmd.tags.append(tag_or_range(cmd.default_tag))
69+
else:
70+
cmd.tags.append(tag_or_range(tag))
6871
except ValueError as ex:
6972
LOGGER.warn("%s", ex)
70-
continue
73+
74+
for tag in cmd.tags:
7175
candidates = get_matching_install_tags(
7276
installed,
73-
t_or_r,
77+
tag,
7478
default_platform=cmd.default_platform,
7579
)
7680
if not candidates:

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ def quiet_log():
6060

6161

6262
class LogCaptureHandler(list):
63-
def skip_until(self, pattern, args=()):
63+
def skip_until(self, pattern, args=None):
6464
return ('until', pattern, args)
6565

66-
def not_logged(self, pattern, args=()):
66+
def not_logged(self, pattern, args=None):
6767
return ('not', pattern, args)
6868

6969
def __call__(self, *cmp):

tests/test_pep514utils.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import winreg
33

44
from manage import pep514utils
5+
from manage import tagutils
56

67
def test_is_tag_managed(registry, tmp_path):
78
registry.setup(Company={
@@ -23,3 +24,37 @@ def test_is_tag_managed(registry, tmp_path):
2324
assert pep514utils._is_tag_managed(registry.key, r"Company\3.0", creating=True)
2425
with winreg.OpenKey(registry.key, r"Company\3.0.2"):
2526
pass
27+
28+
29+
def test_is_tag_managed_warning_suppressed(registry, tmp_path, assert_log):
30+
registry.setup(Company={
31+
"3.0.0": {"": "Just in the way here"},
32+
"3.0.1": {"": "Also in the way here"},
33+
})
34+
pep514utils.update_registry(
35+
rf"HKEY_CURRENT_USER\{registry.root}",
36+
dict(company="Company", tag="3.0.0"),
37+
dict(kind="pep514", Key="Company\\3.0.0", InstallPath=dict(_="dir")),
38+
warn_for=[tagutils.tag_or_range(r"Company\3.0.1")],
39+
)
40+
assert_log(
41+
"Registry key %s appears invalid.+",
42+
assert_log.not_logged("An existing runtime is registered at %s"),
43+
)
44+
45+
46+
def test_is_tag_managed_warning(registry, tmp_path, assert_log):
47+
registry.setup(Company={
48+
"3.0.0": {"": "Just in the way here"},
49+
"3.0.1": {"": "Also in the way here"},
50+
})
51+
pep514utils.update_registry(
52+
rf"HKEY_CURRENT_USER\{registry.root}",
53+
dict(company="Company", tag="3.0.0"),
54+
dict(kind="pep514", Key="Company\\3.0.0", InstallPath=dict(_="dir")),
55+
warn_for=[tagutils.tag_or_range(r"Company\3.0.0")],
56+
)
57+
assert_log(
58+
"Registry key %s appears invalid.+",
59+
assert_log.skip_until("An existing registry key for %s"),
60+
)

0 commit comments

Comments
 (0)