Skip to content

Commit 198a73f

Browse files
committed
Refactor and simplify code for aliases
1 parent 935ab05 commit 198a73f

File tree

6 files changed

+229
-246
lines changed

6 files changed

+229
-246
lines changed

src/manage/aliasutils.py

Lines changed: 133 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import os
22

3-
from .exceptions import FilesInUseError
3+
from .exceptions import FilesInUseError, NoLauncherTemplateError
44
from .fsutils import atomic_unlink, ensure_tree, unlink
55
from .logging import LOGGER
66
from .pathutils import Path
77
from .tagutils import install_matches_any
88

9+
_EXE = ".exe".casefold()
10+
11+
DEFAULT_SITE_DIRS = ["Lib\\site-packages", "Scripts"]
12+
913
SCRIPT_CODE = """import sys
1014
1115
# Clear sys.path[0] if it contains this script.
@@ -38,6 +42,33 @@
3842
sys.exit({func}())
3943
"""
4044

45+
46+
class AliasInfo:
47+
def __init__(self, **kwargs):
48+
self.install = kwargs.get("install")
49+
self.name = kwargs.get("name")
50+
self.windowed = kwargs.get("windowed", 0)
51+
self.target = kwargs.get("target")
52+
self.mod = kwargs.get("mod")
53+
self.func = kwargs.get("func")
54+
55+
def replace(self, **kwargs):
56+
return AliasInfo(**{
57+
"install": self.install,
58+
"name": self.name,
59+
"windowed": self.windowed,
60+
"target": self.target,
61+
"mod": self.mod,
62+
"func": self.func,
63+
**kwargs,
64+
})
65+
66+
@property
67+
def script_code(self):
68+
if self.mod and self.func:
69+
return SCRIPT_CODE.format(mod=self.mod, func=self.func)
70+
71+
4172
def _if_exists(launcher, plat):
4273
suffix = "." + launcher.suffix.lstrip(".")
4374
plat_launcher = launcher.parent / f"{launcher.stem}{plat}{suffix}"
@@ -46,24 +77,17 @@ def _if_exists(launcher, plat):
4677
return launcher
4778

4879

49-
def create_alias(cmd, install, alias, target, aliases_written, *, script_code=None, _link=os.link):
50-
p = cmd.global_dir / alias["name"]
80+
def _create_alias(cmd, *, name, target, plat=None, windowed=0, script_code=None, _link=os.link):
81+
p = cmd.global_dir / name
5182
if not p.match("*.exe"):
5283
p = p.with_name(p.name + ".exe")
5384
if not isinstance(target, Path):
5485
target = Path(target)
5586
ensure_tree(p)
5687
launcher = cmd.launcher_exe
57-
if alias.get("windowed"):
88+
if windowed:
5889
launcher = cmd.launcherw_exe or launcher
5990

