From c985d08e51128903479aa389a14a4b123cd36f2f Mon Sep 17 00:00:00 2001 From: Vinay Sajip Date: Thu, 25 Jul 2013 11:20:55 +0100 Subject: [PATCH] Closes #18491: Added script-wrapper functionality to launcher source (but not to executable). --- PC/launcher.c | 172 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 138 insertions(+), 34 deletions(-) diff --git a/PC/launcher.c b/PC/launcher.c index 90a87b1c26d..c8af1972068 100644 --- a/PC/launcher.c +++ b/PC/launcher.c @@ -20,6 +20,27 @@ #define SKIP_PREFIX #define SEARCH_PATH +/* Error codes */ + +#define RC_NO_STD_HANDLES 100 +#define RC_CREATE_PROCESS 101 +#define RC_BAD_VIRTUAL_PATH 102 +#define RC_NO_PYTHON 103 +#define RC_NO_MEMORY 104 +/* + * SCRIPT_WRAPPER is used to choose between two variants of an executable built + * from this source file. If not defined, the PEP 397 Python launcher is built; + * if defined, a script launcher of the type used by setuptools is built, which + * looks for a script name related to the executable name and runs that script + * with the appropriate Python interpreter. + * + * SCRIPT_WRAPPER should be undefined in the source, and defined in a VS project + * which builds the setuptools-style launcher. + */ +#if defined(SCRIPT_WRAPPER) +#define RC_NO_SCRIPT 105 +#endif + /* Just for now - static definition */ static FILE * log_fp = NULL; @@ -32,32 +53,6 @@ skip_whitespace(wchar_t * p) return p; } -/* - * This function is here to simplify memory management - * and to treat blank values as if they are absent. - */ -static wchar_t * get_env(wchar_t * key) -{ - /* This is not thread-safe, just like getenv */ - static wchar_t buf[256]; - DWORD result = GetEnvironmentVariableW(key, buf, 256); - - if (result > 255) { - /* Large environment variable. Accept some leakage */ - wchar_t *buf2 = (wchar_t*)malloc(sizeof(wchar_t) * (result+1)); - GetEnvironmentVariableW(key, buf2, result); - return buf2; - } - - if (result == 0) - /* Either some error, e.g. ERROR_ENVVAR_NOT_FOUND, - or an empty environment variable. */ - return NULL; - - return buf; -} - - static void debug(wchar_t * format, ...) { @@ -100,11 +95,40 @@ error(int rc, wchar_t * format, ... ) #if !defined(_WINDOWS) fwprintf(stderr, L"%s\n", message); #else - MessageBox(NULL, message, TEXT("Python Launcher is sorry to say ..."), MB_OK); + MessageBox(NULL, message, TEXT("Python Launcher is sorry to say ..."), + MB_OK); #endif ExitProcess(rc); } +/* + * This function is here to simplify memory management + * and to treat blank values as if they are absent. + */ +static wchar_t * get_env(wchar_t * key) +{ + /* This is not thread-safe, just like getenv */ + static wchar_t buf[BUFSIZE]; + DWORD result = GetEnvironmentVariableW(key, buf, BUFSIZE); + + if (result >= BUFSIZE) { + /* Large environment variable. Accept some leakage */ + wchar_t *buf2 = (wchar_t*)malloc(sizeof(wchar_t) * (result+1)); + if (buf2 = NULL) { + error(RC_NO_MEMORY, L"Could not allocate environment buffer"); + } + GetEnvironmentVariableW(key, buf2, result); + return buf2; + } + + if (result == 0) + /* Either some error, e.g. ERROR_ENVVAR_NOT_FOUND, + or an empty environment variable. */ + return NULL; + + return buf; +} + #if defined(_WINDOWS) #define PYTHON_EXECUTABLE L"pythonw.exe" @@ -115,11 +139,6 @@ error(int rc, wchar_t * format, ... ) #endif -#define RC_NO_STD_HANDLES 100 -#define RC_CREATE_PROCESS 101 -#define RC_BAD_VIRTUAL_PATH 102 -#define RC_NO_PYTHON 103 - #define MAX_VERSION_SIZE 4 typedef struct { @@ -457,6 +476,51 @@ locate_python(wchar_t * wanted_ver) return result; } +#if defined(SCRIPT_WRAPPER) +/* + * Check for a script located alongside the executable + */ + +#if defined(_WINDOWS) +#define SCRIPT_SUFFIX L"-script.pyw" +#else +#define SCRIPT_SUFFIX L"-script.py" +#endif + +static wchar_t wrapped_script_path[MAX_PATH]; + +/* Locate the script being wrapped. + * + * This code should store the name of the wrapped script in + * wrapped_script_path, or terminate the program with an error if there is no + * valid wrapped script file. + */ +static void +locate_wrapped_script() +{ + wchar_t * p; + size_t plen; + DWORD attrs; + + plen = GetModuleFileNameW(NULL, wrapped_script_path, MAX_PATH); + p = wcsrchr(wrapped_script_path, L'.'); + if (p == NULL) { + debug(L"GetModuleFileNameW returned value has no extension: %s\n", + wrapped_script_path); + error(RC_NO_SCRIPT, L"Wrapper name '%s' is not valid.", wrapped_script_path); + } + + wcsncpy_s(p, MAX_PATH - (p - wrapped_script_path) + 1, SCRIPT_SUFFIX, _TRUNCATE); + attrs = GetFileAttributesW(wrapped_script_path); + if (attrs == INVALID_FILE_ATTRIBUTES) { + debug(L"File '%s' non-existent\n", wrapped_script_path); + error(RC_NO_SCRIPT, L"Script file '%s' is not present.", wrapped_script_path); + } + + debug(L"Using wrapped script file '%s'\n", wrapped_script_path); +} +#endif + /* * Process creation code */ @@ -863,7 +927,7 @@ typedef struct { } BOM; /* - * Strictly, we don't need to handle UTF-16 anf UTF-32, since Python itself + * Strictly, we don't need to handle UTF-16 and UTF-32, since Python itself * doesn't. Never mind, one day it might - there's no harm leaving it in. */ static BOM BOMs[] = { @@ -1250,6 +1314,11 @@ process(int argc, wchar_t ** argv) VS_FIXEDFILEINFO * file_info; UINT block_size; int index; +#if defined(SCRIPT_WRAPPER) + int newlen; + wchar_t * newcommand; + wchar_t * av[2]; +#endif wp = get_env(L"PYLAUNCH_DEBUG"); if ((wp != NULL) && (*wp != L'\0')) @@ -1329,7 +1398,40 @@ process(int argc, wchar_t ** argv) } command = skip_me(GetCommandLineW()); - debug(L"Called with command line: %s", command); + debug(L"Called with command line: %s\n", command); + +#if defined(SCRIPT_WRAPPER) + /* The launcher is being used in "script wrapper" mode. + * There should therefore be a Python script named -script.py in + * the same directory as the launcher executable. + * Put the script name into argv as the first (script name) argument. + */ + + /* Get the wrapped script name - if the script is not present, this will + * terminate the program with an error. + */ + locate_wrapped_script(); + + /* Add the wrapped script to the start of command */ + newlen = wcslen(wrapped_script_path) + wcslen(command) + 2; /* ' ' + NUL */ + newcommand = malloc(sizeof(wchar_t) * newlen); + if (!newcommand) { + error(RC_NO_MEMORY, L"Could not allocate new command line"); + } + else { + wcscpy_s(newcommand, newlen, wrapped_script_path); + wcscat_s(newcommand, newlen, L" "); + wcscat_s(newcommand, newlen, command); + debug(L"Running wrapped script with command line '%s'\n", newcommand); + read_commands(); + av[0] = wrapped_script_path; + av[1] = NULL; + maybe_handle_shebang(av, newcommand); + /* Returns if no shebang line - pass to default processing */ + command = newcommand; + valid = FALSE; + } +#else if (argc <= 1) { valid = FALSE; p = NULL; @@ -1357,6 +1459,8 @@ installed", &p[1]); } } } +#endif + if (!valid) { ip = locate_python(L""); if (ip == NULL)