Commit e44a3a3a authored by PJ Eby's avatar PJ Eby

Overhauled Windows script wrapping to support ``bdist_wininst`` better.

Scripts installed with ``bdist_wininst`` will always use ``#!python.exe`` or
``#!pythonw.exe`` as the executable name (even when built on non-Windows
platforms!), and the wrappers will look for the executable in the script's
parent directory (which should find the right version of Python).
(backport from trunk)

--HG--
branch : setuptools-0.6
extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/branches/setuptools-0.6%4053194
parent 47f061ee
...@@ -14,7 +14,8 @@ Windows ...@@ -14,7 +14,8 @@ Windows
Install setuptools using the provided ``.exe`` installer. If you've previously Install setuptools using the provided ``.exe`` installer. If you've previously
installed older versions of setuptools, please delete all ``setuptools*.egg`` installed older versions of setuptools, please delete all ``setuptools*.egg``
files from your system FIRST. and ``setuptools.pth`` files from your system's ``site-packages`` directory
(and any other ``sys.path`` directories) FIRST.
If you are upgrading a previous version of setuptools that was installed using If you are upgrading a previous version of setuptools that was installed using
an ``.exe`` installer, please be sure to also *uninstall that older version* an ``.exe`` installer, please be sure to also *uninstall that older version*
...@@ -23,9 +24,7 @@ version. ...@@ -23,9 +24,7 @@ version.
Once installation is complete, you will find an ``easy_install.exe`` program in Once installation is complete, you will find an ``easy_install.exe`` program in
your Python ``Scripts`` subdirectory. Be sure to add this directory to your your Python ``Scripts`` subdirectory. Be sure to add this directory to your
``PATH`` environment variable, if you haven't already done so. You must also ``PATH`` environment variable, if you haven't already done so.
have your Python installation directory (e.g. ``C:\\Python23``) on the
``PATH``.
RPM-Based Systems RPM-Based Systems
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
starting. So, we have to use spawnv() and wait for Python to exit before starting. So, we have to use spawnv() and wait for Python to exit before
continuing. :( continuing. :(
*/ */
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
...@@ -29,20 +30,25 @@ ...@@ -29,20 +30,25 @@
#include "windows.h" #include "windows.h"
int fail(char *format, char *data) { int fail(char *format, char *data) {
/* Print error message to stderr and return 1 */ /* Print error message to stderr and return 2 */
fprintf(stderr, format, data); fprintf(stderr, format, data);
return 2; return 2;
} }
char *quoted(char *data) { char *quoted(char *data) {
int i, l = strlen(data), nb; int i, ln = strlen(data), nb;
/* We allocate twice as much space as needed to deal with worse-case /* We allocate twice as much space as needed to deal with worse-case
of having to escape everything. */ of having to escape everything. */
char *result = calloc(l*2+3, sizeof(char)); char *result = calloc(ln*2+3, sizeof(char));
char *presult = result; char *presult = result;
*presult++ = '"'; *presult++ = '"';
for (nb=0, i=0; i < l; i++) for (nb=0, i=0; i < ln; i++)
{ {
if (data[i] == '\\') if (data[i] == '\\')
nb += 1; nb += 1;
...@@ -56,6 +62,7 @@ char *quoted(char *data) { ...@@ -56,6 +62,7 @@ char *quoted(char *data) {
nb = 0; nb = 0;
*presult++ = data[i]; *presult++ = data[i];
} }
for (; nb > 0; nb--) /* Deal w trailing slashes */ for (; nb > 0; nb--) /* Deal w trailing slashes */
*presult++ = '\\'; *presult++ = '\\';
...@@ -64,49 +71,108 @@ char *quoted(char *data) { ...@@ -64,49 +71,108 @@ char *quoted(char *data) {
return result; return result;
} }
char *getpyopt(char *python)
{
/* Search a Python command string, read from a #! line for an
option. An option must be separated from an executable name by
one or more spaces. An option consistes of a hyphen followed by
one or more letters.
*/
static char *letters =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" char *loadable_exe(char *exename) {
; HINSTANCE hPython; /* DLL handle for python executable */
char *p = python + strlen(python) - 1; char *result;
if (strchr(letters, *p) == NULL)
return NULL; /* Path doen't end with a letter. Odd. */ hPython = LoadLibraryEx(exename, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
while (p > python && strchr(letters, *p) != NULL) if (!hPython) return NULL;
p--;
if (p == python || *p != '-') /* Return the absolute filename for spawnv */
return NULL; /* Can't be an option */ result = calloc(MAX_PATH, sizeof(char));
p--; if (result) GetModuleFileName(hPython, result, MAX_PATH);
if (p > python && isspace(*p))
{ /* BINGO, we have an option */ FreeLibrary(hPython);
char *pyopt = p+1; return result;
/* strip trailing spaces from remainder of python command */ }
while (p > python && isspace(*p))
*p-- = '\0';
return pyopt; char *find_exe(char *exename, char *script) {
char drive[_MAX_DRIVE], dir[_MAX_DIR], fname[_MAX_FNAME], ext[_MAX_EXT];
char path[_MAX_PATH], c, *result;
/* convert slashes to backslashes for uniform search below */
result = exename;
while (c = *result++) if (c=='/') result[-1] = '\\';
_splitpath(exename, drive, dir, fname, ext);
if (drive[0] || dir[0]=='\\') {
return loadable_exe(exename); /* absolute path, use directly */
} }
else /* Use the script's parent directory, which should be the Python home
return NULL; (This should only be used for bdist_wininst-installed scripts, because
easy_install-ed scripts use the absolute path to python[w].exe
*/
_splitpath(script, drive, dir, fname, ext);
result = dir + strlen(dir) -1;
if (*result == '\\') result--;
while (*result != '\\' && result>=dir) *result-- = 0;
_makepath(path, drive, dir, exename, NULL);
return loadable_exe(path);
} }
char **parse_argv(char *cmdline, int *argc)
{
/* Parse a command line in-place using MS C rules */
char **result = calloc(strlen(cmdline), sizeof(char *));
char *output = cmdline;
char c;
int nb = 0;
*argc = 0;
result[0] = output;
while (isspace(*cmdline)) cmdline++; /* skip leading spaces */
do {
c = *cmdline++;
if (!c || isspace(c)) {
while (nb) {*output++ = '\\'; nb--; }
*output++ = 0;
result[++*argc] = output;
if (!c) return result;
while (isspace(*cmdline)) cmdline++; /* skip leading spaces */
continue;
}
if (c == '\\')
++nb; /* count \'s */
else {
if (c == '"') {
if (!(nb & 1)) c = 0; /* skip " unless odd # of \ */
nb = nb >> 1; /* cut \'s in half */
}
while (nb) {*output++ = '\\'; nb--; }
if (c) *output++ = c;
}
} while (1);
}
int run(int argc, char **argv, int is_gui) { int run(int argc, char **argv, int is_gui) {
char python[256]; /* python executable's filename*/ char python[256]; /* python executable's filename*/
char *pyopt; /* Python option */ char *pyopt; /* Python option */
char script[256]; /* the script's filename */ char script[256]; /* the script's filename */
HINSTANCE hPython; /* DLL handle for python executable */
int scriptf; /* file descriptor for script file */ int scriptf; /* file descriptor for script file */
char **newargs, **newargsp; /* argument array for exec */ char **newargs, **newargsp, **parsedargs; /* argument array for exec */
char *ptr, *end; /* working pointers for string manipulation */ char *ptr, *end; /* working pointers for string manipulation */
int i; /* loop counter */ int i, parsedargc; /* loop counter */
/* compute script name from our .exe name*/ /* compute script name from our .exe name*/
GetModuleFileName(NULL, script, sizeof(script)); GetModuleFileName(NULL, script, sizeof(script));
...@@ -126,57 +192,51 @@ int run(int argc, char **argv, int is_gui) { ...@@ -126,57 +192,51 @@ int run(int argc, char **argv, int is_gui) {
close(scriptf); close(scriptf);
ptr = python-1; ptr = python-1;
while(++ptr < end && *ptr && *ptr!='\n' && *ptr!='\r') { while(++ptr < end && *ptr && *ptr!='\n' && *ptr!='\r') {;}
if (*ptr=='/')
*ptr='\\'; /* convert slashes to avoid LoadLibrary crashes... */
}
*ptr-- = '\0'; *ptr-- = '\0';
while (ptr>python && isspace(*ptr)) *ptr-- = '\0'; /* strip trailing sp */
if (strncmp(python, "#!", 2)) { if (strncmp(python, "#!", 2)) {
/* default to python.exe if no #! header */ /* default to python.exe if no #! header */
strcpy(python, "#!python.exe"); strcpy(python, "#!python.exe");
} }
/* Check for Python options */ parsedargs = parse_argv(python+2, &parsedargc);
pyopt = getpyopt(python);
/* At this point, the python buffer contains "#!pythonfilename" */
/* Using spawnv() can fail strangely if you e.g. find the Cygwin /* Using spawnv() can fail strangely if you e.g. find the Cygwin
Python, so we'll make sure Windows can find and load it */ Python, so we'll make sure Windows can find and load it */
hPython = LoadLibraryEx(python+2, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
if (!hPython) { ptr = find_exe(parsedargs[0], script);
return fail("Cannot find Python executable %s\n", python+2); if (!ptr) {
return fail("Cannot find Python executable %s\n", parsedargs[0]);
} }
/* And we'll use the absolute filename for spawnv */ /* printf("Python executable: %s\n", ptr); */
GetModuleFileName(hPython, python, sizeof(python));
/* printf("Python executable: %s\n", python); */ /* Argument array needs to be
parsedargc + argc, plus 1 for null sentinel */
/* Argument array needs to be newargs = (char **)calloc(parsedargc + argc + 1, sizeof(char *));
argc+1 for python executable,
plus 1 for possible python opts,
plus 1 for null sentinel */
newargs = (char **)calloc(argc+3, sizeof(char *));
newargsp = newargs; newargsp = newargs;
*newargsp++ = quoted(python);
if (pyopt) *newargsp++ = quoted(ptr);
*newargsp++ = pyopt; for (i = 1; i<parsedargc; i++) *newargsp++ = quoted(parsedargs[i]);
*newargsp++ = quoted(script); *newargsp++ = quoted(script);
for (i = 1; i < argc; i++) for (i = 1; i < argc; i++) *newargsp++ = quoted(argv[i]);
*newargsp++ = quoted(argv[i]);
*newargsp++ = NULL; *newargsp++ = NULL;
/* printf("args 0: %s\nargs 1: %s\n", newargs[0], newargs[1]); */ /* printf("args 0: %s\nargs 1: %s\n", newargs[0], newargs[1]); */
if (is_gui) { if (is_gui) {
/* Use exec, we don't need to wait for the GUI to finish */ /* Use exec, we don't need to wait for the GUI to finish */
execv(python, (const char * const *)(newargs)); execv(python, (const char * const *)(newargs));
return fail("Could not exec %s", python); /* shouldn't get here! */ return fail("Could not exec %s", python); /* shouldn't get here! */
} }
/* We *do* need to wait for a CLI to finish, so use spawn */ /* We *do* need to wait for a CLI to finish, so use spawn */
return spawnv(P_WAIT, python, (const char * const *)(newargs)); return spawnv(P_WAIT, ptr, (const char * const *)(newargs));
} }
...@@ -184,8 +244,3 @@ int WINAPI WinMain(HINSTANCE hI, HINSTANCE hP, LPSTR lpCmd, int nShow) { ...@@ -184,8 +244,3 @@ int WINAPI WinMain(HINSTANCE hI, HINSTANCE hP, LPSTR lpCmd, int nShow) {
return run(__argc, __argv, GUI); return run(__argc, __argv, GUI);
} }
...@@ -2601,6 +2601,13 @@ Release Notes/Change History ...@@ -2601,6 +2601,13 @@ Release Notes/Change History
---------------------------- ----------------------------
0.6c4 0.6c4
* Overhauled Windows script wrapping to support ``bdist_wininst`` better.
Scripts installed with ``bdist_wininst`` will always use ``#!python.exe`` or
``#!pythonw.exe`` as the executable name (even when built on non-Windows
platforms!), and the wrappers will look for the executable in the script's
parent directory (which should find the right version of Python).
* Fix ``upload`` command not uploading files built by ``bdist_rpm`` or * Fix ``upload`` command not uploading files built by ``bdist_rpm`` or
``bdist_wininst`` under Python 2.3 and 2.4. ``bdist_wininst`` under Python 2.3 and 2.4.
......
No preview for this file type
...@@ -369,16 +369,13 @@ Please make the appropriate changes for your system and try again. ...@@ -369,16 +369,13 @@ Please make the appropriate changes for your system and try again.
def install_egg_scripts(self, dist): def install_egg_scripts(self, dist):
"""Write all the scripts for `dist`, unless scripts are excluded""" """Write all the scripts for `dist`, unless scripts are excluded"""
if not self.exclude_scripts and dist.metadata_isdir('scripts'):
for script_name in dist.metadata_listdir('scripts'):
self.install_script(
dist, script_name,
dist.get_metadata('scripts/'+script_name).replace('\r','\n')
)
self.install_wrapper_scripts(dist) self.install_wrapper_scripts(dist)
if self.exclude_scripts or not dist.metadata_isdir('scripts'):
return
for script_name in dist.metadata_listdir('scripts'):
self.install_script(
dist, script_name,
dist.get_metadata('scripts/'+script_name).replace('\r','\n')
)
def add_output(self, path): def add_output(self, path):
if os.path.isdir(path): if os.path.isdir(path):
...@@ -408,6 +405,9 @@ Please make the appropriate changes for your system and try again. ...@@ -408,6 +405,9 @@ Please make the appropriate changes for your system and try again.
def easy_install(self, spec, deps=False): def easy_install(self, spec, deps=False):
tmpdir = tempfile.mkdtemp(prefix="easy_install-") tmpdir = tempfile.mkdtemp(prefix="easy_install-")
download = None download = None
...@@ -1407,7 +1407,6 @@ class PthDistributions(Environment): ...@@ -1407,7 +1407,6 @@ class PthDistributions(Environment):
else: else:
return path return path
def get_script_header(script_text, executable=sys_executable, wininst=False): def get_script_header(script_text, executable=sys_executable, wininst=False):
"""Create a #! line, getting options (if any) from script_text""" """Create a #! line, getting options (if any) from script_text"""
from distutils.command.build_scripts import first_line_re from distutils.command.build_scripts import first_line_re
...@@ -1418,8 +1417,10 @@ def get_script_header(script_text, executable=sys_executable, wininst=False): ...@@ -1418,8 +1417,10 @@ def get_script_header(script_text, executable=sys_executable, wininst=False):
options = match.group(1) or '' options = match.group(1) or ''
if options: if options:
options = ' '+options options = ' '+options
if wininst and sys.platform!='win32': if wininst:
executable = "python.exe" executable = "python.exe"
else:
executable = nt_quote_arg(executable)
hdr = "#!%(executable)s%(options)s\n" % locals() hdr = "#!%(executable)s%(options)s\n" % locals()
if unicode(hdr,'ascii','ignore').encode('ascii') != hdr: if unicode(hdr,'ascii','ignore').encode('ascii') != hdr:
# Non-ascii path to sys.executable, use -x to prevent warnings # Non-ascii path to sys.executable, use -x to prevent warnings
...@@ -1432,7 +1433,6 @@ def get_script_header(script_text, executable=sys_executable, wininst=False): ...@@ -1432,7 +1433,6 @@ def get_script_header(script_text, executable=sys_executable, wininst=False):
hdr = "#!%(executable)s%(options)s\n" % locals() hdr = "#!%(executable)s%(options)s\n" % locals()
return hdr return hdr
def auto_chmod(func, arg, exc): def auto_chmod(func, arg, exc):
if func is os.remove and os.name=='nt': if func is os.remove and os.name=='nt':
os.chmod(arg, stat.S_IWRITE) os.chmod(arg, stat.S_IWRITE)
...@@ -1474,6 +1474,47 @@ def is_python(text, filename='<string>'): ...@@ -1474,6 +1474,47 @@ def is_python(text, filename='<string>'):
def nt_quote_arg(arg):
"""Quote a command line argument according to Windows parsing rules"""
result = []
needquote = False
nb = 0
needquote = (" " in arg) or ("\t" in arg)
if needquote:
result.append('"')
for c in arg:
if c == '\\':
nb += 1
elif c == '"':
# double preceding backslashes, then add a \"
result.append('\\' * (nb*2) + '\\"')
nb = 0
else:
if nb:
result.append('\\' * nb)
nb = 0
result.append(c)
if nb:
result.append('\\' * nb)
if needquote:
result.append('\\' * nb) # double the trailing backslashes
result.append('"')
return ''.join(result)
def is_python_script(script_text, filename): def is_python_script(script_text, filename):
"""Is this text, as a whole, a Python script? (as opposed to shell/bat/etc. """Is this text, as a whole, a Python script? (as opposed to shell/bat/etc.
""" """
...@@ -1532,7 +1573,7 @@ def get_script_args(dist, executable=sys_executable, wininst=False): ...@@ -1532,7 +1573,7 @@ def get_script_args(dist, executable=sys_executable, wininst=False):
")\n" ")\n"
) % locals() ) % locals()
if sys.platform=='win32' or wininst: if sys.platform=='win32' or wininst:
# On Windows, add a .py extension and an .exe launcher # On Windows/wininst, add a .py extension and an .exe launcher
if group=='gui_scripts': if group=='gui_scripts':
ext, launcher = '-script.pyw', 'gui.exe' ext, launcher = '-script.pyw', 'gui.exe'
old = ['.pyw'] old = ['.pyw']
...@@ -1540,9 +1581,9 @@ def get_script_args(dist, executable=sys_executable, wininst=False): ...@@ -1540,9 +1581,9 @@ def get_script_args(dist, executable=sys_executable, wininst=False):
else: else:
ext, launcher = '-script.py', 'cli.exe' ext, launcher = '-script.py', 'cli.exe'
old = ['.py','.pyc','.pyo'] old = ['.py','.pyc','.pyo']
new_header = re.sub('(?i)pythonw.exe','pythonw.exe',header) new_header = re.sub('(?i)pythonw.exe','python.exe',header)
if os.path.exists(new_header[2:-1]): if os.path.exists(new_header[2:-1]) or sys.platform!='win32':
hdr = new_header hdr = new_header
else: else:
hdr = header hdr = header
......
No preview for this file type
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment