Skip to content

Commit 1263c28

Browse files
committed
Improved script and fixed names
1 parent 7ff8626 commit 1263c28

File tree

5 files changed

+120
-40
lines changed

5 files changed

+120
-40
lines changed

src/manage/aliasutils.py

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,37 @@
55
from .pathutils import Path
66
from .tagutils import install_matches_any
77

8+
SCRIPT_CODE = """import sys
9+
10+
# Clear sys.path[0] if it contains this script.
11+
# Be careful to use the most compatible Python code possible.
12+
try:
13+
if sys.path[0]:
14+
if sys.argv[0].startswith(sys.path[0]):
15+
sys.path[0] = ""
16+
else:
17+
open(sys.path[0] + "/" + sys.argv[0], "rb").close()
18+
sys.path[0] = ""
19+
except OSError:
20+
pass
21+
except AttributeError:
22+
pass
23+
except IndexError:
24+
pass
25+
26+
# Replace argv[0] with our executable instead of the script name.
27+
try:
28+
if sys.argv[0][-14:].upper() == ".__SCRIPT__.PY":
29+
sys.argv[0] = sys.argv[0][:-14]
30+
sys.orig_argv[0] = sys.argv[0]
31+
except AttributeError:
32+
pass
33+
except IndexError:
34+
pass
35+
36+
from {mod} import {func}
37+
sys.exit({func}())
38+
"""
839

940
def _if_exists(launcher, plat):
1041
suffix = "." + launcher.suffix.lstrip(".")
@@ -15,13 +46,22 @@ def _if_exists(launcher, plat):
1546

1647

1748
def create_alias(cmd, install, alias, target, *, script_code=None, _link=os.link):
18-
p = (cmd.global_dir / alias["name"])
49+
p = cmd.global_dir / alias["name"]
50+
if not p.match("*.exe"):
51+
p = p.with_name(p.name + ".exe")
1952
target = Path(target)
2053
ensure_tree(p)
2154
launcher = cmd.launcher_exe
2255
if alias.get("windowed"):
2356
launcher = cmd.launcherw_exe or launcher
2457

