Closes #18491: Added script-wrapper functionality to launcher source (but not to executable).

This commit is contained in:
Vinay Sajip 2013-07-25 11:20:55 +01:00
parent 7c8cd257e4
commit c985d08e51
1 changed files with 138 additions and 34 deletions

View File

@ -20,6 +20,27 @@
#define SKIP_PREFIX #define SKIP_PREFIX
#define SEARCH_PATH #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 */ /* Just for now - static definition */
static FILE * log_fp = NULL; static FILE * log_fp = NULL;
@ -32,32 +53,6 @@ skip_whitespace(wchar_t * p)
return 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 static void
debug(wchar_t * format, ...) debug(wchar_t * format, ...)
{ {
@ -100,11 +95,40 @@ error(int rc, wchar_t * format, ... )
#if !defined(_WINDOWS) #if !defined(_WINDOWS)
fwprintf(stderr, L"%s\n", message); fwprintf(stderr, L"%s\n", message);
#else #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 #endif
ExitProcess(rc); 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) #if defined(_WINDOWS)
#define PYTHON_EXECUTABLE L"pythonw.exe" #define PYTHON_EXECUTABLE L"pythonw.exe"
@ -115,11 +139,6 @@ error(int rc, wchar_t * format, ... )
#endif #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 #define MAX_VERSION_SIZE 4
typedef struct { typedef struct {
@ -457,6 +476,51 @@ locate_python(wchar_t * wanted_ver)
return result; 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 * Process creation code
*/ */
@ -863,7 +927,7 @@ typedef struct {
} BOM; } 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. * doesn't. Never mind, one day it might - there's no harm leaving it in.
*/ */
static BOM BOMs[] = { static BOM BOMs[] = {
@ -1250,6 +1314,11 @@ process(int argc, wchar_t ** argv)
VS_FIXEDFILEINFO * file_info; VS_FIXEDFILEINFO * file_info;
UINT block_size; UINT block_size;
int index; int index;
#if defined(SCRIPT_WRAPPER)
int newlen;
wchar_t * newcommand;
wchar_t * av[2];
#endif
wp = get_env(L"PYLAUNCH_DEBUG"); wp = get_env(L"PYLAUNCH_DEBUG");
if ((wp != NULL) && (*wp != L'\0')) if ((wp != NULL) && (*wp != L'\0'))
@ -1329,7 +1398,40 @@ process(int argc, wchar_t ** argv)
} }
command = skip_me(GetCommandLineW()); 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 <exename>-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) { if (argc <= 1) {
valid = FALSE; valid = FALSE;
p = NULL; p = NULL;
@ -1357,6 +1459,8 @@ installed", &p[1]);
} }
} }
} }
#endif
if (!valid) { if (!valid) {
ip = locate_python(L""); ip = locate_python(L"");
if (ip == NULL) if (ip == NULL)