Path: blob/main/tools/pylauncher/pylauncher.c
7085 views
/*1* Copyright 2026 The Emscripten Authors. All rights reserved.2* Emscripten is available under two separate licenses, the MIT license and the3* University of Illinois/NCSA Open Source License. Both these licenses can be4* found in the LICENSE file.5*6* Small win32 application that is used to launcher emscripten via python.exe.7* On non-windows platforms this is done via the run_python.sh shell script.8*9* The binary will look for a python script that matches its own name and run10* that using python.exe.11*12* Built with /NODEFAULTLIB linking only against ucrt.lib (ucrtbase.dll) to13* avoid any dependency on a specific Visual C++ Redistributable version.14*/1516// Define _WIN32_WINNT to Windows 7 for max portability17#define _WIN32_WINNT 0x06011819#include <windows.h>20#include <stdio.h>21#include <stdbool.h>22#include <stdlib.h>23#include <string.h>24#include <wchar.h>2526// ZeroMemory expands to memset which lives in vcruntime, not ucrt.27// SecureZeroMemory is an inline in <winnt.h> with no runtime dependency.28#undef ZeroMemory29#define ZeroMemory SecureZeroMemory3031#define WLEN(lit) (sizeof(lit) / sizeof(wchar_t) - 1)3233static bool launcher_debug = false;3435static void dbg(const char* format, ...) {36if (launcher_debug) {37va_list args;38va_start(args, format);39vfprintf(stderr, format, args);40va_end(args);41}42}4344static const wchar_t* get_python_executable() {45const wchar_t* python_exe_w = _wgetenv(L"EMSDK_PYTHON");46if (!python_exe_w) {47return L"python.exe";48}49return python_exe_w;50}5152// Get the name of the currently running executable (module)53static wchar_t* get_module_path() {54DWORD buffer_size = MAX_PATH;55wchar_t* module_path_w = malloc(sizeof(wchar_t) * buffer_size);56if (!module_path_w)57abort();5859DWORD path_len = GetModuleFileNameW(NULL, module_path_w, buffer_size);60// Keep doubling buffer size until GetModuleFileNameW returns something61// less than the full buffer size62while (path_len == buffer_size) {63buffer_size *= 2;64module_path_w = realloc(module_path_w, sizeof(wchar_t) * buffer_size);65if (!module_path_w)66abort();67path_len = GetModuleFileNameW(NULL, module_path_w, buffer_size);68}6970if (path_len == 0)71abort();7273return module_path_w;74}7576/**77* A custom replacement for PathGetArgsW that is safe for command lines78* longer than MAX_PATH.79*/80static const wchar_t* find_args(const wchar_t* command_line) {81const wchar_t* p = command_line;8283// Skip past the executable name, which can be quoted.84if (*p == L'"') {85// The path is quoted, find the closing quote.86p++;87while (*p) {88if (*p == L'"') {89p++;90break;91}92p++;93}94} else {95// The path is not quoted, find the first space.96while (*p && *p != L' ' && *p != L'\t') {97p++;98}99}100101// Skip any whitespace between the executable and the first argument.102while (*p && (*p == L' ' || *p == L'\t')) {103p++;104}105106return p;107}108109static bool path_exists(const wchar_t* path) {110return GetFileAttributesW(path) != INVALID_FILE_ATTRIBUTES;111}112113/**114* Create the script path by finding the launcher path and replacing the115* extension with .py. For example `C:\path\to\emcc.exe` becomes116* `C:\path\to\emcc.py`.117*118* If the corresponging .py file does not exist then also look it in the tools119* subdirectory. e.g. `C:\path\to\tools\emcc.py`120*/121static wchar_t* get_script_path() {122wchar_t* script_path = get_module_path();123if (!script_path)124abort();125126size_t path_len = wcslen(script_path);127if (path_len < WLEN(L".exe") || _wcsicmp(script_path + path_len - WLEN(L".exe"), L".exe") != 0)128abort();129// Strip .exe130path_len -= WLEN(L".exe");131// Append .py (no need to realloc since ".py" is shorter than ".exe")132wcscpy(script_path + path_len, L".py");133path_len += WLEN(L".py");134135if (path_exists(script_path)) {136return script_path;137}138139// Python file not found alongside launcher; try under tools140// C:\path\to\emcc.py` => C:\path\to\tools\emcc.py`141size_t dir_len = 0;142for (size_t i = path_len; i > 0; i--) {143if (script_path[i - 1] == L'\\') {144dir_len = i;145break;146}147}148size_t tools_path_size = path_len + WLEN(L"tools\\") + 1;149wchar_t* script_path_tools = malloc(tools_path_size * sizeof(wchar_t));150swprintf(script_path_tools, tools_path_size, L"%.*lstools\\%ls", (int)dir_len, script_path, script_path + dir_len);151152if (!path_exists(script_path_tools)) {153fprintf(stderr, "pylauncher: target python file not found: %ls / %ls\n", script_path, script_path_tools);154abort();155}156free(script_path);157158return script_path_tools;159}160161int main() {162// Setting EMCC_LAUNCHER_DEBUG enabled debug output for the launcher itself.163launcher_debug = GetEnvironmentVariableW(L"EMCC_LAUNCHER_DEBUG", NULL, 0);164165dbg("pylauncher: main\n");166167const wchar_t* ccache_prefix = L"";168DWORD env_len = GetEnvironmentVariableW(L"_EMCC_CCACHE", NULL, 0);169if (env_len) {170dbg("pylauncher: running via ccache.exe\n");171ccache_prefix = L"ccache.exe ";172SetEnvironmentVariableW(L"_EMCC_CCACHE", NULL);173}174175const wchar_t* application_name = get_python_executable();176wchar_t* script_path_w = get_script_path();177size_t command_line_len = wcslen(ccache_prefix) + wcslen(application_name) + wcslen(script_path_w) + 9;178wchar_t* command_line = malloc(sizeof(wchar_t) * command_line_len);179swprintf(command_line, command_line_len, L"%ls\"%ls\" -E \"%ls\"", ccache_prefix, application_name, script_path_w);180free(script_path_w);181182// -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal183// of cpython used in cross compilation via setup.py.184SetEnvironmentVariableW(L"_PYTHON_SYSCONFIGDATA_NAME", NULL);185186// Build the final command line by appending the original arguments187const wchar_t* all_args = find_args(GetCommandLineW());188if (all_args && *all_args) {189size_t current_len = wcslen(command_line);190size_t args_len = wcslen(all_args);191// +2 for the space and the null terminator192command_line = realloc(command_line, (current_len + args_len + 2) * sizeof(wchar_t));193if (!command_line)194abort();195wcscat_s(command_line, current_len + args_len + 2, L" ");196wcscat_s(command_line, current_len + args_len + 2, all_args);197}198199// Work around python bug 34780 by closing stdin, so that it is not inherited200// by the python subprocess.201env_len = GetEnvironmentVariableW(L"EM_WORKAROUND_PYTHON_BUG_34780", NULL, 0);202if (env_len) {203dbg("pylauncher: using EM_WORKAROUND_PYTHON_BUG_34780\n");204CloseHandle(GetStdHandle(STD_INPUT_HANDLE));205}206207STARTUPINFOW si;208PROCESS_INFORMATION pi;209ZeroMemory(&si, sizeof(si));210si.cb = sizeof(si);211ZeroMemory(&pi, sizeof(pi));212213dbg("pylauncher: running: %ls\n", command_line);214if (!CreateProcessW(NULL, command_line, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {215fprintf(stderr, "pylauncher: CreateProcess failed (%lu): %ls\n", GetLastError(), command_line);216abort();217}218WaitForSingleObject(pi.hProcess, INFINITE);219220DWORD exit_code;221GetExitCodeProcess(pi.hProcess, &exit_code);222CloseHandle(pi.hProcess);223CloseHandle(pi.hThread);224225dbg("pylauncher: done: %d\n", exit_code);226return exit_code;227}228229230