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. */
5 /* Copyright (C) 2016 Jason R Coombs <jaraco@jaraco.com>
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:
14 The above copyright notice and this permission notice shall be included in all
15 copies or substantial portions of the Software.
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
25 /* Setuptools Script Launcher for Windows
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.)
36 To build/rebuild with mingw32, do this in the setuptools project directory:
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
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"
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
67 int fail(char *format
, char *data
) {
68 /* Print error message to stderr and return 2 */
69 fprintf(stderr
, format
, data
);
73 char *quoted(char *data
) {
74 int i
, ln
= strlen(data
), nb
;
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
;
82 for (nb
=0, i
=0; i
< ln
; i
++)
86 else if (data
[i
] == '"')
97 for (; nb
> 0; nb
--) /* Deal w trailing slashes */
114 char *loadable_exe(char *exename
) {
115 /* HINSTANCE hPython; DLL handle for python executable */
118 /* hPython = LoadLibraryEx(exename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
119 if (!hPython) return NULL; */
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);
126 FreeLibrary(hPython); */
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
;
135 /* convert slashes to backslashes for uniform search below */
137 while (c
= *result
++) if (c
=='/') result
[-1] = '\\';
139 _splitpath(exename
, drive
, dir
, fname
, ext
);
140 if (drive
[0] || dir
[0]=='\\') {
141 return loadable_exe(exename
); /* absolute path, use directly */
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
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
);
156 char **parse_argv(char *cmdline
, int *argc
)
158 /* Parse a command line in-place using MS C rules */
160 char **result
= calloc(strlen(cmdline
), sizeof(char *));
161 char *output
= cmdline
;
168 while (isspace(*cmdline
)) cmdline
++; /* skip leading spaces */
172 if (!c
|| (isspace(c
) && !iq
)) {
173 while (nb
) {*output
++ = '\\'; nb
--; }
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 */
182 ++nb
; /* count \'s */
185 if (!(nb
& 1)) { iq
= !iq
; c
= 0; } /* skip " unless odd # of \ */
186 nb
= nb
>> 1; /* cut \'s in half */
188 while (nb
) {*output
++ = '\\'; nb
--; }
189 if (c
) *output
++ = c
;
194 void pass_control_to_child(DWORD control_type
) {
196 * distribute-issue207
197 * passes the control event to child process (Python)
202 GenerateConsoleCtrlEvent(child_pid
,0);
205 BOOL
control_handler(DWORD control_type
) {
207 * distribute-issue207
208 * control event handler callback function
210 switch (control_type
) {
212 pass_control_to_child(0);
218 int create_and_wait_for_subprocess(char* command
) {
220 * distribute-issue207
221 * launches child process (Python)
223 DWORD return_value
= 0;
224 LPSTR commandline
= command
;
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");
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");
246 char* join_executable_and_args(char *executable
, char **args
, int argc
)
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
256 len
=strlen(executable
)+2;
257 for (counter
=1; counter
<argc
; counter
++) {
258 len
+=strlen(args
[counter
])+1;
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;
271 int run(int argc
, char **argv
, int is_gui
) {
273 char python
[256]; /* python executable's filename*/
274 char *pyopt
; /* Python option */
275 char script
[256]; /* the script's filename */
277 int scriptf
; /* file descriptor for script file */
279 char **newargs
, **newargsp
, **parsedargs
; /* argument array for exec */
280 char *ptr
, *end
; /* working pointers for string manipulation */
282 int i
, parsedargc
; /* loop counter */
284 /* compute script name from our .exe name*/
285 GetModuleFileNameA(NULL
, script
, sizeof(script
));
286 end
= script
+ strlen(script
);
287 while( end
>script
&& *end
!= '.')
290 strcat(script
, (GUI
? "-script.pyw" : "-script.py"));
292 /* figure out the target python executable */
294 scriptf
= open(script
, O_RDONLY
);
296 return fail("Cannot open %s\n", script
);
298 end
= python
+ read(scriptf
, python
, sizeof(python
));
302 while(++ptr
< end
&& *ptr
&& *ptr
!='\n' && *ptr
!='\r') {;}
306 if (strncmp(python
, "#!", 2)) {
307 /* default to python.exe if no #! header */
308 strcpy(python
, "#!python.exe");
311 parsedargs
= parse_argv(python
+2, &parsedargc
);
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 */
316 ptr
= find_exe(parsedargs
[0], script
);
318 return fail("Cannot find Python executable %s\n", parsedargs
[0]);
321 /* printf("Python executable: %s\n", ptr); */
323 /* Argument array needs to be
324 parsedargc + argc, plus 1 for null sentinel */
326 newargs
= (char **)calloc(parsedargc
+ argc
+ 1, sizeof(char *));
329 *newargsp
++ = quoted(ptr
);
330 for (i
= 1; i
<parsedargc
; i
++) *newargsp
++ = quoted(parsedargs
[i
]);
332 *newargsp
++ = quoted(script
);
333 for (i
= 1; i
< argc
; i
++) *newargsp
++ = quoted(argv
[i
]);
337 /* printf("args 0: %s\nargs 1: %s\n", newargs[0], newargs[1]); */
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! */
346 * distribute-issue207: using CreateProcessA instead of spawnv
348 cmdline
= join_executable_and_args(ptr
, newargs
, parsedargc
+ argc
);
349 return create_and_wait_for_subprocess(cmdline
);
352 int WINAPI
WinMain(HINSTANCE hI
, HINSTANCE hP
, LPSTR lpCmd
, int nShow
) {
353 return run(__argc
, __argv
, GUI
);
356 int main(int argc
, char** argv
) {
357 return run(argc
, argv
, GUI
);