2020#define SKIP_PREFIX
2121#define SEARCH_PATH
2222
23+ /* Error codes */
24+
25+ #define RC_NO_STD_HANDLES 100
26+ #define RC_CREATE_PROCESS 101
27+ #define RC_BAD_VIRTUAL_PATH 102
28+ #define RC_NO_PYTHON 103
29+ #define RC_NO_MEMORY 104
30+ /*
31+ * SCRIPT_WRAPPER is used to choose between two variants of an executable built
32+ * from this source file. If not defined, the PEP 397 Python launcher is built;
33+ * if defined, a script launcher of the type used by setuptools is built, which
34+ * looks for a script name related to the executable name and runs that script
35+ * with the appropriate Python interpreter.
36+ *
37+ * SCRIPT_WRAPPER should be undefined in the source, and defined in a VS project
38+ * which builds the setuptools-style launcher.
39+ */
40+ #if defined(SCRIPT_WRAPPER )
41+ #define RC_NO_SCRIPT 105
42+ #endif
43+
2344/* Just for now - static definition */
2445
2546static FILE * log_fp = NULL ;
@@ -32,32 +53,6 @@ skip_whitespace(wchar_t * p)
3253 return p ;
3354}
3455
35- /*
36- * This function is here to simplify memory management
37- * and to treat blank values as if they are absent.
38- */
39- static wchar_t * get_env (wchar_t * key )
40- {
41- /* This is not thread-safe, just like getenv */
42- static wchar_t buf [256 ];
43- DWORD result = GetEnvironmentVariableW (key , buf , 256 );
44-
45- if (result > 255 ) {
46- /* Large environment variable. Accept some leakage */
47- wchar_t * buf2 = (wchar_t * )malloc (sizeof (wchar_t ) * (result + 1 ));
48- GetEnvironmentVariableW (key , buf2 , result );
49- return buf2 ;
50- }
51-
52- if (result == 0 )
53- /* Either some error, e.g. ERROR_ENVVAR_NOT_FOUND,
54- or an empty environment variable. */
55- return NULL ;
56-
57- return buf ;
58- }
59-
60-
6156static void
6257debug (wchar_t * format , ...)
6358{
@@ -100,11 +95,40 @@ error(int rc, wchar_t * format, ... )
10095#if !defined(_WINDOWS )
10196 fwprintf (stderr , L"%s\n" , message );
10297#else
103- MessageBox (NULL , message , TEXT ("Python Launcher is sorry to say ..." ), MB_OK );
98+ MessageBox (NULL , message , TEXT ("Python Launcher is sorry to say ..." ),
99+ MB_OK );
104100#endif
105101 ExitProcess (rc );
106102}
107103
104+ /*
105+ * This function is here to simplify memory management
106+ * and to treat blank values as if they are absent.
107+ */
108+ static wchar_t * get_env (wchar_t * key )
109+ {
110+ /* This is not thread-safe, just like getenv */
111+ static wchar_t buf [BUFSIZE ];
112+ DWORD result = GetEnvironmentVariableW (key , buf , BUFSIZE );
113+
114+ if (result >= BUFSIZE ) {
115+ /* Large environment variable. Accept some leakage */
116+ wchar_t * buf2 = (wchar_t * )malloc (sizeof (wchar_t ) * (result + 1 ));
117+ if (buf2 = NULL ) {
118+ error (RC_NO_MEMORY , L"Could not allocate environment buffer" );
119+ }
120+ GetEnvironmentVariableW (key , buf2 , result );
121+ return buf2 ;
122+ }
123+
124+ if (result == 0 )
125+ /* Either some error, e.g. ERROR_ENVVAR_NOT_FOUND,
126+ or an empty environment variable. */
127+ return NULL ;
128+
129+ return buf ;
130+ }
131+
108132#if defined(_WINDOWS )
109133
110134#define PYTHON_EXECUTABLE L"pythonw.exe"
@@ -115,11 +139,6 @@ error(int rc, wchar_t * format, ... )
115139
116140#endif
117141
118- #define RC_NO_STD_HANDLES 100
119- #define RC_CREATE_PROCESS 101
120- #define RC_BAD_VIRTUAL_PATH 102
121- #define RC_NO_PYTHON 103
122-
123142#define MAX_VERSION_SIZE 4
124143
125144typedef struct {
@@ -457,6 +476,51 @@ locate_python(wchar_t * wanted_ver)
457476 return result ;
458477}
459478
479+ #if defined(SCRIPT_WRAPPER )
480+ /*
481+ * Check for a script located alongside the executable
482+ */
483+
484+ #if defined(_WINDOWS )
485+ #define SCRIPT_SUFFIX L"-script.pyw"
486+ #else
487+ #define SCRIPT_SUFFIX L"-script.py"
488+ #endif
489+
490+ static wchar_t wrapped_script_path [MAX_PATH ];
491+
492+ /* Locate the script being wrapped.
493+ *
494+ * This code should store the name of the wrapped script in
495+ * wrapped_script_path, or terminate the program with an error if there is no
496+ * valid wrapped script file.
497+ */
498+ static void
499+ locate_wrapped_script ()
500+ {
501+ wchar_t * p ;
502+ size_t plen ;
503+ DWORD attrs ;
504+
505+ plen = GetModuleFileNameW (NULL , wrapped_script_path , MAX_PATH );
506+ p = wcsrchr (wrapped_script_path , L'.' );
507+ if (p == NULL ) {
508+ debug (L"GetModuleFileNameW returned value has no extension: %s\n" ,
509+ wrapped_script_path );
510+ error (RC_NO_SCRIPT , L"Wrapper name '%s' is not valid." , wrapped_script_path );
511+ }
512+
513+ wcsncpy_s (p , MAX_PATH - (p - wrapped_script_path ) + 1 , SCRIPT_SUFFIX , _TRUNCATE );
514+ attrs = GetFileAttributesW (wrapped_script_path );
515+ if (attrs == INVALID_FILE_ATTRIBUTES ) {
516+ debug (L"File '%s' non-existent\n" , wrapped_script_path );
517+ error (RC_NO_SCRIPT , L"Script file '%s' is not present." , wrapped_script_path );
518+ }
519+
520+ debug (L"Using wrapped script file '%s'\n" , wrapped_script_path );
521+ }
522+ #endif
523+
460524/*
461525 * Process creation code
462526 */
@@ -863,7 +927,7 @@ typedef struct {
863927} BOM ;
864928
865929/*
866- * Strictly, we don't need to handle UTF-16 anf UTF-32, since Python itself
930+ * Strictly, we don't need to handle UTF-16 and UTF-32, since Python itself
867931 * doesn't. Never mind, one day it might - there's no harm leaving it in.
868932 */
869933static BOM BOMs [] = {
@@ -1250,6 +1314,11 @@ process(int argc, wchar_t ** argv)
12501314 VS_FIXEDFILEINFO * file_info ;
12511315 UINT block_size ;
12521316 int index ;
1317+ #if defined(SCRIPT_WRAPPER )
1318+ int newlen ;
1319+ wchar_t * newcommand ;
1320+ wchar_t * av [2 ];
1321+ #endif
12531322
12541323 wp = get_env (L"PYLAUNCH_DEBUG" );
12551324 if ((wp != NULL ) && (* wp != L'\0' ))
@@ -1329,7 +1398,40 @@ process(int argc, wchar_t ** argv)
13291398 }
13301399
13311400 command = skip_me (GetCommandLineW ());
1332- debug (L"Called with command line: %s" , command );
1401+ debug (L"Called with command line: %s\n" , command );
1402+
1403+ #if defined(SCRIPT_WRAPPER )
1404+ /* The launcher is being used in "script wrapper" mode.
1405+ * There should therefore be a Python script named <exename>-script.py in
1406+ * the same directory as the launcher executable.
1407+ * Put the script name into argv as the first (script name) argument.
1408+ */
1409+
1410+ /* Get the wrapped script name - if the script is not present, this will
1411+ * terminate the program with an error.
1412+ */
1413+ locate_wrapped_script ();
1414+
1415+ /* Add the wrapped script to the start of command */
1416+ newlen = wcslen (wrapped_script_path ) + wcslen (command ) + 2 ; /* ' ' + NUL */
1417+ newcommand = malloc (sizeof (wchar_t ) * newlen );
1418+ if (!newcommand ) {
1419+ error (RC_NO_MEMORY , L"Could not allocate new command line" );
1420+ }
1421+ else {
1422+ wcscpy_s (newcommand , newlen , wrapped_script_path );
1423+ wcscat_s (newcommand , newlen , L" " );
1424+ wcscat_s (newcommand , newlen , command );
1425+ debug (L"Running wrapped script with command line '%s'\n" , newcommand );
1426+ read_commands ();
1427+ av [0 ] = wrapped_script_path ;
1428+ av [1 ] = NULL ;
1429+ maybe_handle_shebang (av , newcommand );
1430+ /* Returns if no shebang line - pass to default processing */
1431+ command = newcommand ;
1432+ valid = FALSE;
1433+ }
1434+ #else
13331435 if (argc <= 1 ) {
13341436 valid = FALSE;
13351437 p = NULL ;
@@ -1357,6 +1459,8 @@ installed", &p[1]);
13571459 }
13581460 }
13591461 }
1462+ #endif
1463+
13601464 if (!valid ) {
13611465 ip = locate_python (L"" );
13621466 if (ip == NULL )
0 commit comments