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