Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
emscripten-core
GitHub Repository: emscripten-core/emscripten
Path: blob/main/tools/pylauncher/pylauncher.c
7085 views
1
/*
2
* Copyright 2026 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
* Small win32 application that is used to launcher emscripten via python.exe.
8
* On non-windows platforms this is done via the run_python.sh shell script.
9
*
10
* The binary will look for a python script that matches its own name and run
11
* that using python.exe.
12
*
13
* Built with /NODEFAULTLIB linking only against ucrt.lib (ucrtbase.dll) to
14
* avoid any dependency on a specific Visual C++ Redistributable version.
15
*/
16
17
// Define _WIN32_WINNT to Windows 7 for max portability
18
#define _WIN32_WINNT 0x0601
19
20
#include <windows.h>
21
#include <stdio.h>
22
#include <stdbool.h>
23
#include <stdlib.h>
24
#include <string.h>
25
#include <wchar.h>
26
27
// ZeroMemory expands to memset which lives in vcruntime, not ucrt.
28
// SecureZeroMemory is an inline in <winnt.h> with no runtime dependency.
29
#undef ZeroMemory
30
#define ZeroMemory SecureZeroMemory
31
32
#define WLEN(lit) (sizeof(lit) / sizeof(wchar_t) - 1)
33
34
static bool launcher_debug = false;
35
36
static void dbg(const char* format, ...) {
37
if (launcher_debug) {
38
va_list args;
39
va_start(args, format);
40
vfprintf(stderr, format, args);
41
va_end(args);
42
}
43
}
44
45
static const wchar_t* get_python_executable() {
46
const wchar_t* python_exe_w = _wgetenv(L"EMSDK_PYTHON");
47
if (!python_exe_w) {
48
return L"python.exe";
49
}
50
return python_exe_w;
51
}
52
53
// Get the name of the currently running executable (module)
54
static wchar_t* get_module_path() {
55
DWORD buffer_size = MAX_PATH;
56
wchar_t* module_path_w = malloc(sizeof(wchar_t) * buffer_size);
57
if (!module_path_w)
58
abort();
59
60
DWORD path_len = GetModuleFileNameW(NULL, module_path_w, buffer_size);
61
// Keep doubling buffer size until GetModuleFileNameW returns something
62
// less than the full buffer size
63
while (path_len == buffer_size) {
64
buffer_size *= 2;
65
module_path_w = realloc(module_path_w, sizeof(wchar_t) * buffer_size);
66
if (!module_path_w)
67
abort();
68
path_len = GetModuleFileNameW(NULL, module_path_w, buffer_size);
69
}
70
71
if (path_len == 0)
72
abort();
73
74
return module_path_w;
75
}
76
77
/**
78
* A custom replacement for PathGetArgsW that is safe for command lines
79
* longer than MAX_PATH.
80
*/
81
static const wchar_t* find_args(const wchar_t* command_line) {
82
const wchar_t* p = command_line;
83
84
// Skip past the executable name, which can be quoted.
85
if (*p == L'"') {
86
// The path is quoted, find the closing quote.
87
p++;
88
while (*p) {
89
if (*p == L'"') {
90
p++;
91
break;
92
}
93
p++;
94
}
95
} else {
96
// The path is not quoted, find the first space.
97
while (*p && *p != L' ' && *p != L'\t') {
98
p++;
99
}
100
}
101
102
// Skip any whitespace between the executable and the first argument.
103
while (*p && (*p == L' ' || *p == L'\t')) {
104
p++;
105
}
106
107
return p;
108
}
109
110
static bool path_exists(const wchar_t* path) {
111
return GetFileAttributesW(path) != INVALID_FILE_ATTRIBUTES;
112
}
113
114
/**
115
* Create the script path by finding the launcher path and replacing the
116
* extension with .py. For example `C:\path\to\emcc.exe` becomes
117
* `C:\path\to\emcc.py`.
118
*
119
* If the corresponging .py file does not exist then also look it in the tools
120
* subdirectory. e.g. `C:\path\to\tools\emcc.py`
121
*/
122
static wchar_t* get_script_path() {
123
wchar_t* script_path = get_module_path();
124
if (!script_path)
125
abort();
126
127
size_t path_len = wcslen(script_path);
128
if (path_len < WLEN(L".exe") || _wcsicmp(script_path + path_len - WLEN(L".exe"), L".exe") != 0)
129
abort();
130
// Strip .exe
131
path_len -= WLEN(L".exe");
132
// Append .py (no need to realloc since ".py" is shorter than ".exe")
133
wcscpy(script_path + path_len, L".py");
134
path_len += WLEN(L".py");
135
136
if (path_exists(script_path)) {
137
return script_path;
138
}
139
140
// Python file not found alongside launcher; try under tools
141
// C:\path\to\emcc.py` => C:\path\to\tools\emcc.py`
142
size_t dir_len = 0;
143
for (size_t i = path_len; i > 0; i--) {
144
if (script_path[i - 1] == L'\\') {
145
dir_len = i;
146
break;
147
}
148
}
149
size_t tools_path_size = path_len + WLEN(L"tools\\") + 1;
150
wchar_t* script_path_tools = malloc(tools_path_size * sizeof(wchar_t));
151
swprintf(script_path_tools, tools_path_size, L"%.*lstools\\%ls", (int)dir_len, script_path, script_path + dir_len);
152
153
if (!path_exists(script_path_tools)) {
154
fprintf(stderr, "pylauncher: target python file not found: %ls / %ls\n", script_path, script_path_tools);
155
abort();
156
}
157
free(script_path);
158
159
return script_path_tools;
160
}
161
162
int main() {
163
// Setting EMCC_LAUNCHER_DEBUG enabled debug output for the launcher itself.
164
launcher_debug = GetEnvironmentVariableW(L"EMCC_LAUNCHER_DEBUG", NULL, 0);
165
166
dbg("pylauncher: main\n");
167
168
const wchar_t* ccache_prefix = L"";
169
DWORD env_len = GetEnvironmentVariableW(L"_EMCC_CCACHE", NULL, 0);
170
if (env_len) {
171
dbg("pylauncher: running via ccache.exe\n");
172
ccache_prefix = L"ccache.exe ";
173
SetEnvironmentVariableW(L"_EMCC_CCACHE", NULL);
174
}
175
176
const wchar_t* application_name = get_python_executable();
177
wchar_t* script_path_w = get_script_path();
178
size_t command_line_len = wcslen(ccache_prefix) + wcslen(application_name) + wcslen(script_path_w) + 9;
179
wchar_t* command_line = malloc(sizeof(wchar_t) * command_line_len);
180
swprintf(command_line, command_line_len, L"%ls\"%ls\" -E \"%ls\"", ccache_prefix, application_name, script_path_w);
181
free(script_path_w);
182
183
// -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal
184
// of cpython used in cross compilation via setup.py.
185
SetEnvironmentVariableW(L"_PYTHON_SYSCONFIGDATA_NAME", NULL);
186
187
// Build the final command line by appending the original arguments
188
const wchar_t* all_args = find_args(GetCommandLineW());
189
if (all_args && *all_args) {
190
size_t current_len = wcslen(command_line);
191
size_t args_len = wcslen(all_args);
192
// +2 for the space and the null terminator
193
command_line = realloc(command_line, (current_len + args_len + 2) * sizeof(wchar_t));
194
if (!command_line)
195
abort();
196
wcscat_s(command_line, current_len + args_len + 2, L" ");
197
wcscat_s(command_line, current_len + args_len + 2, all_args);
198
}
199
200
// Work around python bug 34780 by closing stdin, so that it is not inherited
201
// by the python subprocess.
202
env_len = GetEnvironmentVariableW(L"EM_WORKAROUND_PYTHON_BUG_34780", NULL, 0);
203
if (env_len) {
204
dbg("pylauncher: using EM_WORKAROUND_PYTHON_BUG_34780\n");
205
CloseHandle(GetStdHandle(STD_INPUT_HANDLE));
206
}
207
208
STARTUPINFOW si;
209
PROCESS_INFORMATION pi;
210
ZeroMemory(&si, sizeof(si));
211
si.cb = sizeof(si);
212
ZeroMemory(&pi, sizeof(pi));
213
214
dbg("pylauncher: running: %ls\n", command_line);
215
if (!CreateProcessW(NULL, command_line, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
216
fprintf(stderr, "pylauncher: CreateProcess failed (%lu): %ls\n", GetLastError(), command_line);
217
abort();
218
}
219
WaitForSingleObject(pi.hProcess, INFINITE);
220
221
DWORD exit_code;
222
GetExitCodeProcess(pi.hProcess, &exit_code);
223
CloseHandle(pi.hProcess);
224
CloseHandle(pi.hThread);
225
226
dbg("pylauncher: done: %d\n", exit_code);
227
return exit_code;
228
}
229
230