Skip to content

Commit 6c9f005

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 03b668a commit 6c9f005

File tree

4 files changed

+128
-2
lines changed

4 files changed

+128
-2
lines changed

.circleci/config.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,10 +1077,17 @@ jobs:
10771077
EMTEST_BROWSER: "0"
10781078
steps:
10791079
- checkout
1080+
- run:
1081+
name: Setup Visual Stdio
1082+
shell: cmd.exe
1083+
command: dir 'C:\Program Files (x86)\Microsoft Visual Studio'
1084+
#"C:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Auxiliary/Build/vcvarsall.bat" x86
1085+
- run:
1086+
name: Build Python Launcher
1087+
command: cd tools/maint/win32_launcher && ./build.bat
10801088
- run:
10811089
name: Install packages
1082-
command: |
1083-
choco install -y cmake.portable ninja pkgconfiglite
1090+
command: choco install -y cmake.portable ninja pkgconfiglite
10841091
- run:
10851092
name: Add python to bash path
10861093
command: echo "export PATH=\"$PATH:/c/Python27amd64/\"" >> $BASH_ENV

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/launcher.c

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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+
#include <process.h> // For _spawnvp
9+
#include <stdio.h>
10+
#include <stdlib.h> // For _splitpath_s, _makepath_s, GetEnvironmentVariable
11+
#include <string.h>
12+
#include <windows.h>
13+
14+
#if 1
15+
#define dbg printf
16+
#else
17+
#define dbg(...)
18+
#endif
19+
20+
void findPythonExePath(char* pythonExePath, size_t bufferSize) {
21+
char envPythonPath[MAX_PATH]; // Temporary buffer for environment variable
22+
23+
// Attempt to retrieve the value of the "PYTHON" environment variable.
24+
DWORD result = GetEnvironmentVariable("PYTHON", envPythonPath, MAX_PATH);
25+
26+
if (result > 0 && result < MAX_PATH) {
27+
// If the "PYTHON" environment variable is set and its value fits within the
28+
// buffer, use this explicit path. strncpy_s is used for safe string
29+
// copying.
30+
strncpy_s(pythonExePath, bufferSize, envPythonPath, _TRUNCATE);
31+
dbg("Launcher: Using PYTHON environment variable: %s\n", pythonExePath);
32+
return;
33+
}
34+
35+
// If "PYTHON" environment variable is not set or its value is too long,
36+
// default to "python.exe". The _spawnvp function in main() will then
37+
// search for "python.exe" in the system's PATH.
38+
strcpy(pythonExePath, "python.exe");
39+
dbg("Launcher: PYTHON environment variable not found or invalid, defaulting "
40+
"to 'python.exe' (will use PATH search).\n");
41+
}
42+
43+
int main() {
44+
char exePath[MAX_PATH];
45+
char drive[_MAX_DRIVE];
46+
char dir[_MAX_DIR];
47+
char fname[_MAX_FNAME];
48+
char ext[_MAX_EXT];
49+
char pythonScriptPath[MAX_PATH];
50+
char pythonExeToLaunch[MAX_PATH]; // Buffer to store the determined Python
51+
// executable path
52+
53+
// Call the subroutine to determine the Python executable path and its
54+
// discovery method. The return value (discoveryMethod) is no longer used for
55+
// adding -Xutf8.
56+
findPythonExePath(pythonExeToLaunch, MAX_PATH);
57+
58+
// 1. Get the full path of the current executable.
59+
if (GetModuleFileName(NULL, exePath, MAX_PATH) == 0) {
60+
fprintf(stderr, "Error: Could not get executable path (Error code: %lu)\n", GetLastError());
61+
return 1;
62+
}
63+
64+
// 2. Split the executable path into its components (drive, directory, file
65+
// name, extension).
66+
_splitpath_s(exePath, drive, _MAX_DRIVE, dir, _MAX_DIR, fname, _MAX_FNAME, ext, _MAX_EXT);
67+
68+
// 3. Construct the Python script path. It uses the same base name as the
69+
// launcher but with a ".py" extension, residing in the same directory.
70+
_makepath_s(pythonScriptPath, MAX_PATH, drive, dir, fname, ".py");
71+
72+
dbg("Launcher: Executable path: %s\n", exePath);
73+
dbg("Launcher: Python script to run: %s\n", pythonScriptPath);
74+
75+
// 4. Prepare arguments for the Python executable.
76+
// The arguments are now always 3: python_exe, script_path, and NULL
77+
// terminator.
78+
char* argv[] = {
79+
pythonExeToLaunch, // Use the path determined by findPythonExePath
80+
pythonScriptPath,
81+
NULL};
82+
83+
// Check for EM_WORKAROUND_PYTHON_BUG_34780 environment variable
84+
if (GetEnvironmentVariable("EM_WORKAROUND_PYTHON_BUG_34780", NULL, 0) > 0) {
85+
dbg("Launcher: EM_WORKAROUND_PYTHON_BUG_34780 is set so closing stdin\n");
86+
fclose(stdin);
87+
}
88+
89+
// 5. Launch the Python executable and wait for it to finish.
90+
// _spawnvp searches for the program specified in argv[0] in the system's PATH
91+
// (if argv[0] is not a full path) and executes it. _P_WAIT ensures the
92+
// launcher waits for the Python process to complete before continuing.
93+
intptr_t result = _spawnvp(_P_WAIT, argv[0], argv);
94+
95+
if (result == -1) {
96+
// If _spawnvp returns -1, an error occurred during the launch of the child
97+
// process.
98+
perror("Launcher Error: Failed to launch python.exe");
99+
return 1; // Set a specific exit code to indicate launch failure.
100+
}
101+
102+
// If successful, the return value of _spawnvp is the exit code of the spawned
103+
// process.
104+
int exitCode = (int)result;
105+
dbg("Launcher: python.exe exited with code: %d\n", exitCode);
106+
return exitCode;
107+
}

0 commit comments

Comments
 (0)