diff options
-rwxr-xr-x | launcher.c | 88 | ||||
-rwxr-xr-x | setuptools/cli.exe | bin | 6144 -> 7680 bytes | |||
-rwxr-xr-x | setuptools/gui.exe | bin | 6144 -> 7680 bytes | |||
-rw-r--r-- | setuptools/tests/__init__.py | 15 | ||||
-rw-r--r-- | setuptools/tests/win_script_wrapper.txt | 103 |
5 files changed, 191 insertions, 15 deletions
@@ -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) { diff --git a/setuptools/cli.exe b/setuptools/cli.exe Binary files differindex fc833396..aa601168 100755 --- a/setuptools/cli.exe +++ b/setuptools/cli.exe diff --git a/setuptools/gui.exe b/setuptools/gui.exe Binary files differindex 63ff35f4..5589a8f6 100755 --- a/setuptools/gui.exe +++ b/setuptools/gui.exe diff --git a/setuptools/tests/__init__.py b/setuptools/tests/__init__.py index ea469446..14942c61 100644 --- a/setuptools/tests/__init__.py +++ b/setuptools/tests/__init__.py @@ -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""" diff --git a/setuptools/tests/win_script_wrapper.txt b/setuptools/tests/win_script_wrapper.txt new file mode 100644 index 00000000..db1a7c3b --- /dev/null +++ b/setuptools/tests/win_script_wrapper.txt @@ -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)
|