58+
alias_written = cmd.scratch.setdefault("aliasutils.create_alias.alias_written", set())
59+
n = p.stem.casefold()
60+
if n in alias_written:
61+
# We've already written this alias in this session, so skip it.
62+
return
63+
alias_written.add(n)
64+
2565
plat = install["tag"].rpartition("-")[-1]
2666
if plat:
2767
LOGGER.debug("Checking for launcher for platform -%s", plat)
@@ -60,7 +100,6 @@ def create_alias(cmd, install, alias, target, *, script_code=None, _link=os.link
60100
LOGGER.debug("Failed to read existing alias launcher.")
61101

62102
launcher_remap = cmd.scratch.setdefault("aliasutils.create_alias.launcher_remap", {})
63-
64103
if existing_bytes == launcher_bytes:
65104
# Valid existing launcher, so save its path in case we need it later
66105
# for a hard link.
@@ -106,7 +145,7 @@ def create_alias(cmd, install, alias, target, *, script_code=None, _link=os.link
106145
if do_update:
107146
p_target.write_text(str(target), encoding="utf-8")
108147

109-
p_script = p.with_name(p.name + "-script.py")
148+
p_script = p.with_name(p.name + ".__script__.py")
110149
if script_code:
111150
do_update = True
112151
try:
@@ -126,6 +165,24 @@ def create_alias(cmd, install, alias, target, *, script_code=None, _link=os.link
126165
LOGGER.info("Failed to remove %s.", p_script, exc_info=True)
127166

128167

168+
def cleanup_alias(cmd):
169+
if not cmd.global_dir or not cmd.global_dir.is_dir():
170+
return
171+
172+
alias_written = cmd.scratch.get("aliasutils.create_alias.alias_written") or ()
173+
174+
for alias in cmd.global_dir.glob("*.exe"):
175+
target = alias.with_name(alias.name + ".__target__")
176+
script = alias.with_name(alias.name + ".__script__.py")
177+
if alias.stem.casefold() not in alias_written:
178+
LOGGER.debug("Unlink %s", alias)
179+
unlink(alias, f"Attempting to remove {alias} is taking some time. " +
180+
"Ensure it is not is use, and please continue to wait " +
181+
"or press Ctrl+C to abort.")
182+
unlink(target)
183+
unlink(script)
184+
185+
129186
def _parse_entrypoint_line(line):
130187
line = line.partition("#")[0]
131188
name, sep, rest = line.partition("=")
@@ -170,7 +227,7 @@ def _scan_one(root):
170227
if name and mod and func:
171228
yield (
172229
{**alias, "name": name},
173-
f"import sys; from {mod} import {func}; sys.exit({func}())",
230+
SCRIPT_CODE.format(mod=mod, func=func),
174231
)
175232

176233

src/manage/install_command.py

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -266,12 +266,12 @@ def _cleanup_entrypoints(cmd, install_shortcut_pairs):
266266
}
267267

268268

269-
def update_all_shortcuts(cmd, *, _create_alias=None):
269+
def update_all_shortcuts(cmd, *, _create_alias=None, _cleanup_alias=None):
270270
if not _create_alias:
271271
from .aliasutils import create_alias as _create_alias
272+
from .aliasutils import cleanup_alias as _cleanup_alias
272273

273274
LOGGER.debug("Updating global shortcuts")
274-
alias_written = set()
275275
shortcut_written = {}
276276
for i in cmd.get_installs():
277277
if cmd.global_dir:
@@ -288,14 +288,11 @@ def update_all_shortcuts(cmd, *, _create_alias=None):
288288
aliases.append({**alias_2[0], "name": "pythonw.exe"})
289289

290290
for a in aliases:
291-
if a["name"].casefold() in alias_written:
292-
continue
293291
target = i["prefix"] / a["target"]
294292
if not target.is_file():
295293
LOGGER.warn("Skipping alias '%s' because target '%s' does not exist", a["name"], a["target"])
296294
continue
297295
_create_alias(cmd, i, a, target)
298-
alias_written.add(a["name"].casefold())
299296

300297
for s in i.get("shortcuts", ()):
301298
if cmd.enable_shortcut_kinds and s["kind"] not in cmd.enable_shortcut_kinds:
@@ -321,15 +318,7 @@ def update_all_shortcuts(cmd, *, _create_alias=None):
321318
create(cmd, i, s)
322319
shortcut_written.setdefault("site-dirs", []).append((i, s))
323320

324-
if cmd.global_dir and cmd.global_dir.is_dir() and cmd.launcher_exe:
325-
for target in cmd.global_dir.glob("*.exe.__target__"):
326-
alias = target.with_suffix("")
327-
if alias.name.casefold() not in alias_written:
328-
LOGGER.debug("Unlink %s", alias)
329-
unlink(alias, f"Attempting to remove {alias} is taking some time. " +
330-
"Ensure it is not is use, and please continue to wait " +
331-
"or press Ctrl+C to abort.")
332-
target.unlink()
321+
_cleanup_alias(cmd)
333322

334323
for k, (_, cleanup) in SHORTCUT_HANDLERS.items():
335324
cleanup(cmd, shortcut_written.get(k, []))

src/manage/pathutils.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,17 @@ def __bool__(self):
4444

4545
@property
4646
def stem(self):
47-
return self.name.rpartition(".")[0]
47+
stem, dot, suffix = self.name.rpartition(".")
48+
if not dot:
49+
return suffix
50+
return stem
4851

4952
@property
5053
def suffix(self):
51-
return self.name.rpartition(".")[2]
54+
stem, dot, suffix = self.name.rpartition(".")
55+
if not dot:
56+
return ""
57+
return dot + suffix
5258

5359
@property
5460
def parent(self):

src/pymanager/launcher.cpp

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -145,43 +145,56 @@ get_executable(wchar_t *executable, unsigned int bufferSize)
145145
int
146146
insert_script(int *argc, wchar_t ***argv)
147147
{
148-
DWORD len = GetModuleFileNameW(NULL, NULL, 0);
149-
if (len == 0) {
150-
return HRESULT_FROM_WIN32(GetLastError());
151-
} else if (len < 5) {
152-
return 0;
153-
}
154-
155148
HANDLE ph = GetProcessHeap();
156-
DWORD path_len = len + 7;
157-
wchar_t *path = (wchar_t *)HeapAlloc(ph, HEAP_ZERO_MEMORY, sizeof(wchar_t) * path_len);
158-
len = path ? GetModuleFileNameW(NULL, path, path_len) : 0;
159-
if (len == 0) {
160-
return HRESULT_FROM_WIN32(GetLastError());
161-
}
149+
wchar_t *path = NULL;
150+
DWORD path_len = 0;
151+
DWORD len = 0;
152+
int error = 0;
153+
const wchar_t *SUFFIX = L".__script__.py";
154+
155+
// Get our path in a dynamic buffer with enough space to add SUFFIX
156+
while (len >= path_len) {
157+
if (path) {
158+
HeapFree(ph, 0, path);
159+
}
160+
path_len += 260;
162161

163-
if (wcsicmp(&path[len - 4], L".exe")) {
164-
HeapFree(ph, 0, path);
165-
return 0;
162+
path = (wchar_t *)HeapAlloc(ph, HEAP_ZERO_MEMORY, sizeof(wchar_t) * path_len);
163+
if (!path) {
164+
return HRESULT_FROM_WIN32(GetLastError());
165+
}
166+
167+
len = GetModuleFileNameW(NULL, path, path_len - wcslen(SUFFIX));
168+
if (len == 0) {
169+
error = GetLastError();
170+
HeapFree(ph, 0, path);
171+
return HRESULT_FROM_WIN32(error);
172+
}
166173
}
167174

168-
wcscpy_s(&path[len - 4], path_len, L"-script.py");
175+
wcscpy_s(&path[len], path_len, SUFFIX);
176+
177+
// Check that we have a script file. FindFirstFile should be fastest.
169178
WIN32_FIND_DATAW fd;
170179
HANDLE fh = FindFirstFileW(path, &fd);
171180
if (fh == INVALID_HANDLE_VALUE) {
172-
int err = GetLastError();
181+
error = GetLastError();
173182
HeapFree(ph, 0, path);
174-
switch (err) {
183+
switch (error) {
175184
case ERROR_INVALID_FUNCTION:
176185
case ERROR_FILE_NOT_FOUND:
177186
case ERROR_PATH_NOT_FOUND:
187+
// This is the typical exit for normal launches. We ought to be nice
188+
// and fast up until this point, but can be slower through every
189+
// other path.
178190
return 0;
179191
default:
180-
return HRESULT_FROM_WIN32(GetLastError());
192+
return HRESULT_FROM_WIN32(error);
181193
}
182194
}
183195
CloseHandle(fh);
184196

197+
// Create a new argv that will be used to launch the script.
185198
wchar_t **argv2 = (wchar_t **)HeapAlloc(ph, HEAP_ZERO_MEMORY, sizeof(wchar_t *) * (*argc + 1));
186199
if (!argv2) {
187200
HeapFree(ph, 0, path);

tests/test_pathutils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,18 @@ def test_path_match():
1515
assert not p.match("example*")
1616
assert not p.match("example*.com")
1717
assert not p.match("*ple*")
18+
19+
20+
def test_path_stem():
21+
p = Path("python3.12.exe")
22+
assert p.stem == "python3.12"
23+
assert p.suffix == ".exe"
24+
p = Path("python3.12")
25+
assert p.stem == "python3"
26+
assert p.suffix == ".12"
27+
p = Path("python3")
28+
assert p.stem == "python3"
29+
assert p.suffix == ""
30+
p = Path(".exe")
31+
assert p.stem == ""
32+
assert p.suffix == ".exe"

0 commit comments

Comments
 (0)