Added quoting of script arguments and extended the quoting logic to
handle embedded quotes. Added support for passing a single argument on the shebang line to pass things like -O and -i. Fixed bug in handling trailing whitespace in Python command. --HG-- branch : setuptools-0.6 extra : convert_revision : svn%3A6015fed2-1504-0410-9fe1-9d1591cc4771/sandbox/branches/setuptools-0.6%4051487
This commit is contained in:
parent
f744be10b9
commit
cd84d4b5eb
88
launcher.c
88
launcher.c
|
@ -33,22 +33,80 @@ int fail(char *format, char *data) {
|
|||
fprintf(stderr, format, data);
|
||||
return 2;
|
||||
}
|
||||
|
||||
char *quoted(char *data) {
|
||||
char *result = calloc(strlen(data)+3,sizeof(char));
|
||||
strcat(result,"\""); strcat(result,data); strcat(result,"\"");
|
||||
int i, l = strlen(data), nb;
|
||||
/* We allocate twice as much space as needed to deal with worse-case
|
||||
of having to escape everything. */
|
||||
char *result = calloc(l*2+3, sizeof(char));
|
||||
char *presult = result;
|
||||
|
||||
*presult++ = '"';
|
||||
for (nb=0, i=0; i < l; i++)
|
||||
{
|
||||
if (data[i] == '\\')
|
||||
nb += 1;
|
||||
else if (data[i] == '"')
|
||||
{
|
||||
for (; nb > 0; nb--)
|
||||
*presult++ = '\\';
|
||||
*presult++ = '\\';
|
||||
}
|
||||
else
|
||||
nb = 0;
|
||||
*presult++ = data[i];
|
||||
}
|
||||
for (; nb > 0; nb--) /* Deal w trailing slashes */
|
||||
*presult++ = '\\';
|
||||
|
||||
*presult++ = '"';
|
||||
*presult++ = 0;
|
||||
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 *p = python + strlen(python) - 1;
|
||||
if (strchr(letters, *p) == NULL)
|
||||
return NULL; /* Path doen't end with a letter. Odd. */
|
||||
while (p > python && strchr(letters, *p) != NULL)
|
||||
p--;
|
||||
if (p == python || *p != '-')
|
||||
return NULL; /* Can't be an option */
|
||||
p--;
|
||||
if (p > python && isspace(*p))
|
||||
{ /* BINGO, we have an option */
|
||||
char *pyopt = p+1;
|
||||
/* strip trailing spaces from remainder of python command */
|
||||
while (p > python && isspace(*p))
|
||||
*p-- = '\0';
|
||||
return pyopt;
|
||||
}
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int run(int argc, char **argv, int is_gui) {
|
||||
|
||||
char python[256]; /* python executable's filename*/
|
||||
char *pyopt; /* Python option */
|
||||
char script[256]; /* the script's filename */
|
||||
|
||||
HINSTANCE hPython; /* DLL handle for python executable */
|
||||
int scriptf; /* file descriptor for script file */
|
||||
|
||||
char **newargs; /* argument array for exec */
|
||||
char **newargs, **newargsp; /* argument array for exec */
|
||||
char *ptr, *end; /* working pointers for string manipulation */
|
||||
int i; /* loop counter */
|
||||
|
||||
/* compute script name from our .exe name*/
|
||||
GetModuleFileName(NULL, script, sizeof(script));
|
||||
|
@ -73,13 +131,16 @@ int run(int argc, char **argv, int is_gui) {
|
|||
*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)) {
|
||||
/* default to python.exe if no #! header */
|
||||
strcpy(python, "#!python.exe");
|
||||
}
|
||||
|
||||
/* Check for Python options */
|
||||
pyopt = getpyopt(python);
|
||||
|
||||
/* At this point, the python buffer contains "#!pythonfilename" */
|
||||
|
||||
/* Using spawnv() can fail strangely if you e.g. find the Cygwin
|
||||
|
@ -94,12 +155,19 @@ int run(int argc, char **argv, int is_gui) {
|
|||
|
||||
/* printf("Python executable: %s\n", python); */
|
||||
|
||||
/* Argument array needs to be argc+1 for args, plus 1 for null sentinel */
|
||||
newargs = (char **)calloc(argc+2, sizeof(char *));
|
||||
newargs[0] = quoted(python);
|
||||
newargs[1] = quoted(script);
|
||||
memcpy(newargs+2, argv+1, (argc-1)*sizeof(char *));
|
||||
newargs[argc+1] = NULL;
|
||||
/* Argument array needs to be
|
||||
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++ = quoted(python);
|
||||
if (pyopt)
|
||||
*newargsp++ = pyopt;
|
||||
*newargsp++ = quoted(script);
|
||||
for (i = 1; i < argc; i++)
|
||||
*newargsp++ = quoted(argv[i]);
|
||||
*newargsp++ = NULL;
|
||||
|
||||
/* printf("args 0: %s\nargs 1: %s\n", newargs[0], newargs[1]); */
|
||||
if (is_gui) {
|
||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -14,11 +14,16 @@ from distutils.util import convert_path
|
|||
import sys, os.path
|
||||
|
||||
def additional_tests():
|
||||
import doctest
|
||||
return doctest.DocFileSuite(
|
||||
'api_tests.txt', optionflags=doctest.ELLIPSIS, package='pkg_resources',
|
||||
)
|
||||
|
||||
import doctest, unittest
|
||||
suite = unittest.TestSuite((
|
||||
doctest.DocFileSuite(
|
||||
'api_tests.txt',
|
||||
optionflags=doctest.ELLIPSIS, package='pkg_resources',
|
||||
),
|
||||
))
|
||||
if sys.platform == 'win32':
|
||||
suite.addTest(doctest.DocFileSuite('win_script_wrapper.txt'))
|
||||
return suite
|
||||
|
||||
def makeSetup(**args):
|
||||
"""Return distribution from 'setup(**args)', without executing commands"""
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
Python Script Wrapper for Windows
|
||||
=================================
|
||||
|
||||
setuptools includes wrappers for Python scripts that allows them to be
|
||||
executed like regular windows programs. There are 2 wrappers, once
|
||||
for command-line programs, cli.exe, and one for graphica programs,
|
||||
gui.exe. These programs are almost identical, function pretty much
|
||||
the same way, and are generated from the same source file. In this
|
||||
document, we'll demonstrate use of the command-line program only. The
|
||||
wrapper programs are used by copying them to the directory containing
|
||||
the script they are to wrap and with the same name as the script they
|
||||
are to wrap. In the rest of this document, we'll give an example that
|
||||
will illustrate this.
|
||||
|
||||
Let's create a simple script, foo-script.py:
|
||||
|
||||
>>> import os, sys, tempfile
|
||||
>>> sample_directory = tempfile.mkdtemp()
|
||||
>>> open(os.path.join(sample_directory, 'foo-script.py'), 'w').write(
|
||||
... """#!%(python_exe)s
|
||||
... import sys
|
||||
... input = repr(sys.stdin.read())
|
||||
... print sys.argv[0][-14:]
|
||||
... print sys.argv[1:]
|
||||
... print input
|
||||
... if __debug__:
|
||||
... print 'non-optimized'
|
||||
... """ % dict(python_exe=sys.executable))
|
||||
|
||||
Note that the script starts with a Unix-style '#!' line saying which
|
||||
Python executable to run. The wrapper will use this to find the
|
||||
correct Python executable.
|
||||
|
||||
We'll also copy cli.exe to the sample-directory with the name foo.exe:
|
||||
|
||||
>>> import pkg_resources
|
||||
>>> open(os.path.join(sample_directory, 'foo.exe'), 'wb').write(
|
||||
... pkg_resources.resource_string('setuptools', 'cli.exe')
|
||||
... )
|
||||
|
||||
When the copy of cli.exe, foo.exe in this example, runs, it examines
|
||||
the path name it was run with and computes a Python script path name
|
||||
by removing the '.exe' suffic and adding the '-script.py' suffix. (For
|
||||
GUI programs, the suffix '-script-pyw' is added.) This is why we
|
||||
named out script the way we did. Now we can run out script by running
|
||||
the wrapper:
|
||||
|
||||
>>> import os
|
||||
>>> input, output = os.popen4(os.path.join(sample_directory, 'foo.exe')
|
||||
... + r' arg1 "arg 2" "arg \"2\\\"" "arg 4\\" "arg5 a\\b')
|
||||
>>> input.write('hello\nworld\n')
|
||||
>>> input.close()
|
||||
>>> print output.read(),
|
||||
\foo-script.py
|
||||
['arg1', 'arg 2', 'arg "2\\"', 'arg 4\\', 'arg5 a\\\\b']
|
||||
'hello\nworld\n'
|
||||
non-optimized
|
||||
|
||||
This example was a little pathological in that it exercised windows
|
||||
(MS C runtime) quoting rules:
|
||||
|
||||
- Strings containing spaces are surrounded by double quotes.
|
||||
|
||||
- Double quotes in strings need to be escaped by preceding them with
|
||||
back slashes.
|
||||
|
||||
- One or more backslashes preceding double quotes quotes need to be
|
||||
escaped by preceding each of them them with back slashes.
|
||||
|
||||
Specifying Python Command-line Options
|
||||
--------------------------------------
|
||||
|
||||
You can specify a single argument on the '#!' line. This can be used
|
||||
to specify Python options like -O, to run in optimized mode or -i
|
||||
to start the interactive interpreter. You can combine multiple
|
||||
options as usual. For example, to run in optimized mode and
|
||||
enter the interpreter after running the script, you could use -Oi:
|
||||
|
||||
>>> open(os.path.join(sample_directory, 'foo-script.py'), 'w').write(
|
||||
... """#!%(python_exe)s -Oi
|
||||
... import sys
|
||||
... input = repr(sys.stdin.read())
|
||||
... print sys.argv[0][-14:]
|
||||
... print sys.argv[1:]
|
||||
... print input
|
||||
... if __debug__:
|
||||
... print 'non-optimized'
|
||||
... sys.ps1 = '---'
|
||||
... """ % dict(python_exe=sys.executable))
|
||||
|
||||
>>> input, output = os.popen4(os.path.join(sample_directory, 'foo.exe'))
|
||||
>>> input.close()
|
||||
>>> print output.read(),
|
||||
\foo-script.py
|
||||
[]
|
||||
''
|
||||
---
|
||||
|
||||
|
||||
We're done with the sample_directory:
|
||||
|
||||
>>> import shutil
|
||||
>>> shutil.rmtree(sample_directory)
|
Loading…
Reference in New Issue