| Nan Zhang | 8539a2a | 2018-05-15 14:00:05 -0700 | [diff] [blame] | 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 |  | 
 | 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 |  | 
 | 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 |  | 
 | 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 |  | 
 | 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 |  | 
 | 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 |  | 
 | 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 |  | 
 | 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 |  | 
 | 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 |  | 
 | 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 |  | 
 | 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 |  | 
 | 328 | int WINAPI WinMain(HINSTANCE hI, HINSTANCE hP, LPSTR lpCmd, int nShow) { | 
 | 329 |     return run(__argc, __argv, GUI); | 
 | 330 | } | 
 | 331 |  | 
 | 332 | int main(int argc, char** argv) { | 
 | 333 |     return run(argc, argv, GUI); | 
 | 334 | } | 
 | 335 |  |