Skip to content

Commit 3aabac6

Browse files
committed
Add a windows launcher program to replace the current .bat files
This is still just an experiment but eventually I hope to remove the `.bat` files in favor of this executable. There are several reasons to want to do this: 1. Batch files are notoriously painful to work with and mis-understood. 2. Should be faster (no need to launch cmd.exe). 3. Works around several known issues with .bat files including one that is known unsolvable one (see emcc.bat for more details)
1 parent 83b1301 commit 3aabac6

File tree

5 files changed

+180
-0
lines changed

5 files changed

+180
-0
lines changed

tools/maint/win32_launcher/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Windows Python Script Launcher
2+
==============================
3+
4+
This directory contains a simple launcher program for windows which is used to
5+
execute the emscripten compiler entry points using the python interpreter. It
6+
uses the its own name (the name of the currently running executable) to
7+
determine which python script to run and serves the same purpose as the
8+
``run_python.sh`` script does on non-windows platforms.
9+
10+
We build this executable statically using ``/MT`` so that it is maximally
11+
portable.

tools/maint/win32_launcher/build.bat

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
cl launcher.c /Fe:launcher.exe /MT

tools/maint/win32_launcher/build.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/sh
2+
3+
SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
4+
cd ${SCRIPT_DIR}
5+
6+
x86_64-w64-mingw32-gcc -municode -static-libgcc -static-libstdc++ -s -O2 launcher.c -o launcher.exe -lshlwapi -lshell32

