diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -220,6 +220,26 @@ Discard docstrings in addition to the :option:`-O` optimizations. +.. cmdoption:: --path0 dir + + Explicitly initialize ``sys.path[0]`` to ``dir``. If used multiple times, + all but the last one are ignored. The :option:`--nopath0` option will + negate the usage of any preceding ``--path0``. + + See the entry on :data:`~sys.path` for more information on the normal + implicit behavior the interpreter regarding ``sys.path[0]``. + + .. versionadded:: 3.3 + + +.. cmdoption:: --nopath0 + + Disable initialization of ``sys.path[0]``. The :option:`--path0` option + will negate the usage of any preceding ``--nopath0``. + + .. versionadded:: 3.3 + + .. cmdoption:: -q Don't display the copyright and version messages even in interactive mode. diff --git a/Include/pygetopt.h b/Include/pygetopt.h --- a/Include/pygetopt.h +++ b/Include/pygetopt.h @@ -9,6 +9,7 @@ PyAPI_DATA(int) _PyOS_opterr; PyAPI_DATA(int) _PyOS_optind; PyAPI_DATA(wchar_t *) _PyOS_optarg; +PyAPI_DATA(wchar_t *) _PyOS_optlong; PyAPI_FUNC(void) _PyOS_ResetGetOpt(void); #endif diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -61,6 +61,40 @@ opts = eval(out.splitlines()[0]) self.assertEqual(opts, {'a': True, 'b': 'c,d=e'}) + def test_path(self): + path = tempfile.gettempdir() + path_bytes = path.encode('ascii') + + rc, out, err = assert_python_ok('-c', 'import sys; print(sys.path[0])') + path0 = out.splitlines()[0] + self.assertEqual(path0, b'') + + rc, out, err = assert_python_ok('--path0', path, + '-c', 'import sys; print(sys.path[0])') + path0 = out.splitlines()[0] + self.assertEqual(path0, path_bytes) + + rc, out, err = assert_python_ok('--nopath0', + '-c', 'import sys; print(sys.path[0])') + path0 = out.splitlines()[0] + self.assertNotEqual(path0, b'') + + rc, out, err = assert_python_ok('--path0', path, '--nopath0', + '-c', 'import sys; print(sys.path[0])') + path0 = out.splitlines()[0] + self.assertNotEqual(path0, path_bytes) + self.assertNotEqual(path0, b'') + + rc, out, err = assert_python_ok('--nopath0', '--path0', path, + '-c', 'import sys; print(sys.path[0])') + path0 = out.splitlines()[0] + self.assertEqual(path0, path_bytes) + + rc, out, err = assert_python_ok('--path0', '/tmp', '--path0', path, + '-c', 'import sys; print(sys.path[0])') + path0 = out.splitlines()[0] + self.assertEqual(path0, path_bytes) + def test_run_module(self): # Test expected operation of the '-m' switch # Switch needs an argument diff --git a/Modules/main.c b/Modules/main.c --- a/Modules/main.c +++ b/Modules/main.c @@ -88,6 +88,8 @@ -X opt : set implementation-specific option\n\ "; static char *usage_4 = "\ +--path0 dir : initialize sys.path[0] to dir\n\ +--nopath0 : disable sys.path[0] initialization\n\ file : program read from script file\n\ - : program read from stdin (default; interactive mode if a tty)\n\ arg ...: arguments passed to program in sys.argv[1:]\n\n\ @@ -319,6 +321,7 @@ wchar_t *command = NULL; wchar_t *filename = NULL; wchar_t *module = NULL; + wchar_t *path0 = NULL; FILE *fp = stdin; char *p; #ifdef MS_WINDOWS @@ -462,6 +465,16 @@ /* This space reserved for other options */ + case '\0': + /* a long option without a short option */ + + if (wcscmp(_PyOS_optlong, L"path0") == 0) + { + path0 = _PyOS_optarg; + } + + break; + default: return usage(2, argv[0]); /*NOTREACHED*/ @@ -644,7 +657,48 @@ argv[_PyOS_optind] = L"-m"; } - PySys_SetArgv(argc-_PyOS_optind, argv+_PyOS_optind); + /* if either the -p or -P options were used, path0 will not be NULL */ + PySys_SetArgvEx(argc-_PyOS_optind, argv+_PyOS_optind, (path0 == NULL)); + + /* handle the "-P" command-line option */ + if (path0 != NULL && *path0 == L'\0') { + /* nothing will be inserted to sys.path[0] */ + } + + /* handle the "-p dir" command-line option */ + else if (path0 != NULL) { + /* path0 will be inserted to sys.path[0] */ + int res; + PyObject *newpath0, *syspath; + + newpath0 = PyUnicode_FromWideChar(path0, wcslen(path0)); + if (newpath0 == NULL) { + /* XXX shouldn't this call Py_FatalError() instead? */ + PyErr_Clear(); + fprintf(stderr, "could not create string for sys.path[0]; " + "falling back to default behavior"); + PySys_SetArgv(argc-_PyOS_optind, argv+_PyOS_optind); + } + + syspath = PySys_GetObject("path"); + if (syspath == NULL) { + /* XXX shouldn't this call Py_FatalError() instead? */ + PyErr_Clear(); + fprintf(stderr, "could not get sys.path; " + "falling back to default behavior"); + PySys_SetArgv(argc-_PyOS_optind, argv+_PyOS_optind); + } + + res = PyList_Insert(syspath, 0, newpath0); + Py_DECREF(newpath0); + if (res < 0) { + /* XXX shouldn't this call Py_FatalError() instead? */ + PyErr_Clear(); + fprintf(stderr, "could not add %S to sys.path; " + "falling back to default behavior", path0); + PySys_SetArgv(argc-_PyOS_optind, argv+_PyOS_optind); + } + } if ((Py_InspectFlag || (command == NULL && filename == NULL && module == NULL)) && isatty(fileno(stdin))) { diff --git a/Python/getopt.c b/Python/getopt.c --- a/Python/getopt.c +++ b/Python/getopt.c @@ -40,6 +40,7 @@ int _PyOS_opterr = 1; /* generate error messages */ int _PyOS_optind = 1; /* index into argv array */ wchar_t *_PyOS_optarg = NULL; /* optional argument */ +wchar_t *_PyOS_optlong = NULL; /* long option without short option */ static wchar_t *opt_ptr = L""; @@ -48,6 +49,7 @@ _PyOS_opterr = 1; _PyOS_optind = 1; _PyOS_optarg = NULL; + _PyOS_optlong = NULL; opt_ptr = L""; } @@ -57,35 +59,55 @@ wchar_t option; if (*opt_ptr == '\0') { + wchar_t *arg = argv[_PyOS_optind]; if (_PyOS_optind >= argc) return -1; #ifdef MS_WINDOWS - else if (wcscmp(argv[_PyOS_optind], L"/?") == 0) { + else if (wcscmp(arg, L"/?") == 0) { ++_PyOS_optind; return 'h'; } #endif - else if (argv[_PyOS_optind][0] != L'-' || - argv[_PyOS_optind][1] == L'\0' /* lone dash */ ) + else if (arg[0] != L'-' || arg[1] == L'\0' /* lone dash */ ) return -1; - else if (wcscmp(argv[_PyOS_optind], L"--") == 0) { + else if (wcscmp(arg, L"--") == 0) { ++_PyOS_optind; return -1; } - else if (wcscmp(argv[_PyOS_optind], L"--help") == 0) { + else if (wcscmp(arg, L"--help") == 0) { ++_PyOS_optind; return 'h'; } - else if (wcscmp(argv[_PyOS_optind], L"--version") == 0) { + else if (wcscmp(arg, L"--version") == 0) { ++_PyOS_optind; return 'V'; } + else if (wcscmp(arg, L"--nopath0") == 0) { + ++_PyOS_optind; + _PyOS_optarg = L"\0"; + _PyOS_optlong = L"path0"; + return '\0'; + } + + else if (wcscmp(arg, L"--path0") == 0) { + _PyOS_optarg = argv[++_PyOS_optind]; + ++_PyOS_optind; + _PyOS_optlong = L"path0"; + return '\0'; + } + else if (wcsstr(arg, L"--path0=") == arg) { + _PyOS_optarg = arg + 8; + ++_PyOS_optind; + _PyOS_optlong = L"path0"; + return '\0'; + } + opt_ptr = &argv[_PyOS_optind++][1]; }