|
| 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 | + * Small win32 application that is used to launcher emscripten via python.exe. |
| 8 | + * On non-windows platforms this is done via the run_pyton.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 | + |
| 14 | +// Define _WIN32_WINNT to Windows 7 for max portability |
| 15 | +#define _WIN32_WINNT 0x0601 |
| 16 | + |
| 17 | +#include <windows.h> |
| 18 | +#include <shellapi.h> |
| 19 | +#include <shlwapi.h> |
| 20 | +#include <stdio.h> |
| 21 | +#include <stdlib.h> |
| 22 | + |
| 23 | +#pragma comment(lib, "shlwapi.lib") |
| 24 | +#pragma comment(lib, "shell32.lib") |
| 25 | + |
| 26 | +wchar_t* get_python_executable() { |
| 27 | + wchar_t* python_exe_w = _wgetenv(L"EMSDK_PYTHON"); |
| 28 | + if (!python_exe_w) { |
| 29 | + return L"python"; |
| 30 | + } |
| 31 | + return python_exe_w; |
| 32 | +} |
| 33 | + |
| 34 | +wchar_t* get_long_path(const wchar_t* path) { |
| 35 | + DWORD path_len = GetFullPathNameW(path, 0, NULL, NULL); |
| 36 | + if (path_len == 0) |
| 37 | + abort(); |
| 38 | + |
| 39 | + wchar_t* full_path = malloc(sizeof(wchar_t) * (path_len + 4)); |
| 40 | + if (!full_path) |
| 41 | + abort(); |
| 42 | + |
| 43 | + wcscpy_s(full_path, path_len + 4, L"\\\\?\\"); |
| 44 | + if (GetFullPathNameW(path, path_len, full_path + 4, NULL) == 0) |
| 45 | + abort(); |
| 46 | + return full_path; |
| 47 | +} |
| 48 | + |
| 49 | +// Get the name of the currently running executable (module) |
| 50 | +wchar_t* get_module_path() { |
| 51 | + DWORD buffer_size = MAX_PATH; |
| 52 | + wchar_t* module_path_w = malloc(sizeof(wchar_t) * buffer_size); |
| 53 | + if (!module_path_w) |
| 54 | + abort(); |
| 55 | + |
| 56 | + DWORD path_len = GetModuleFileNameW(NULL, module_path_w, buffer_size); |
| 57 | + while (path_len > 0 && path_len == buffer_size) { |
| 58 | + buffer_size *= 2; |
| 59 | + module_path_w = realloc(module_path_w, sizeof(wchar_t) * buffer_size); |
| 60 | + if (!module_path_w) |
| 61 | + abort(); |
| 62 | + path_len = GetModuleFileNameW(NULL, module_path_w, buffer_size); |
| 63 | + } |
| 64 | + |
| 65 | + if (path_len == 0) |
| 66 | + abort(); |
| 67 | + |
| 68 | + return module_path_w; |
| 69 | +} |
| 70 | + |
| 71 | +// Handle the _EMCC_CCACHE environment variable which essentially re-executes |
| 72 | +// the lauccher command with `cacche.exe` prefixed. |
| 73 | +BOOL handle_ccache(wchar_t** application_name, wchar_t** command_line, const wchar_t* launcher_path_w) { |
| 74 | + const wchar_t* ccache_env = _wgetenv(L"_EMCC_CCACHE"); |
| 75 | + if (!ccache_env || !*ccache_env) { |
| 76 | + return FALSE; |
| 77 | + } |
| 78 | + |
| 79 | + _wputenv_s(L"_EMCC_CCACHE", L""); |
| 80 | + *application_name = L"ccache.exe"; |
| 81 | + |
| 82 | + size_t command_line_len = wcslen(*application_name) + wcslen(launcher_path_w) + 5; |
| 83 | + *command_line = malloc(sizeof(wchar_t) * command_line_len); |
| 84 | + if (!*command_line) |
| 85 | + abort(); |
| 86 | + |
| 87 | + swprintf(*command_line, command_line_len, L"\"%s\" \"%s\"", *application_name, launcher_path_w); |
| 88 | + return TRUE; |
| 89 | +} |
| 90 | + |
| 91 | +// A custom replacement for PathGetArgsW that is safe for command lines |
| 92 | +// longer than MAX_PATH. |
| 93 | +const wchar_t* find_args(const wchar_t* command_line) { |
| 94 | + const wchar_t* p = command_line; |
| 95 | + |
| 96 | + // Skip past the executable name, which can be quoted. |
| 97 | + if (*p == L'"') { |
| 98 | + // The path is quoted, find the closing quote. |
| 99 | + p++; |
| 100 | + while (*p) { |
| 101 | + if (*p == L'"') { |
| 102 | + p++; |
| 103 | + break; |
| 104 | + } |
| 105 | + p++; |
| 106 | + } |
| 107 | + } else { |
| 108 | + // The path is not quoted, find the first space. |
| 109 | + while (*p && *p != L' ' && *p != L'\t') { |
| 110 | + p++; |
| 111 | + } |
| 112 | + } |
| 113 | + |
| 114 | + // Skip any whitespace between the executable and the first argument. |
| 115 | + while (*p && (*p == L' ' || *p == L'\t')) { |
| 116 | + p++; |
| 117 | + } |
| 118 | + |
| 119 | + return p; |
| 120 | +} |
| 121 | + |
| 122 | +int wmain(int argc, wchar_t** argv) { |
| 123 | + wchar_t* launcher_path_w = get_module_path(); |
| 124 | + |
| 125 | + wchar_t* launcher_dir = wcsdup(launcher_path_w); |
| 126 | + PathRemoveFileSpecW(launcher_dir); |
| 127 | + |
| 128 | + size_t path_len = wcslen(launcher_path_w); |
| 129 | + wchar_t* launcher_name_w = malloc((path_len + 1) * sizeof(wchar_t)); |
| 130 | + if (!launcher_name_w) |
| 131 | + abort(); |
| 132 | + wcscpy_s(launcher_name_w, path_len + 1, launcher_path_w); |
| 133 | + PathStripPathW(launcher_name_w); |
| 134 | + PathRemoveExtensionW(launcher_name_w); |
| 135 | + |
| 136 | + size_t script_path_len = wcslen(launcher_dir) + wcslen(launcher_name_w) + 6; |
| 137 | + wchar_t* script_path_w = malloc(sizeof(wchar_t) * script_path_len); |
| 138 | + swprintf(script_path_w, script_path_len, L"%s\%s.py", launcher_dir, launcher_name_w); |
| 139 | + |
| 140 | + wchar_t* application_name = NULL; |
| 141 | + wchar_t* command_line = NULL; |
| 142 | + |
| 143 | + if (!handle_ccache(&application_name, &command_line, launcher_path_w)) { |
| 144 | + application_name = get_python_executable(); |
| 145 | + wchar_t* long_script_path = get_long_path(script_path_w); |
| 146 | + size_t command_line_len = wcslen(application_name) + wcslen(long_script_path) + 10; |
| 147 | + command_line = malloc(sizeof(wchar_t) * command_line_len); |
| 148 | + swprintf(command_line, command_line_len, L"\"%s\" -E \"%s\"", application_name, long_script_path); |
| 149 | + free(long_script_path); |
| 150 | + // -E will not ignore _PYTHON_SYSCONFIGDATA_NAME an internal |
| 151 | + // of cpython used in cross compilation via setup.py. |
| 152 | + _wputenv_s(L"_PYTHON_SYSCONFIGDATA_NAME", L""); |
| 153 | + } |
| 154 | + |
| 155 | + // Build the final command line by appending the original arguments |
| 156 | + const wchar_t* all_args = find_args(GetCommandLineW()); |
| 157 | + if (all_args && *all_args) { |
| 158 | + size_t current_len = wcslen(command_line); |
| 159 | + size_t args_len = wcslen(all_args); |
| 160 | + // +2 for the space and the null terminator |
| 161 | + command_line = realloc(command_line, (current_len + args_len + 2) * sizeof(wchar_t)); |
| 162 | + if (!command_line) |
| 163 | + abort(); |
| 164 | + wcscat_s(command_line, current_len + args_len + 2, L" "); |
| 165 | + wcscat_s(command_line, current_len + args_len + 2, all_args); |
| 166 | + } |
| 167 | + |
| 168 | + |
| 169 | + // Work around python bug 34780 by closing stdin, so that it is not inherited |
| 170 | + // by the python subprocess. |
| 171 | + wchar_t* workaround_env = _wgetenv(L"EM_WORKAROUND_PYTHON_BUG_34780"); |
| 172 | + if (workaround_env) { |
| 173 | + CloseHandle(GetStdHandle(STD_INPUT_HANDLE)); |
| 174 | + } |
| 175 | + |
| 176 | + STARTUPINFOW si; |
| 177 | + PROCESS_INFORMATION pi; |
| 178 | + ZeroMemory(&si, sizeof(si)); |
| 179 | + si.cb = sizeof(si); |
| 180 | + ZeroMemory(&pi, sizeof(pi)); |
| 181 | + |
| 182 | + if (!CreateProcessW(application_name, command_line, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) { |
| 183 | + wprintf(L"CreateProcess failed (%d).\n", GetLastError()); |
| 184 | + abort(); |
| 185 | + } |
| 186 | + |
| 187 | + WaitForSingleObject(pi.hProcess, INFINITE); |
| 188 | + |
| 189 | + DWORD exit_code; |
| 190 | + GetExitCodeProcess(pi.hProcess, &exit_code); |
| 191 | + CloseHandle(pi.hProcess); |
| 192 | + CloseHandle(pi.hThread); |
| 193 | + return exit_code; |
| 194 | +} |
0 commit comments