8000 Add launcher executables for 32-bit and ARM64. (#69) · python/pymanager@89afccf · GitHub
[go: up one dir, main page]

Skip to content

Commit 89afccf

Browse files
authored
Add launcher executables for 32-bit and ARM64. (#69)
Fixes #67
1 parent 7504619 commit 89afccf

File tree

8 files changed

+163
-37
lines changed

8 files changed

+163
-37
lines changed

_msbuild.py

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ def main_exe(name):
107107
ConfigurationType='Application',
108108
)
109109

110+
110111
def mainw_exe(name):
111112
return CProject(name,
112113
VersionInfo(FileDescription="Python Install Manager (windowed)"),
@@ -127,6 +128,26 @@ def mainw_exe(name):
127128
)
128129

129130

131+
def launcher_exe(name, platform, windowed=False):
132+
return CProject(name,
133+
VersionInfo(
134+
FileDescription="Python launcher" + (" (windowed)" if windowed else ""),
135+
OriginalFilename=f"{name}.exe"
136+
),
137+
CPP_SETTINGS,
138+
Property('StaticLibcppLinkage', 'true'),
139+
ItemDefinition('Link', SubSystem='WINDOWS' if windowed else 'CONSOLE'),
140+
Manifest('default.manifest'),
141+
ResourceFile('pywicon.rc' if windowed else 'pyicon.rc'),
142+
CSourceFile('launcher.cpp'),
143+
CSourceFile('_launch.cpp'),
144+
IncludeFile('*.h'),
145+
source='src/pymanager',
146+
ConfigurationType='Application',
147+
Platform=platform,
148+
)
149+
150+
130151
PACKAGE = Package('python-manager',
131152
PyprojectTomlFile('pyproject.toml'),
132153
# MSIX manifest
@@ -150,32 +171,12 @@ def mainw_exe(name):
150171
Package(
151172
'templates',
152173
File('src/pymanager/templates/template.py'),
153-
CProject('launcher',
154-
VersionInfo(FileDescription="Python launcher", OriginalFilename="launcher.exe"),
155-
CPP_SETTINGS,
156-
Property('StaticLibcppLinkage', 'true'),
157-
ItemDefinition('Link', SubSystem='CONSOLE'),
158-
Manifest('default.manifest'),
159-
ResourceFile('pyicon.rc'),
160-
CSourceFile('launcher.cpp'),
161-
CSourceFile('_launch.cpp'),
162-
IncludeFile('*.h'),
163-
source='src/pymanager',
164-
ConfigurationType='Application',
165-
),
166-
CProject('launcherw',
167-
VersionInfo(FileDescription="Python launcher (windowed)", OriginalFilename="launcherw.exe"),
168-
CPP_SETTINGS,
169-
Property('StaticLibcppLinkage', 'true'),
170-
ItemDefinition('Link', SubSystem='WINDOWS'),
171-
Manifest('default.manifest'),
172-
ResourceFile('pywicon.rc'),
173-
CSourceFile('launcher.cpp'),
174-
CSourceFile('_launch.cpp'),
175-
IncludeFile('*.h'),
176-
source='src/pymanager',
177-
ConfigurationType='Application',
178-
),
174+
launcher_exe("launcher-64", "x64", windowed=False),
175+
launcher_exe("launcherw-64", "x64", windowed=True),
176+
launcher_exe("launcher-arm64", "ARM64", windowed=False),
177+
launcher_exe("launcherw-arm64", "ARM64", windowed=True),
178+
launcher_exe("launcher-32", "Win32", windowed=False),
179+
launcher_exe("launcherw-32", "Win32", windowed=True),
179180
),
180181

181182
# Directory for MSIX resources

src/manage/commands.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,10 @@ def execute(self):
274274
# Default: Python
275275
"start_folder": (str, None),
276276

277-
# Overrides for launcher executables
277+
# Overrides for launcher executables. Platform-specific versions will be
278+
# chosen automatically by inserting the last hypenated part of the tag
279+
# before the suffix, falling back on the default platform or '-64' and
280+
# eventually the unmodified version. See install_command._write_alias().
278281
# Default: .\launcher.exe and .\launcherw.exe
279282
"launcher_exe": (str, None, "path"),
280283
"launcherw_exe": (str, None, "path"),

src/manage/install_command.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,13 +215,30 @@ def _calc(prefix, filename, calculate_dest=calculate_dest):
215215
LOGGER.debug("Attempted to overwrite: %s", dest)
216216

217217

218-
def _write_alias(cmd, alias, target):
218+
def _if_exists(launcher, plat):
219+
plat_launcher = launcher.parent / f"{launcher.stem}{plat}{launcher.suffix}"
220+
if plat_launcher.is_file():
221+
return plat_launcher
222+
return launcher
223+
224+
225+
def _write_alias(cmd, install, alias, target):
219226
p = (cmd.global_dir / alias["name"])
220227
ensure_tree(p)
221228
unlink(p)
222229
launcher = cmd.launcher_exe
223230
if alias.get("windowed"):
224231
launcher = cmd.launcherw_exe or launcher
232+
plat = install["tag"].rpartition("-")[-1]
233+
if plat:
234+
LOGGER.debug("Checking for launcher for platform -%s", plat)
235+
launcher = _if_exists(launcher, f"-{plat}")
236+
if not launcher.is_file():
237+
LOGGER.debug("Checking for launcher for default platform %s", cmd.default_platform)
238+
launcher = _if_exists(launcher, cmd.default_platform)
239+
if not launcher.is_file():
240+
LOGGER.debug("Checking for launcher for -64")
241+
launcher = _if_exists(launcher, "-64")
225242
LOGGER.debug("Create %s linking to %s using %s", alias["name"], target, launcher)
226243
if not launcher or not launcher.is_file():
227244
LOGGER.warn("Skipping %s alias because the launcher template was not found.", alias["name"])
@@ -281,7 +298,7 @@ def update_all_shortcuts(cmd, path_warning=True):
281298
if not target.is_file():
282299
LOGGER.warn("Skipping alias '%s' because target '%s' does not exist", a["name"], a["target"])
283300
continue
284-
_write_alias(cmd, a, target)
301+
_write_alias(cmd, i, a, target)
285302
alias_written.add(a["name"].casefold())
286303

287304
for s in i.get("shortcuts", ()):

src/pymanager/_launch.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ ctrl_c_handler(DWORD code)
1414
static int
1515
dup_handle(HANDLE input, HANDLE *output)
1616
{
17-
static HANDLE self = GetCurrentProcess();
17+
static HANDLE self = NULL;
18+
if (self == NULL) {
19+
self = GetCurrentProcess();
20+
}
1821
if (input == NULL || input == INVALID_HANDLE_VALUE) {
1922
*output = input;
2023
return 0;

src/pymanager/launcher.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ get_executable(wchar_t *executable, unsigned int bufferSize)
117117
return HRESULT_FROM_WIN32(GetLastError());
118118
}
119119

120-
wcscat_s(config, L".__target__");
120+
wcscat_s(config, MAXLEN, L".__target__");
121121

122122
HANDLE hFile = CreateFileW(config, GENERIC_READ,
123123
FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, 0, NULL);
@@ -150,7 +150,7 @@ try_load_python3_dll(const wchar_t *executable, unsigned int bufferSize, void **
150150
return ERROR_DLL_LOAD_DISABLED;
151151
#else
152152
wchar_t directory[MAXLEN];
153-
wcscpy_s(directory, executable);
153+
wcscpy_s(directory, MAXLEN, executable);
154154
wchar_t *sep = wcsrchr(directory, L'\\');
155155
if (!sep) {
156156
return ERROR_RELATIVE_PATH;

src/pymanager/main.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,8 @@ wmain(int argc, wchar_t **argv)
541541

542542
if (err) {
543543
// Most 'not found' errors have been handled above. These are internal
544-
fprintf(stderr, "INTERNAL ERROR 0x%08X. Please report to https://github.com/python/pymanager\n", err);
544+
fprintf(stderr, "[ERROR] Internal error 0x%08X. "
545+
"Please report to https://github.com/python/pymanager\n", err);
545546
goto error;
546547
}
547548

@@ -560,15 +561,15 @@ wmain(int argc, wchar_t **argv)
560561
case ERROR_EXE_MACHINE_TYPE_MISMATCH:
561562
case HRESULT_FROM_WIN32(ERROR_EXE_MACHINE_TYPE_MISMATCH):
562563
fprintf(stderr,
563-
"[FATAL ERROR] Executable '%ls' is for a different kind of "
564+
"[ERROR] Executable '%ls' is for a different kind of "
564565
"processor architecture.\n",
565566
executable.c_str());
566567
fprintf(stderr,
567568
"Try using '-V:<version>' to select a different runtime, or use "
568569
"'py install' to install one for your CPU.\n");
569570
break;
570571
default:
571-
fprintf(stderr, "[FATAL ERROR] Failed to launch '%ls' (0x%08X)\n", executable.c_str(), err);
572+
fprintf(stderr, "[ERROR] Failed to launch '%ls' (0x%08X)\n", executable.c_str(), err);
572573
fprintf(stderr, "This may be a corrupt install or a system configuration issue.\n");
573574
break;
574575
}

src/pymanager/msi.wxs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,12 @@
107107
</Component>
108108

109109
<Component Id="TemplatesComponent" Directory="TEMPLATES" Guid="3ACC110C-5C33-45C8-9843-A446718930A0">
110-
<File KeyPath="yes" Source="templates\launcher.exe" Name="launcher.exe" />
111-
<File Source="templates\launcherw.exe" Name="launcherw.exe" />
110+
<File KeyPath="yes" Source="templates\launcher-64.exe" Name="launcher-64.exe" />
111+
<File Source="templates\launcherw-64.exe" Name="launcherw-64.exe" />
112+
<File Source="templates\launcher-arm64.exe" Name="launcher-arm64.exe" />
113+
<File Source="templates\launcherw-arm64.exe" Name="launcherw-arm64.exe" />
114+
<File Source="templates\launcher-32.exe" Name="launcher-32.exe" />
115+
<File Source="templates\launcherw-32.exe" Name="launcherw-32.exe" />
112116
</Component>
113117

114118
</Package>

tests/test_install_command.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import pytest
2+
import secrets
3+
from manage import install_command as IC
4+
5+
6+
@pytest.fixture
7+
def alias_checker(tmp_path):
8+
with AliasChecker(tmp_path) as checker:
9+
yield checker
10+
11+
class AliasChecker:
12+
class Cmd:
13+
global_dir = "out"
14+
launcher_exe = "launcher.txt"
15+
launcherw_exe = "launcherw.txt"
16+
default_platform = "-64"
17+
18+
def __init__(self, platform=None):
19+
if platform:
20+
self.default_platform = platform
21+
22+
23+
def __init__(self, tmp_path):
24+
self.Cmd.global_dir = tmp_path / "out"
25+
self.Cmd.launcher_exe = tmp_path / "launcher.txt"
26+
self.Cmd.launcherw_exe = tmp_path / "launcherw.txt"
27+
self._expect_target = "target-" + secrets.token_hex(32)
28+
self._expect = {
29+
"-32": "-32-" + secrets.token_hex(32),
30+
"-64": "-64-" + secrets.token_hex(32),
31+
"-arm64": "-arm64-" + secrets.token_hex(32),
32+
"w-32": "w-32-" + secrets.token_hex(32),
33+
"w-64": "w-64-" + secrets.token_hex(32),
34+
"w-arm64": "w-arm64-" + secrets.token_hex(32),
35+
}
36+
for k, v in self._expect.items():
37+
(tmp_path / f"launcher{k}.txt").write_text(v)
38+
39+
def __enter__(self):
40+
return self
41+
42+
def __exit__(self, *exc_info):
43+
pass
44+
45+
def check(self, cmd, tag, name, expect, windowed=0):
46+
IC._write_alias(
47+
cmd,
48+
{"tag": tag},
49+
{"name": f"{name}.txt", "windowed": windowed},
50+
self._expect_target,
51+
)
52+
print(*cmd.global_dir.glob("*"), sep="\n")
53+
assert (cmd.global_dir / f"{name}.txt").is_file()
54+
assert (cmd.global_dir / f"{name}.txt.__target__").is_file()
55+
assert (cmd.global_dir / f"{name}.txt").read_text() == expect
56+
assert (cmd.global_dir / f"{name}.txt.__target__").read_text() == self._expect_target
57+
58+
def check_32(self, cmd, tag, name):
59+
self.check(cmd, tag, name, self._expect["-32"])
60+
61+
def check_w32(self, cmd, tag, name):
62+
self.check(cmd, tag, name, self._expect["w-32"], windowed=1)
63+
64+
def check_64(self, cmd, tag, name):
65+
self.check(cmd, tag, name, self._expect["-64"])
66+
67+
def check_w64(self, cmd, tag, name):
68+
self.check(cmd, tag, name, self._expect["w-64"], windowed=1)
69+
70+
def check_arm64(self, cmd, tag, name):
71+
self.check(cmd, tag, name, self._expect["-arm64"])
72+
73+
def check_warm64(self, cmd, tag, name):
74+
self.check(cmd, tag, name, self._expect["w-arm64"], windowed=1)
75+
76+
77+
def test_write_alias_tag_with_platform(alias_checker):
78+
alias_checker.check_32(alias_checker.Cmd, "1.0-32", "testA")
79+
alias_checker.check_w32(alias_checker.Cmd, "1.0-32", "testB")
80+
alias_checker.check_64(alias_checker.Cmd, "1.0-64", "testC")
81+
alias_checker.check_w64(alias_checker.Cmd, "1.0-64", "testD")
82+
alias_checker.check_arm64(alias_checker.Cmd, "1.0-arm64", "testE")
83+
alias_checker.check_warm64(alias_checker.Cmd, "1.0-arm64", "testF")
84+
85+
86+
def test_write_alias_default_platform(alias_checker):
87+
alias_checker.check_32(alias_checker.Cmd("-32"), "1.0", "testA")
88+
alias_checker.check_w32(alias_checker.Cmd("-32"), "1.0", "testB")
89+
alias_checker.check_64(alias_checker.Cmd, "1.0", "testC")
90+
alias_checker.check_w64(alias_checker.Cmd, "1.0", "testD")
91+
alias_checker.check_arm64(alias_checker.Cmd("-arm64"), "1.0", "testE")
92+
alias_checker.check_warm64(alias_checker.Cmd("-arm64"), "1.0", "testF")
93+
94+
95+
def test_write_alias_fallback_platform(alias_checker):
96+
alias_checker.check_64(alias_checker.Cmd("-spam"), "1.0", "testA")
97+
alias_checker.check_w64(alias_checker.Cmd("-spam"), "1.0", "testB")

0 commit comments

Comments
 (0)
0