1 /* Setuptools Script Launcher for Windows
2
3 This is a stub executable for Windows that functions somewhat like
4 Effbot's "exemaker", in that it runs a script with the same name but
5 a .py extension, using information from a #! line. It differs in that
6 it spawns the actual Python executable, rather than attempting to
7 hook into the Python DLL. This means that the script will run with
8 sys.executable set to the Python executable, where exemaker ends up with
9 sys.executable pointing to itself. (Which means it won't work if you try
10 to run another Python process using sys.executable.)
11
12 To build/rebuild with mingw32, do this in the setuptools project directory:
13
14 gcc -DGUI=0 -mno-cygwin -O -s -o setuptools/cli.exe launcher.c
15 gcc -DGUI=1 -mwindows -mno-cygwin -O -s -o setuptools/gui.exe launcher.c
16
17 To build for Windows RT, install both Visual Studio Express for Windows 8
18 and for Windows Desktop (both freeware), create "win32" application using
19 "Windows Desktop" version, create new "ARM" target via
20 "Configuration Manager" menu and modify ".vcxproj" file by adding
21 "<WindowsSDKDesktopARMSupport>true</WindowsSDKDesktopARMSupport>" tag
22 as child of "PropertyGroup" tags that has "Debug|ARM" and "Release|ARM"
23 properties.
24
25 It links to msvcrt.dll, but this shouldn't be a problem since it doesn't
26 actually run Python in the same process. Note that using 'exec' instead
27 of 'spawn' doesn't work, because on Windows this leads to the Python
28 executable running in the *background*, attached to the same console
29 window, meaning you get a command prompt back *before* Python even finishes
30 starting. So, we have to use spawnv() and wait for Python to exit before
31 continuing. :(
32 */
33
34 #include <stdlib.h>
35 #include <stdio.h>
36 #include <string.h>
37 #include <windows.h>
38 #include <tchar.h>
39 #include <fcntl.h>
40
41 int child_pid=0;
42
fail(char * format,char * data)43 int fail(char *format, char *data) {
44 /* Print error message to stderr and return 2 */
45 fprintf(stderr, format, data);
46 return 2;
47 }
48
quoted(char * data)49 char *quoted(char *data) {
50 int i, ln = strlen(data), nb;
51
52 /* We allocate twice as much space as needed to deal with worse-case
53 of having to escape everything. */
54 char *result = calloc(ln*2+3, sizeof(char));
55 char *presult = result;
56
57 *presult++ = '"';
58 for (nb=0, i=0; i < ln; i++)
59 {
60 if (data[i] == '\\')
61 nb += 1;
62 else if (data[i] == '"')
63 {
64 for (; nb > 0; nb--)
65 *presult++ = '\\';
66 *presult++ = '\\';
67 }
68 else
69 nb = 0;
70 *presult++ = data[i];
71 }
72
73 for (; nb > 0; nb--) /* Deal w trailing slashes */
74 *presult++ = '\\';
75
76 *presult++ = '"';
77 *presult++ = 0;
78 return result;
79 }
80
81
82
83
84
85
86
87
88
89
loadable_exe(char * exename)90 char *loadable_exe(char *exename) {
91 /* HINSTANCE hPython; DLL handle for python executable */
92 char *result;
93
94 /* hPython = LoadLibraryEx(exename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
95 if (!hPython) return NULL; */
96
97 /* Return the absolute filename for spawnv */
98 result = calloc(MAX_PATH, sizeof(char));
99 strncpy(result, exename, MAX_PATH);
100 /*if (result) GetModuleFileNameA(hPython, result, MAX_PATH);
101
102 FreeLibrary(hPython); */
103 return result;
104 }
105
106
find_exe(char * exename,char * script)107 char *find_exe(char *exename, char *script) {
108 char drive[_MAX_DRIVE], dir[_MAX_DIR], fname[_MAX_FNAME], ext[_MAX_EXT];
109 char path[_MAX_PATH], c, *result;
110
111 /* convert slashes to backslashes for uniform search below */
112 result = exename;
113 while (c = *result++) if (c=='/') result[-1] = '\\';
114
115 _splitpath(exename, drive, dir, fname, ext);
116 if (drive[0] || dir[0]=='\\') {
117 return loadable_exe(exename); /* absolute path, use directly */
118 }
119 /* Use the script's parent directory, which should be the Python home
120 (This should only be used for bdist_wininst-installed scripts, because
121 easy_install-ed scripts use the absolute path to python[w].exe
122 */
123 _splitpath(script, drive, dir, fname, ext);
124 result = dir + strlen(dir) -1;
125 if (*result == '\\') result--;
126 while (*result != '\\' && result>=dir) *result-- = 0;
127 _makepath(path, drive, dir, exename, NULL);
128 return loadable_exe(path);
129 }
130
131
parse_argv(char * cmdline,int * argc)132 char **parse_argv(char *cmdline, int *argc)
133 {
134 /* Parse a command line in-place using MS C rules */
135
136 char **result = calloc(strlen(cmdline), sizeof(char *));
137 char *output = cmdline;
138 char c;
139 int nb = 0;
140 int iq = 0;
141 *argc = 0;
142
143 result[0] = output;
144 while (isspace(*cmdline)) cmdline++; /* skip leading spaces */
145
146 do {
147 c = *cmdline++;
148 if (!c || (isspace(c) && !iq)) {
149 while (nb) {*output++ = '\\'; nb--; }
150 *output++ = 0;
151 result[++*argc] = output;
152 if (!c) return result;
153 while (isspace(*cmdline)) cmdline++; /* skip leading spaces */
154 if (!*cmdline) return result; /* avoid empty arg if trailing ws */
155 continue;
156 }
157 if (c == '\\')
158 ++nb; /* count \'s */
159 else {
160 if (c == '"') {
161 if (!(nb & 1)) { iq = !iq; c = 0; } /* skip " unless odd # of \ */
162 nb = nb >> 1; /* cut \'s in half */
163 }
164 while (nb) {*output++ = '\\'; nb--; }
165 if (c) *output++ = c;
166 }
167 } while (1);
168 }
169
pass_control_to_child(DWORD control_type)170 void pass_control_to_child(DWORD control_type) {
171 /*
172 * distribute-issue207
173 * passes the control event to child process (Python)
174 */
175 if (!child_pid) {
176 return;
177 }
178 GenerateConsoleCtrlEvent(child_pid,0);
179 }
180
control_handler(DWORD control_type)181 BOOL control_handler(DWORD control_type) {
182 /*
183 * distribute-issue207
184 * control event handler callback function
185 */
186 switch (control_type) {
187 case CTRL_C_EVENT:
188 pass_control_to_child(0);
189 break;
190 }
191 return TRUE;
192 }
193
create_and_wait_for_subprocess(char * command)194 int create_and_wait_for_subprocess(char* command) {
195 /*
196 * distribute-issue207
197 * launches child process (Python)
198 */
199 DWORD return_value = 0;
200 LPSTR commandline = command;
201 STARTUPINFOA s_info;
202 PROCESS_INFORMATION p_info;
203 ZeroMemory(&p_info, sizeof(p_info));
204 ZeroMemory(&s_info, sizeof(s_info));
205 s_info.cb = sizeof(STARTUPINFO);
206 // set-up control handler callback funciotn
207 SetConsoleCtrlHandler((PHANDLER_ROUTINE) control_handler, TRUE);
208 if (!CreateProcessA(NULL, commandline, NULL, NULL, TRUE, 0, NULL, NULL, &s_info, &p_info)) {
209 fprintf(stderr, "failed to create process.\n");
210 return 0;
211 }
212 child_pid = p_info.dwProcessId;
213 // wait for Python to exit
214 WaitForSingleObject(p_info.hProcess, INFINITE);
215 if (!GetExitCodeProcess(p_info.hProcess, &return_value)) {
216 fprintf(stderr, "failed to get exit code from process.\n");
217 return 0;
218 }
219 return return_value;
220 }
221
join_executable_and_args(char * executable,char ** args,int argc)222 char* join_executable_and_args(char *executable, char **args, int argc)
223 {
224 /*
225 * distribute-issue207
226 * CreateProcess needs a long string of the executable and command-line arguments,
227 * so we need to convert it from the args that was built
228 */
229 int len,counter;
230 char* cmdline;
231
232 len=strlen(executable)+2;
233 for (counter=1; counter<argc; counter++) {
234 len+=strlen(args[counter])+1;
235 }
236
237 cmdline = (char*)calloc(len, sizeof(char));
238 sprintf(cmdline, "%s", executable);
239 len=strlen(executable);
240 for (counter=1; counter<argc; counter++) {
241 sprintf(cmdline+len, " %s", args[counter]);
242 len+=strlen(args[counter])+1;
243 }
244 return cmdline;
245 }
246
run(int argc,char ** argv,int is_gui)247 int run(int argc, char **argv, int is_gui) {
248
249 char python[256]; /* python executable's filename*/
250 char *pyopt; /* Python option */
251 char script[256]; /* the script's filename */
252
253 int scriptf; /* file descriptor for script file */
254
255 char **newargs, **newargsp, **parsedargs; /* argument array for exec */
256 char *ptr, *end; /* working pointers for string manipulation */
257 char *cmdline;
258 int i, parsedargc; /* loop counter */
259
260 /* compute script name from our .exe name*/
261 GetModuleFileNameA(NULL, script, sizeof(script));
262 end = script + strlen(script);
263 while( end>script && *end != '.')
264 *end-- = '\0';
265 *end-- = '\0';
266 strcat(script, (GUI ? "-script.pyw" : "-script.py"));
267
268 /* figure out the target python executable */
269
270 scriptf = open(script, O_RDONLY);
271 if (scriptf == -1) {
272 return fail("Cannot open %s\n", script);
273 }
274 end = python + read(scriptf, python, sizeof(python));
275 close(scriptf);
276
277 ptr = python-1;
278 while(++ptr < end && *ptr && *ptr!='\n' && *ptr!='\r') {;}
279
280 *ptr-- = '\0';
281
282 if (strncmp(python, "#!", 2)) {
283 /* default to python.exe if no #! header */
284 strcpy(python, "#!python.exe");
285 }
286
287 parsedargs = parse_argv(python+2, &parsedargc);
288
289 /* Using spawnv() can fail strangely if you e.g. find the Cygwin
290 Python, so we'll make sure Windows can find and load it */
291
292 ptr = find_exe(parsedargs[0], script);
293 if (!ptr) {
294 return fail("Cannot find Python executable %s\n", parsedargs[0]);
295 }
296
297 /* printf("Python executable: %s\n", ptr); */
298
299 /* Argument array needs to be
300 parsedargc + argc, plus 1 for null sentinel */
301
302 newargs = (char **)calloc(parsedargc + argc + 1, sizeof(char *));
303 newargsp = newargs;
304
305 *newargsp++ = quoted(ptr);
306 for (i = 1; i<parsedargc; i++) *newargsp++ = quoted(parsedargs[i]);
307
308 *newargsp++ = quoted(script);
309 for (i = 1; i < argc; i++) *newargsp++ = quoted(argv[i]);
310
311 *newargsp++ = NULL;
312
313 /* printf("args 0: %s\nargs 1: %s\n", newargs[0], newargs[1]); */
314
315 if (is_gui) {
316 /* Use exec, we don't need to wait for the GUI to finish */
317 execv(ptr, (const char * const *)(newargs));
318 return fail("Could not exec %s", ptr); /* shouldn't get here! */
319 }
320
321 /*
322 * distribute-issue207: using CreateProcessA instead of spawnv
323 */
324 cmdline = join_executable_and_args(ptr, newargs, parsedargc + argc);
325 return create_and_wait_for_subprocess(cmdline);
326 }
327
WinMain(HINSTANCE hI,HINSTANCE hP,LPSTR lpCmd,int nShow)328 int WINAPI WinMain(HINSTANCE hI, HINSTANCE hP, LPSTR lpCmd, int nShow) {
329 return run(__argc, __argv, GUI);
330 }
331
main(int argc,char ** argv)332 int main(int argc, char** argv) {
333 return run(argc, argv, GUI);
334 }
335
336