tools/maint/win32_launcher/launcher.c

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/*
2+
* Copyright 2025 The Emscripten Authors. All rights reserved.
3+
* Emscripten is available under two separate licenses, the MIT license and the
4+
* University of Illinois/NCSA Open Source License. Both these licenses can be
5+
* found in the LICENSE file.
6+
*/
7+
8+
// Define _WIN32_WINNT to Windows 7 for max portability
9+
#define _WIN32_WINNT 0x0601
10+
11+
#include <windows.h>
12+
#include <shellapi.h>
13+
#include <shlwapi.h>
14+
#include <stdio.h>
15+
#include <stdlib.h>
16+
17+
#pragma comment(lib, "shlwapi.lib")
18+
#pragma comment(lib, "shell32.lib")
19+
20+
wchar_t* get_python_executable() {
21+
wchar_t* python_exe_w = _wgetenv(L"EMSDK_PYTHON");
22+
if (!python_exe_w) {
23+
return L"python";
24+
}
25+
return python_exe_w;
26+
}
27+
28+
wchar_t* get_long_path(const wchar_t* path) {
29+
DWORD path_len = GetFullPathNameW(path, 0, NULL, NULL);
30+
if (path_len == 0)
31+
abort();
32+
33+
wchar_t* full_path = malloc(sizeof(wchar_t) * (path_len + 4));
34+
if (!full_path)
35+
abort();
36+
37+
wcscpy_s(full_path, path_len + 4, L"\\\\?\\");
38+
if (GetFullPathNameW(path, path_len, full_path + 4, NULL) == 0) {
39+
abort();
40+
}
41+
return full_path;
42+
}
43+
44+
// Get the name of the currently running executable (module)
45+
wchar_t* get_module_path() {
46+
DWORD buffer_size = MAX_PATH;
47+
wchar_t* module_path_w = malloc(sizeof(wchar_t) * buffer_size);
48+
if (!module_path_w)
49+
abort();
50+
51+
DWORD path_len = GetModuleFileNameW(NULL, module_path_w, buffer_size);
52+
while (path_len > 0 && path_len == buffer_size) {
53+
buffer_size *= 2;
54+
module_path_w = realloc(module_path_w, sizeof(wchar_t) * buffer_size);
55+
if (!module_path_w)
56+
abort();
57+
path_len = GetModuleFileNameW(NULL, module_path_w, buffer_size);
58+
}
59+
60+
if (path_len == 0)
61+
abort();
62+
63+
return module_path_w;
64+
}
65+
66+
BOOL handle_ccache(wchar_t** application_name, wchar_t** command_line, const wchar_t* launcher_path_w) {
67+
wchar_t* ccache_env = _wgetenv(L"_EMCC_CCACHE");
68+
if (!_wgetenv(L"_EMCC_CCACHE")) {
69+
return FALSE;
70+
}
71+
72+
_wputenv_s(L"_EMCC_CCACHE", L"");
73+
*application_name = L"ccache.exe";
74+
75+
wchar_t* long_launcher_path = get_long_path(launcher_path_w);
76+
77+
size_t command_line_len = wcslen(*application_name) + wcslen(long_launcher_path) + 5;
78+
*command_line = malloc(sizeof(wchar_t) * command_line_len);
79+
if (!*command_line) {
80+
abort();
81+
}
82+
83+
swprintf(*command_line, command_line_len, L"\"%s\" \"%s\"", *application_name, long_launcher_path);
84+
free(long_launcher_path);
85+
return TRUE;
86+
}
87+
88+
int wmain(int argc, wchar_t** argv) {
89+
// Unset _PYTHON_SYSCONFIGDATA_NAME to match batch file behavior.
90+
_wputenv_s(L"_PYTHON_SYSCONFIGDATA_NAME", L"");
91+
92+
wchar_t* launcher_path_w = get_module_path();
93+
94+
wchar_t* launcher_dir = wcsdup(launcher_path_w);
95+
PathRemoveFileSpecW(launcher_dir);
96+
97+
size_t path_len = wcslen(launcher_path_w);
98+
wchar_t* launcher_name_w = malloc((path_len + 1) * sizeof(wchar_t));
99+
if (!launcher_name_w) {
100+
abort();
101+
}
102+
wcscpy_s(launcher_name_w, path_len + 1, launcher_path_w);
103+
PathStripPathW(launcher_name_w);
104+
PathRemoveExtensionW(launcher_name_w);
105+
106+
size_t script_path_len = wcslen(launcher_dir) + wcslen(launcher_name_w) + 6;
107+
wchar_t* script_path_w = malloc(sizeof(wchar_t) * script_path_len);
108+
swprintf(script_path_w, script_path_len, L"%s\\%s.py", launcher_dir, launcher_name_w);
109+
110+
wchar_t* application_name = NULL;
111+
wchar_t* command_line = NULL;
112+
113+
if (!handle_ccache(&application_name, &command_line, launcher_path_w)) {
114+
application_name = get_python_executable();
115+
wchar_t* long_script_path = get_long_path(script_path_w);
116+
size_t command_line_len = wcslen(application_name) + wcslen(long_script_path) + 10;
117+
command_line = malloc(sizeof(wchar_t) * command_line_len);
118+
swprintf(command_line, command_line_len, L"\"%s\" -E \"%s\"", application_name, long_script_path);
119+
free(long_script_path);
120+
}
121+
122+
// Build the final command line
123+
int arg_count;
124+
LPWSTR* arg_list = CommandLineToArgvW(GetCommandLineW(), &arg_count);
125+
for (int i = 1; i < arg_count; i++) {
126+
size_t current_len = wcslen(command_line);
127+
size_t arg_len = wcslen(arg_list[i]);
128+
wchar_t* new_command_line = realloc(command_line, (current_len + arg_len + 2) * sizeof(wchar_t));
129+
if (new_command_line) {
130+
command_line = new_command_line;
131+
wcscat_s(command_line, current_len + arg_len + 2, L" ");
132+
wcscat_s(command_line, current_len + arg_len + 2, arg_list[i]);
133+
}
134+
}
135+
LocalFree(arg_list);
136+
137+
// Work around python bug 34780 by closing stdin, so that it is not inherited
138+
// by the python subprocess.
139+
wchar_t* workaround_env = _wgetenv(L"EM_WORKAROUND_PYTHON_BUG_34780");
140+
if (workaround_env) {
141+
CloseHandle(GetStdHandle(STD_INPUT_HANDLE));
142+
}
143+
144+
STARTUPINFOW si;
145+
PROCESS_INFORMATION pi;
146+
ZeroMemory(&si, sizeof(si));
147+
si.cb = sizeof(si);
148+
ZeroMemory(&pi, sizeof(pi));
149+
150+
if (!CreateProcessW(application_name, command_line, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
151+
wprintf(L"CreateProcess failed (%d).\n", GetLastError());
152+
abort();
153+
}
154+
155+
WaitForSingleObject(pi.hProcess, INFINITE);
156+
157+
DWORD exit_code;
158+
GetExitCodeProcess(pi.hProcess, &exit_code);
159+
CloseHandle(pi.hProcess);
160+
CloseHandle(pi.hThread);
161+
return exit_code;
162+
}
42.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)