60-
n = p.stem.casefold()
61-
if n in aliases_written:
62-
# We've already written this alias in this session, so skip it.
63-
return
64-
aliases_written.add(n)
65-
66-
plat = install["tag"].rpartition("-")[-1]
6791
if plat:
6892
LOGGER.debug("Checking for launcher for platform -%s", plat)
6993
launcher = _if_exists(launcher, f"-{plat}")
@@ -73,13 +97,9 @@ def create_alias(cmd, install, alias, target, aliases_written, *, script_code=No
7397
if not launcher.is_file():
7498
LOGGER.debug("Checking for launcher for -64")
7599
launcher = _if_exists(launcher, "-64")
76-
LOGGER.debug("Create %s linking to %s using %s", alias["name"], target, launcher)
100+
LOGGER.debug("Create %s linking to %s using %s", name, target, launcher)
77101
if not launcher or not launcher.is_file():
78-
if install_matches_any(install, getattr(cmd, "tags", None)):
79-
LOGGER.warn("Skipping %s alias because the launcher template was not found.", alias["name"])
80-
else:
81-
LOGGER.debug("Skipping %s alias because the launcher template was not found.", alias["name"])
82-
return
102+
raise NoLauncherTemplateError()
83103

84104
try:
85105
launcher_bytes = launcher.read_bytes()
@@ -131,7 +151,7 @@ def create_alias(cmd, install, alias, target, aliases_written, *, script_code=No
131151
LOGGER.debug("Created %s as copy of %s", p.name, launcher.name)
132152
launcher_remap[launcher.name] = p
133153
except OSError:
134-
LOGGER.error("Failed to create global command %s.", alias["name"])
154+
LOGGER.error("Failed to create global command %s.", name)
135155
LOGGER.debug("TRACEBACK", exc_info=True)
136156

137157
p_target = p.with_name(p.name + ".__target__")
@@ -199,12 +219,13 @@ def _readlines(path):
199219
return
200220

201221

202-
def _scan_one(root):
222+
def _scan_one(install, root):
203223
# Scan d for dist-info directories with entry_points.txt
204224
dist_info = [d for d in root.glob("*.dist-info") if d.is_dir()]
205-
LOGGER.debug("Found %i dist-info directories in %s", len(dist_info), root)
206225
entrypoints = [f for f in [d / "entry_points.txt" for d in dist_info] if f.is_file()]
207-
LOGGER.debug("Found %i entry_points.txt files in %s", len(entrypoints), root)
226+
if len(entrypoints):
227+
LOGGER.debug("Found %i entry_points.txt files in %i dist-info in %s",
228+
len(entrypoints), len(dist_info), root)
208229

209230
# Filter down to [console_scripts] and [gui_scripts]
210231
for ep in entrypoints:
@@ -219,64 +240,117 @@ def _scan_one(root):
219240
elif alias is not None:
220241
name, mod, func = _parse_entrypoint_line(line)
221242
if name and mod and func:
222-
yield (
223-
{**alias, "name": name},
224-
SCRIPT_CODE.format(mod=mod, func=func),
225-
)
243+
yield AliasInfo(install=install, name=name,
244+
mod=mod, func=func, **alias)
226245

227246

228-
def _scan(prefix, dirs):
247+
def _scan(install, prefix, dirs):
229248
for dirname in dirs or ():
230249
root = prefix / dirname
231-
yield from _scan_one(root)
250+
yield from _scan_one(install, root)
232251

233252

234-
def scan_and_create_entrypoints(cmd, install, shortcut, aliases_written, *, _create_alias=create_alias, _scan=_scan):
235-
prefix = install["prefix"]
253+
def calculate_aliases(cmd, install, *, _scan=_scan):
254+
LOGGER.debug("Calculating aliases for %s", install["id"])
236255

237-
aliases = list(install.get("alias", ()))
238-
alias_1 = [a for a in aliases if not a.get("windowed")]
239-
# If no windowed targets, we'll use the non-windowed one
240-
alias_2 = [a for a in aliases if a.get("windowed")] or alias_1
256+
prefix = install["prefix"]
241257

242-
targets = [
243-
(prefix / alias_1[0]["target"]) if alias_1 else None,
244-
(prefix / alias_2[0]["target"]) if alias_2 else None,
245-
]
258+
default_alias = None
259+
default_alias_w = None
246260

247-
if not any(targets):
248-
LOGGER.debug("No suitable alias found for %s. Skipping entrypoints",
249-
install["id"])
261+
for a in install.get("alias", ()):
262+
target = prefix / a["target"]
263+
if not target.is_file():
264+
LOGGER.warn("Skipping alias '%s' because target '%s' does not exist",
265+
a["name"], a["target"])
266+
continue
267+
ai = AliasInfo(install=install, **a)
268+
yield ai
269+
if a.get("windowed") and not default_alias_w:
270+
default_alias_w = ai
271+
if not default_alias:
272+
default_alias = ai
273+
274+
if not default_alias_w:
275+
default_alias_w = default_alias
276+
277+
if install.get("default"):
278+
if default_alias:
279+
yield default_alias.replace(name="python")
280+
if default_alias_w:
281+
yield default_alias_w.replace(name="pythonw", windowed=1)
282+
283+
site_dirs = DEFAULT_SITE_DIRS
284+
for s in install.get("shortcuts", ()):
285+
if s.get("kind") == "site-dirs":
286+
site_dirs = s.get("dirs", ())
287+
break
288+
289+
for ai in _scan(install, prefix, site_dirs):
290+
if ai.windowed and default_alias_w:
291+
yield ai.replace(target=default_alias_w.target)
292+
elif not ai.windowed and default_alias:
293+
yield ai.replace(target=default_alias.target)
294+
295+
296+
def create_aliases(cmd, aliases, *, _create_alias=_create_alias):
297+
if not cmd.global_dir:
250298
return
251299

252-
for alias, code in _scan(prefix, shortcut.get("dirs")):
253-
# Copy the launcher template and create a standard __target__ file
254-
target = targets[1 if alias.get("windowed", 0) else 0]
255-
if not target:
256-
LOGGER.debug("No suitable alias found for %s. Skipping this " +
257-
"entrypoint", alias["name"])
300+
written = set()
301+
302+
LOGGER.debug("Creating aliases")
303+
304+
for alias in aliases:
305+
if not alias.name:
306+
LOGGER.debug("Invalid alias info provided with no name.")
307+
continue
308+
309+
n = alias.name.casefold().removesuffix(_EXE)
310+
if n in written:
311+
# We've already written this alias, so skip it.
258312
continue
259-
_create_alias(cmd, install, alias, target, aliases_written, script_code=code)
313+
written.add(n)
260314

315+
if not alias.target:
316+
LOGGER.debug("No suitable alias found for %s. Skipping", alias.name)
317+
continue
261318

262-
def cleanup_alias(cmd, site_dirs_written, *, _unlink_many=atomic_unlink, _scan=_scan):
319+
target = alias.install["prefix"] / alias.target
320+
try:
321+
_create_alias(
322+
cmd,
323+
install=alias.install,
324+
name=alias.name,
325+
plat=alias.install.get("tag", "").rpartition("-")[2],
326+
target=target,
327+
script_code=alias.script_code,
328+
windowed=alias.windowed,
329+
)
330+
except NoLauncherTemplateError:
331+
if install_matches_any(alias.install, getattr(cmd, "tags", None)):
332+
LOGGER.warn("Skipping %s alias because "
333+
"the launcher template was not found.", alias.name)
334+
else:
335+
LOGGER.debug("Skipping %s alias because "
336+
"the launcher template was not found.", alias.name)
337+
338+
339+
340+
def cleanup_aliases(cmd, *, preserve, _unlink_many=atomic_unlink):
263341
if not cmd.global_dir or not cmd.global_dir.is_dir():
264342
return
265343

344+
LOGGER.debug("Cleaning up aliases")
266345
expected = set()
267-
for i in cmd.get_installs():
268-
expected.update(a.get("name", "").casefold() for a in i.get("alias", ()))
269-
270-
if expected:
271-
expected.add("python".casefold())
272-
expected.add("pythonw".casefold())
273-
274-
for i, s in site_dirs_written or ():
275-
for alias, code in _scan(i["prefix"], s.get("dirs")):
276-
expected.add(alias.get("name", "").casefold())
346+
for alias in preserve:
347+
if alias.name:
348+
n = alias.name.casefold().removesuffix(_EXE) + _EXE
349+
expected.add(n)
277350

351+
LOGGER.debug("Retaining %d aliases", len(expected))
278352
for alias in cmd.global_dir.glob("*.exe"):
279-
if alias.stem.casefold() in expected or alias.name.casefold() in expected:
353+
if alias.name.casefold() in expected:
280354
continue
281355
target = alias.with_name(alias.name + ".__target__")
282356
script = alias.with_name(alias.name + ".__script__.py")

src/manage/arputils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def _self_cmd():
2525
if not appdata:
2626
appdata = os.path.expanduser(r"~\AppData\Local")
2727
apps = Path(appdata) / r"Microsoft\WindowsApps"
28-
LOGGER.debug("Searching %s for pymanager.exe", apps)
28+
LOGGER.debug("Searching %s for pymanager.exe for ARP entries", apps)
2929
for d in apps.iterdir():
3030
if not d.match("PythonSoftwareFoundation.PythonManager_*"):
3131
continue

src/manage/exceptions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,8 @@ def __init__(self):
7575
class FilesInUseError(Exception):
7676
def __init__(self, files):
7777
self.files = files
78+
79+
80+
class NoLauncherTemplateError(Exception):
81+
def __init__(self):
82+
super().__init__("No suitable launcher template was found.")

0 commit comments

Comments
 (0)