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