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 #include <process.h>
41
42 int child_pid=0;
43
fail(char * format,char * data)44 int fail(char *format, char *data) {
45 /* Print error message to stderr and return 2 */
46 fprintf(stderr, format, data);
47 return 2;
48 }
49
quoted(char * data)50 char *quoted(char *data) {
51 int i, ln = strlen(data), nb;
52
53 /* We allocate twice as much space as needed to deal with worse-case
54 of having to escape everything. */
55 char *result = calloc(ln*2+3, sizeof(char));
56 char *presult = result;
57
58 *presult++ = '"';
59 for (nb=0, i=0; i < ln; i++)
60 {
61 if (data[i] == '\\')
62 nb += 1;
63 else if (data[i] == '"')
64 {
65 for (; nb > 0; nb--)
66 *presult++ = '\\';
67 *presult++ = '\\';
68 }
69 else
70 nb = 0;
71 *presult++ = data[i];
72 }
73
74 for (; nb > 0; nb--) /* Deal w trailing slashes */
75 *presult++ = '\\';
76
77 *presult++ = '"';
78 *presult++ = 0;
79 return result;
80 }
81
82
83
84
85
86
87
88
89
90
loadable_exe(char * exename)91 char *loadable_exe(char *exename) {
92 /* HINSTANCE hPython; DLL handle for python executable */
93 char *result;
94
95 /* hPython = LoadLibraryEx(exename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
96 if (!hPython) return NULL; */
97
98 /* Return the absolute filename for spawnv */
99 result = calloc(MAX_PATH, sizeof(char));
100 strncpy(result, exename, MAX_PATH);
101 /*if (result) GetModuleFileNameA(hPython, result, MAX_PATH);
102
103 FreeLibrary(hPython); */
104 return result;
105 }
106
107
find_exe(char * exename,char * script)108 char *find_exe(char *exename, char *script) {
109 char drive[_MAX_DRIVE], dir[_MAX_DIR], fname[_MAX_FNAME], ext[_MAX_EXT];
110 char path[_MAX_PATH], c, *result;
111
112 /* convert slashes to backslashes for uniform search below */
113 result = exename;
114 while (c = *result++) if (c=='/') result[-1] = '\\';
115
116 _splitpath(exename, drive, dir, fname, ext);
117 if (drive[0] || dir[0]=='\\') {
118 return loadable_exe(exename); /* absolute path, use directly */
119 }
120 /* Use the script's parent directory, which should be the Python home
121 (This should only be used for bdist_wininst-installed scripts, because
122 easy_install-ed scripts use the absolute path to python[w].exe
123 */
124 _splitpath(script, drive, dir, fname, ext);
125 result = dir + strlen(dir) -1;
126 if (*result == '\\') result--;
127 while (*result != '\\' && result>=dir) *result-- = 0;
128 _makepath(path, drive, dir, exename, NULL);
129 return loadable_exe(path);
130 }
131
132
parse_argv(char * cmdline,int * argc)133 char **parse_argv(char *cmdline, int *argc)
134 {
135 /* Parse a command line in-place using MS C rules */
136
137 char **result = calloc(strlen(cmdline), sizeof(char *));
138 char *output = cmdline;
139 char c;
140 int nb = 0;
141 int iq = 0;
142 *argc = 0;
143
144 result[0] = output;
145 while (isspace(*cmdline)) cmdline++; /* skip leading spaces */
146
147 do {
148 c = *cmdline++;
149 if (!c || (isspace(c) && !iq)) {
150 while (nb) {*output++ = '\\'; nb--; }
151 *output++ = 0;
152 result[++*argc] = output;
153 if (!c) return result;
154 while (isspace(*cmdline)) cmdline++; /* skip leading spaces */
155 if (!*cmdline) return result; /* avoid empty arg if trailing ws */
156 continue;
157 }
158 if (c == '\\')
159 ++nb; /* count \'s */
160 else {
161 if (c == '"') {
162 if (!(nb & 1)) { iq = !iq; c = 0; } /* skip " unless odd # of \ */
163 nb = nb >> 1; /* cut \'s in half */
164 }
165 while (nb) {*output++ = '\\'; nb--; }
166 if (c) *output++ = c;
167 }
168 } while (1);
169 }
170
pass_control_to_child(DWORD control_type)171 void pass_control_to_child(DWORD control_type) {
172 /*
173 * distribute-issue207
174 * passes the control event to child process (Python)
175 */
176 if (!child_pid) {
177 return;
178 }
179 GenerateConsoleCtrlEvent(child_pid,0);
180 }
181
control_handler(DWORD control_type)182 BOOL control_handler(DWORD control_type) {
183 /*
184 * distribute-issue207
185 * control event handler callback function
186 */
187 switch (control_type) {
188 case CTRL_C_EVENT:
189 pass_control_to_child(0);
190 break;
191 }
192 return TRUE;
193 }
194
create_and_wait_for_subprocess(char * command)195 int create_and_wait_for_subprocess(char* command) {
196 /*
197 * distribute-issue207
198 * launches child process (Python)
199 */
200 DWORD return_value = 0;
201 LPSTR commandline = command;
202 STARTUPINFOA s_info;
203 PROCESS_INFORMATION p_info;
204 ZeroMemory(&p_info, sizeof(p_info));
205 ZeroMemory(&s_info, sizeof(s_info));
206 s_info.cb = sizeof(STARTUPINFO);
207 // set-up control handler callback funciotn
208 SetConsoleCtrlHandler((PHANDLER_ROUTINE) control_handler, TRUE);
209 if (!CreateProcessA(NULL, commandline, NULL, NULL, TRUE, 0, NULL, NULL, &s_info, &p_info)) {
210 fprintf(stderr, "failed to create process.\n");
211 return 0;
212 }
213 child_pid = p_info.dwProcessId;
214 // wait for Python to exit
215 WaitForSingleObject(p_info.hProcess, INFINITE);
216 if (!GetExitCodeProcess(p_info.hProcess, &return_value)) {
217 fprintf(stderr, "failed to get exit code from process.\n");
218 return 0;
219 }
220 return return_value;
221 }
222
join_executable_and_args(char * executable,char ** args,int argc)223 char* join_executable_and_args(char *executable, char **args, int argc)
224 {
225 /*
226 * distribute-issue207
227 * CreateProcess needs a long string of the executable and command-line arguments,
228 * so we need to convert it from the args that was built
229 */
230 int len,counter;
231 char* cmdline;
232
233 len=strlen(executable)+2;
234 for (counter=1; counter<argc; counter++) {
235 len+=strlen(args[counter])+1;
236 }
237
238 cmdline = (char*)calloc(len, sizeof(char));
239 sprintf(cmdline, "%s", executable);
240 len=strlen(executable);
241 for (counter=1; counter<argc; counter++) {
242 sprintf(cmdline+len, " %s", args[counter]);
243 len+=strlen(args[counter])+1;
244 }
245 return cmdline;
246 }
247
run(int argc,char ** argv,int is_gui)248 int run(int argc, char **argv, int is_gui) {
249
250 char python[256]; /* python executable's filename*/
251 char *pyopt; /* Python option */
252 char script[256]; /* the script's filename */
253
254 int scriptf; /* file descriptor for script file */
255
256 char **newargs, **newargsp, **parsedargs; /* argument array for exec */
257 char *ptr, *end; /* working pointers for string manipulation */
258 char *cmdline;
259 int i, parsedargc; /* loop counter */
260
261 /* compute script name from our .exe name*/
262 GetModuleFileNameA(NULL, script, sizeof(script));
263 end = script + strlen(script);
264 while( end>script && *end != '.')
265 *end-- = '\0';
266 *end-- = '\0';
267 strcat(script, (GUI ? "-script.pyw" : "-script.py"));
268
269 /* figure out the target python executable */
270
271 scriptf = open(script, O_RDONLY);
272 if (scriptf == -1) {
273 return fail("Cannot open %s\n", script);
274 }
275 end = python + read(scriptf, python, sizeof(python));
276 close(scriptf);
277
278 ptr = python-1;
279 while(++ptr < end && *ptr && *ptr!='\n' && *ptr!='\r') {;}
280
281 *ptr-- = '\0';
282
283 if (strncmp(python, "#!", 2)) {
284 /* default to python.exe if no #! header */
285 strcpy(python, "#!python.exe");
286 }
287
288 parsedargs = parse_argv(python+2, &parsedargc);
289
290 /* Using spawnv() can fail strangely if you e.g. find the Cygwin
291 Python, so we'll make sure Windows can find and load it */
292
293 ptr = find_exe(parsedargs[0], script);
294 if (!ptr) {
295 return fail("Cannot find Python executable %s\n", parsedargs[0]);
296 }
297
298 /* printf("Python executable: %s\n", ptr); */
299
300 /* Argument array needs to be
301 parsedargc + argc, plus 1 for null sentinel */
302
303 newargs = (char **)calloc(parsedargc + argc + 1, sizeof(char *));
304 newargsp = newargs;
305
306 *newargsp++ = quoted(ptr);
307 for (i = 1; i<parsedargc; i++) *newargsp++ = quoted(parsedargs[i]);
308
309 *newargsp++ = quoted(script);
310 for (i = 1; i < argc; i++) *newargsp++ = quoted(argv[i]);
311
312 *newargsp++ = NULL;
313
314 /* printf("args 0: %s\nargs 1: %s\n", newargs[0], newargs[1]); */
315
316 if (is_gui) {
317 /* Use exec, we don't need to wait for the GUI to finish */
318 execv(ptr, (const char * const *)(newargs));
319 return fail("Could not exec %s", ptr); /* shouldn't get here! */
320 }
321
322 /*
323 * distribute-issue207: using CreateProcessA instead of spawnv
324 */
325 cmdline = join_executable_and_args(ptr, newargs, parsedargc + argc);
326 return create_and_wait_for_subprocess(cmdline);
327 }
328
WinMain(HINSTANCE hI,HINSTANCE hP,LPSTR lpCmd,int nShow)329 int WINAPI WinMain(HINSTANCE hI, HINSTANCE hP, LPSTR lpCmd, int nShow) {
330 return run(__argc, __argv, GUI);
331 }
332
main(int argc,char ** argv)333 int main(int argc, char** argv) {
334 return run(argc, argv, GUI);
335 }
336
337