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
68 int fail(const char *format
, const char *data
) {
69 /* Print error message to stderr and return 2 */
70 fprintf(stderr
, format
, data
);
74 char *quoted(char *data
) {
75 int i
, ln
= strlen(data
), nb
;
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
;
83 for (nb
=0, i
=0; i
< ln
; i
++)
87 else if (data
[i
] == '"')
98 for (; nb
> 0; nb
--) /* Deal w trailing slashes */
115 char *loadable_exe(char *exename
) {
116 /* HINSTANCE hPython; DLL handle for python executable */
119 /* hPython = LoadLibraryEx(exename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
120 if (!hPython) return NULL; */
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);
127 FreeLibrary(hPython); */
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
;
136 /* convert slashes to backslashes for uniform search below */
138 while (c
= *result
++) if (c
=='/') result
[-1] = '\\';
140 _splitpath(exename
, drive
, dir
, fname
, ext
);
141 if (drive
[0] || dir
[0]=='\\') {
142 return loadable_exe(exename
); /* absolute path, use directly */
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
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
);
157 char **parse_argv(char *cmdline
, int *argc
)
159 /* Parse a command line in-place using MS C rules */
161 char **result
= (char **)calloc(strlen(cmdline
), sizeof(char *));
162 char *output
= cmdline
;
169 while (isspace(*cmdline
)) cmdline
++; /* skip leading spaces */
173 if (!c
|| (isspace(c
) && !iq
)) {
174 while (nb
) {*output
++ = '\\'; nb
--; }
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 */
183 ++nb
; /* count \'s */
186 if (!(nb
& 1)) { iq
= !iq
; c
= 0; } /* skip " unless odd # of \ */
187 nb
= nb
>> 1; /* cut \'s in half */
189 while (nb
) {*output
++ = '\\'; nb
--; }
190 if (c
) *output
++ = c
;
195 void pass_control_to_child(DWORD control_type
) {
197 * distribute-issue207
198 * passes the control event to child process (Python)
203 GenerateConsoleCtrlEvent(child_pid
,0);
206 BOOL
control_handler(DWORD control_type
) {
208 * distribute-issue207
209 * control event handler callback function
211 switch (control_type
) {
213 pass_control_to_child(0);
219 int create_and_wait_for_subprocess(char* command
) {
221 * distribute-issue207
222 * launches child process (Python)
224 DWORD return_value
= 0;
225 LPSTR commandline
= command
;
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");
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");
247 char* join_executable_and_args(char *executable
, char **args
, int argc
)
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
257 len
=strlen(executable
)+2;
258 for (counter
=1; counter
<argc
; counter
++) {
259 len
+=strlen(args
[counter
])+1;
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;
272 int run(int argc
, char **argv
, int is_gui
) {
274 char python
[256]; /* python executable's filename*/
275 char *pyopt
; /* Python option */
276 char script
[256]; /* the script's filename */
278 int scriptf
; /* file descriptor for script file */
280 char **newargs
, **newargsp
, **parsedargs
; /* argument array for exec */
281 char *ptr
, *end
; /* working pointers for string manipulation */
283 int i
, parsedargc
; /* loop counter */
285 /* compute script name from our .exe name*/
286 GetModuleFileNameA(NULL
, script
, sizeof(script
));
287 end
= script
+ strlen(script
);
288 while( end
>script
&& *end
!= '.')
291 strcat(script
, (GUI
? "-script.pyw" : "-script.py"));
293 /* figure out the target python executable */
295 scriptf
= open(script
, O_RDONLY
);
297 return fail("Cannot open %s\n", script
);
299 end
= python
+ read(scriptf
, python
, sizeof(python
));
303 while(++ptr
< end
&& *ptr
&& *ptr
!='\n' && *ptr
!='\r') {;}
307 if (strncmp(python
, "#!", 2)) {
308 /* default to python.exe if no #! header */
309 strcpy(python
, "#!python.exe");
312 parsedargs
= parse_argv(python
+2, &parsedargc
);
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 */
317 ptr
= find_exe(parsedargs
[0], script
);
319 return fail("Cannot find Python executable %s\n", parsedargs
[0]);
322 /* printf("Python executable: %s\n", ptr); */
324 /* Argument array needs to be
325 parsedargc + argc, plus 1 for null sentinel */
327 newargs
= (char **)calloc(parsedargc
+ argc
+ 1, sizeof(char *));
330 *newargsp
++ = quoted(ptr
);
331 for (i
= 1; i
<parsedargc
; i
++) *newargsp
++ = quoted(parsedargs
[i
]);
333 *newargsp
++ = quoted(script
);
334 for (i
= 1; i
< argc
; i
++) *newargsp
++ = quoted(argv
[i
]);
338 /* printf("args 0: %s\nargs 1: %s\n", newargs[0], newargs[1]); */
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! */
347 * distribute-issue207: using CreateProcessA instead of spawnv
349 cmdline
= join_executable_and_args(ptr
, newargs
, parsedargc
+ argc
);
350 return create_and_wait_for_subprocess(cmdline
);
353 int WINAPI
WinMain(HINSTANCE hI
, HINSTANCE hP
, LPSTR lpCmd
, int nShow
) {
354 return run(__argc
, __argv
, GUI
);
357 int main(int argc
, char** argv
) {
358 return run(argc
, argv
, GUI
);