# HG changeset patch # User Bram Moolenaar # Date 1370892449 -7200 # Node ID 70b1178dec7919120632cdeee6056e38108356a7 # Parent 54430b55ceff27ba9ddb2bc929be2f56addfa9ec updated for version 7.3.1163 Problem: Not easy to load Python modules. Solution: Search "python2", "python3" and "pythonx" directories in 'runtimepath' for Python modules. (ZyX) diff --git a/runtime/doc/if_pyth.txt b/runtime/doc/if_pyth.txt --- a/runtime/doc/if_pyth.txt +++ b/runtime/doc/if_pyth.txt @@ -180,6 +180,12 @@ vim.strwidth(str) *python-strwidth* Like |strwidth()|: returns number of display cells str occupies, tab is counted as one cell. +vim.foreach_rtp(callable) *python-foreach_rtp* + Call the given callable for each path in 'runtimepath' until either + callable returns something but None, the exception is raised or there + are no longer paths. If stopped in case callable returned non-None, + vim.foreach_rtp function returns the value returned by callable. + vim.chdir(*args, **kwargs) *python-chdir* vim.fchdir(*args, **kwargs) *python-fchdir* Run os.chdir or os.fchdir, then all appropriate vim stuff. @@ -300,6 +306,113 @@ Output from Python *python-output* supported, and may cause the program to crash. This should probably be fixed. + *python2-directory* *python3-directory* *pythonx-directory* +Python 'runtimepath' handling *python-special-path* + +In python vim.VIM_SPECIAL_PATH special directory is used as a replacement for +the list of paths found in 'runtimepath': with this directory in sys.path and +vim.path_hooks in sys.path_hooks python will try to load module from +{rtp}/python2 (or python3) and {rtp}/pythonx (for both python versions) for +each {rtp} found in 'runtimepath'. + +Implementation for python 2 is the following: usual importing code with empty +lists in place of sys.path_hooks and sys.meta_path. Code is similar to the +below, but written in C: > + + # Assuming vim variable is already accessible and is set to the current + # module + import sys + + def find_module(fullname): + return vim + + def load_module(fullname): + # see vim._get_paths below + new_path = _get_paths() + + try: old_path = sys.path + except: pass + try: old_meta_path = sys.meta_path + except: pass + try: old_path_hooks = sys.path_hooks + except: pass + + sys.meta_path = [] + sys.path_hooks = sys.meta_path + sys.path = new_path + + try: + exec ('import ' + fullname + ' as m') # No actual exec in C code + return m + finally: + e = None + try: sys.path = old_path + except Exception as e: pass + try: sys.meta_path = old_meta_path + except Exception as e: pass + try: sys.path_hooks = old_path_hooks + except Exception as e: pass + if e: + raise e + + def path_hook(d): + if d == VIM_SPECIAL_PATH: + return vim + raise ImportError + + sys.path_hooks.append(path_hook) + +Implementation for python 3 is cleaner: code is similar to the following, but, +again, written in C: > + + from importlib.machinery import PathFinder + import sys + + class Finder(PathFinder): + @classmethod + def find_module(cls, fullname): + # see vim._get_paths below + new_path = _get_paths() + + # super().find_module is also a class method + # super() is not used because this variant is easier to implement + # in C + return PathFinder.find_module(fullname, new_path) + + def path_hook(path): + if path == VIM_SPECIAL_PATH: + return Finder + raise ImportError + + sys.path_hooks.append(path_hook) + +vim.VIM_SPECIAL_PATH *python-VIM_SPECIAL_PATH* + String constant used in conjunction with vim path hook. If path hook + installed by vim is requested to handle anything but path equal to + vim.VIM_SPECIAL_PATH constant it raises ImportError. In the only other + case it uses special loader. + + Note: you must not use value of this constant directly, always use + vim.VIM_SPECIAL_PATH object. + +vim.load_module(name) *python-load_module* +vim.find_module(...) *python-find_module* +vim.path_hook(path) *python-path_hook* + Methods or objects used to implement path loading as described above. + You should not be using any of these directly except for vim.path_hook + in case you need to do something with sys.meta_path. It is not + guaranteed that any of the objects will exist in the future vim + versions. In fact, load_module and find_module methods do not exists + in python3. + +vim._get_paths *python-_get_paths* + Methods returning a list of paths which will be searched for by path + hook. You should not rely on this method being present in future + versions, but can use it for debugging. + + It returns a list of {rtp}/python2 (or {rtp}/python3) and + {rtp}/pythonx directories for each {rtp} in 'runtimepath'. + ============================================================================== 3. Buffer objects *python-buffer* diff --git a/src/auto/configure b/src/auto/configure --- a/src/auto/configure +++ b/src/auto/configure @@ -5289,10 +5289,10 @@ fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $vi_cv_var_python_version" >&5 $as_echo "$vi_cv_var_python_version" >&6; } - { $as_echo "$as_me:${as_lineno-$LINENO}: checking Python is 2.2 or better" >&5 -$as_echo_n "checking Python is 2.2 or better... " >&6; } + { $as_echo "$as_me:${as_lineno-$LINENO}: checking Python is 2.3 or better" >&5 +$as_echo_n "checking Python is 2.3 or better... " >&6; } if ${vi_cv_path_python} -c \ - "import sys; sys.exit(${vi_cv_var_python_version} < 2.2)" + "import sys; sys.exit(${vi_cv_var_python_version} < 2.3)" then { $as_echo "$as_me:${as_lineno-$LINENO}: result: yep" >&5 $as_echo "yep" >&6; } diff --git a/src/configure.in b/src/configure.in --- a/src/configure.in +++ b/src/configure.in @@ -863,10 +863,10 @@ if test "$enable_pythoninterp" = "yes" - ${vi_cv_path_python} -c 'import sys; print sys.version[:3]'` ]]) - dnl -- it must be at least version 2.2 - AC_MSG_CHECKING(Python is 2.2 or better) + dnl -- it must be at least version 2.3 + AC_MSG_CHECKING(Python is 2.3 or better) if ${vi_cv_path_python} -c \ - "import sys; sys.exit(${vi_cv_var_python_version} < 2.2)" + "import sys; sys.exit(${vi_cv_var_python_version} < 2.3)" then AC_MSG_RESULT(yep) diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c --- a/src/ex_cmds2.c +++ b/src/ex_cmds2.c @@ -2737,6 +2737,10 @@ source_runtime(name, all) * When "all" is TRUE repeat for all matches, otherwise only the first one is * used. * Returns OK when at least one match found, FAIL otherwise. + * + * If "name" is NULL calls callback for each entry in runtimepath. Cookie is + * passed by reference in this case, setting it to NULL indicates that callback + * has done its job. */ int do_in_runtimepath(name, all, callback, cookie) @@ -2768,7 +2772,7 @@ do_in_runtimepath(name, all, callback, c buf = alloc(MAXPATHL); if (buf != NULL && rtp_copy != NULL) { - if (p_verbose > 1) + if (p_verbose > 1 && name != NULL) { verbose_enter(); smsg((char_u *)_("Searching for \"%s\" in \"%s\""), @@ -2782,7 +2786,13 @@ do_in_runtimepath(name, all, callback, c { /* Copy the path from 'runtimepath' to buf[]. */ copy_option_part(&rtp, buf, MAXPATHL, ","); - if (STRLEN(buf) + STRLEN(name) + 2 < MAXPATHL) + if (name == NULL) + { + (*callback)(buf, (void *) &cookie); + if (!did_one) + did_one = (cookie == NULL); + } + else if (STRLEN(buf) + STRLEN(name) + 2 < MAXPATHL) { add_pathsep(buf); tail = buf + STRLEN(buf); @@ -2821,7 +2831,7 @@ do_in_runtimepath(name, all, callback, c } vim_free(buf); vim_free(rtp_copy); - if (p_verbose > 0 && !did_one) + if (p_verbose > 0 && !did_one && name != NULL) { verbose_enter(); smsg((char_u *)_("not found in 'runtimepath': \"%s\""), name); diff --git a/src/if_py_both.h b/src/if_py_both.h --- a/src/if_py_both.h +++ b/src/if_py_both.h @@ -24,6 +24,8 @@ typedef int Py_ssize_t; /* Python 2.4 a #endif #define DOPY_FUNC "_vim_pydo" +static const char *vim_special_path = "_vim_path_"; + #define PyErr_SetVim(str) PyErr_SetString(VimError, str) #define RAISE_NO_EMPTY_KEYS PyErr_SetString(PyExc_ValueError, \ @@ -55,6 +57,8 @@ static PyObject *globals; static PyObject *py_chdir; static PyObject *py_fchdir; static PyObject *py_getcwd; +static PyObject *vim_module; +static PyObject *vim_special_path_object; /* * obtain a lock on the Vim data structures @@ -779,19 +783,168 @@ VimFchdir(PyObject *self UNUSED, PyObjec return _VimChdir(py_fchdir, args, kwargs); } +typedef struct { + PyObject *callable; + PyObject *result; +} map_rtp_data; + + static void +map_rtp_callback(char_u *path, void *_data) +{ + void **data = (void **) _data; + PyObject *pathObject; + map_rtp_data *mr_data = *((map_rtp_data **) data); + + if (!(pathObject = PyString_FromString((char *) path))) + { + *data = NULL; + return; + } + + mr_data->result = PyObject_CallFunctionObjArgs(mr_data->callable, + pathObject, NULL); + + Py_DECREF(pathObject); + + if (!mr_data->result || mr_data->result != Py_None) + *data = NULL; + else + { + Py_DECREF(mr_data->result); + mr_data->result = NULL; + } +} + + static PyObject * +VimForeachRTP(PyObject *self UNUSED, PyObject *args) +{ + map_rtp_data data; + + if (!PyArg_ParseTuple(args, "O", &data.callable)) + return NULL; + + data.result = NULL; + + do_in_runtimepath(NULL, FALSE, &map_rtp_callback, &data); + + if (data.result == NULL) + { + if (PyErr_Occurred()) + return NULL; + else + { + Py_INCREF(Py_None); + return Py_None; + } + } + return data.result; +} + +/* + * _vim_runtimepath_ special path implementation. + */ + + static void +map_finder_callback(char_u *path, void *_data) +{ + void **data = (void **) _data; + PyObject *list = *((PyObject **) data); + PyObject *pathObject1, *pathObject2; + char *pathbuf; + size_t pathlen; + + pathlen = STRLEN(path); + +#if PY_MAJOR_VERSION < 3 +# define PY_MAIN_DIR_STRING "python2" +#else +# define PY_MAIN_DIR_STRING "python3" +#endif +#define PY_ALTERNATE_DIR_STRING "pythonx" + +#define PYTHONX_STRING_LENGTH 7 /* STRLEN("pythonx") */ + if (!(pathbuf = PyMem_New(char, + pathlen + STRLEN(PATHSEPSTR) + PYTHONX_STRING_LENGTH + 1))) + { + PyErr_NoMemory(); + *data = NULL; + return; + } + + mch_memmove(pathbuf, path, pathlen + 1); + add_pathsep((char_u *) pathbuf); + + pathlen = STRLEN(pathbuf); + mch_memmove(pathbuf + pathlen, PY_MAIN_DIR_STRING, + PYTHONX_STRING_LENGTH + 1); + + if (!(pathObject1 = PyString_FromString(pathbuf))) + { + *data = NULL; + PyMem_Free(pathbuf); + return; + } + + mch_memmove(pathbuf + pathlen, PY_ALTERNATE_DIR_STRING, + PYTHONX_STRING_LENGTH + 1); + + if (!(pathObject2 = PyString_FromString(pathbuf))) + { + Py_DECREF(pathObject1); + PyMem_Free(pathbuf); + *data = NULL; + return; + } + + PyMem_Free(pathbuf); + + if (PyList_Append(list, pathObject1) + || PyList_Append(list, pathObject2)) + *data = NULL; + + Py_DECREF(pathObject1); + Py_DECREF(pathObject2); +} + + static PyObject * +Vim_GetPaths(PyObject *self UNUSED) +{ + PyObject *r; + + if (!(r = PyList_New(0))) + return NULL; + + do_in_runtimepath(NULL, FALSE, &map_finder_callback, r); + + if (PyErr_Occurred()) + { + Py_DECREF(r); + return NULL; + } + + return r; +} + /* * Vim module - Definitions */ static struct PyMethodDef VimMethods[] = { - /* name, function, calling, documentation */ - {"command", VimCommand, METH_VARARGS, "Execute a Vim ex-mode command" }, - {"eval", VimEval, METH_VARARGS, "Evaluate an expression using Vim evaluator" }, - {"bindeval", VimEvalPy, METH_VARARGS, "Like eval(), but returns objects attached to vim ones"}, - {"strwidth", VimStrwidth, METH_VARARGS, "Screen string width, counts as having width 1"}, - {"chdir", (PyCFunction)VimChdir, METH_VARARGS|METH_KEYWORDS, "Change directory"}, - {"fchdir", (PyCFunction)VimFchdir, METH_VARARGS|METH_KEYWORDS, "Change directory"}, - { NULL, NULL, 0, NULL } + /* name, function, calling, documentation */ + {"command", VimCommand, METH_VARARGS, "Execute a Vim ex-mode command" }, + {"eval", VimEval, METH_VARARGS, "Evaluate an expression using Vim evaluator" }, + {"bindeval", VimEvalPy, METH_VARARGS, "Like eval(), but returns objects attached to vim ones"}, + {"strwidth", VimStrwidth, METH_VARARGS, "Screen string width, counts as having width 1"}, + {"chdir", (PyCFunction)VimChdir, METH_VARARGS|METH_KEYWORDS, "Change directory"}, + {"fchdir", (PyCFunction)VimFchdir, METH_VARARGS|METH_KEYWORDS, "Change directory"}, + {"foreach_rtp", VimForeachRTP, METH_VARARGS, "Call given callable for each path in &rtp"}, +#if PY_MAJOR_VERSION < 3 + {"find_module", FinderFindModule, METH_VARARGS, "Internal use only, returns loader object for any input it receives"}, + {"load_module", LoaderLoadModule, METH_VARARGS, "Internal use only, tries importing the given module from &rtp by temporary mocking sys.path (to an rtp-based one) and unsetting sys.meta_path and sys.path_hooks"}, +#endif + {"path_hook", VimPathHook, METH_VARARGS, "Hook function to install in sys.path_hooks"}, + {"_get_paths", (PyCFunction)Vim_GetPaths, METH_NOARGS, "Get &rtp-based additions to sys.path"}, + { NULL, NULL, 0, NULL} }; /* @@ -5036,6 +5189,14 @@ typedef struct } CurrentObject; static PyTypeObject CurrentType; +#if PY_MAJOR_VERSION >= 3 +typedef struct +{ + PyObject_HEAD +} FinderObject; +static PyTypeObject FinderType; +#endif + static void init_structs(void) { @@ -5281,6 +5442,81 @@ init_types() PYTYPE_READY(FunctionType); PYTYPE_READY(OptionsType); PYTYPE_READY(OutputType); +#if PY_MAJOR_VERSION >= 3 + PYTYPE_READY(FinderType); +#endif + return 0; +} + + static int +init_sys_path() +{ + PyObject *path; + PyObject *path_hook; + PyObject *path_hooks; + + if (!(path_hook = PyObject_GetAttrString(vim_module, "path_hook"))) + return -1; + + if (!(path_hooks = PySys_GetObject("path_hooks"))) + { + PyErr_Clear(); + path_hooks = PyList_New(1); + PyList_SET_ITEM(path_hooks, 0, path_hook); + if (PySys_SetObject("path_hooks", path_hooks)) + { + Py_DECREF(path_hooks); + return -1; + } + Py_DECREF(path_hooks); + } + else if (PyList_Check(path_hooks)) + { + if (PyList_Append(path_hooks, path_hook)) + { + Py_DECREF(path_hook); + return -1; + } + Py_DECREF(path_hook); + } + else + { + VimTryStart(); + EMSG(_("Failed to set path hook: sys.path_hooks is not a list\n" + "You should now do the following:\n" + "- append vim.path_hook to sys.path_hooks\n" + "- append vim.VIM_SPECIAL_PATH to sys.path\n")); + VimTryEnd(); /* Discard the error */ + Py_DECREF(path_hook); + return 0; + } + + if (!(path = PySys_GetObject("path"))) + { + PyErr_Clear(); + path = PyList_New(1); + Py_INCREF(vim_special_path_object); + PyList_SET_ITEM(path, 0, vim_special_path_object); + if (PySys_SetObject("path", path)) + { + Py_DECREF(path); + return -1; + } + Py_DECREF(path); + } + else if (PyList_Check(path)) + { + if (PyList_Append(path, vim_special_path_object)) + return -1; + } + else + { + VimTryStart(); + EMSG(_("Failed to set path: sys.path is not a list\n" + "You should now append vim.VIM_SPECIAL_PATH to sys.path")); + VimTryEnd(); /* Discard the error */ + } + return 0; } @@ -5332,6 +5568,9 @@ static struct object_constant { {"List", (PyObject *)&ListType}, {"Function", (PyObject *)&FunctionType}, {"Options", (PyObject *)&OptionsType}, +#if PY_MAJOR_VERSION >= 3 + {"Finder", (PyObject *)&FinderType}, +#endif }; typedef int (*object_adder)(PyObject *, const char *, PyObject *); @@ -5417,5 +5656,17 @@ populate_module(PyObject *m, object_adde else PyErr_Clear(); + if (!(vim_special_path_object = PyString_FromString(vim_special_path))) + return -1; + + ADD_OBJECT(m, "VIM_SPECIAL_PATH", vim_special_path_object); + +#if PY_MAJOR_VERSION >= 3 + ADD_OBJECT(m, "_PathFinder", path_finder); + ADD_CHECKED_OBJECT(m, "_find_module", + (py_find_module = PyObject_GetAttrString(path_finder, + "find_module"))); +#endif + return 0; } diff --git a/src/if_python.c b/src/if_python.c --- a/src/if_python.c +++ b/src/if_python.c @@ -24,9 +24,9 @@ /* uncomment this if used with the debug version of python. * Checked on 2.7.4. */ /* #define Py_DEBUG */ -/* Note: most of time you can add -DPy_DEBUG to CFLAGS in place of uncommenting +/* Note: most of time you can add -DPy_DEBUG to CFLAGS in place of uncommenting */ -/* uncomment this if used with the debug version of python, but without its +/* uncomment this if used with the debug version of python, but without its * allocator */ /* #define Py_DEBUG_NO_PYMALLOC */ @@ -168,6 +168,7 @@ struct PyMethodDef { Py_ssize_t a; }; # define PyErr_SetNone dll_PyErr_SetNone # define PyErr_SetString dll_PyErr_SetString # define PyErr_SetObject dll_PyErr_SetObject +# define PyErr_ExceptionMatches dll_PyErr_ExceptionMatches # define PyEval_InitThreads dll_PyEval_InitThreads # define PyEval_RestoreThread dll_PyEval_RestoreThread # define PyEval_SaveThread dll_PyEval_SaveThread @@ -184,6 +185,7 @@ struct PyMethodDef { Py_ssize_t a; }; # define PyLong_Type (*dll_PyLong_Type) # define PyList_GetItem dll_PyList_GetItem # define PyList_Append dll_PyList_Append +# define PyList_Insert dll_PyList_Insert # define PyList_New dll_PyList_New # define PyList_SetItem dll_PyList_SetItem # define PyList_Size dll_PyList_Size @@ -233,6 +235,7 @@ struct PyMethodDef { Py_ssize_t a; }; # define PyFloat_Type (*dll_PyFloat_Type) # define PyImport_AddModule (*dll_PyImport_AddModule) # define PySys_SetObject dll_PySys_SetObject +# define PySys_GetObject dll_PySys_GetObject # define PySys_SetArgv dll_PySys_SetArgv # define PyType_Type (*dll_PyType_Type) # define PyType_Ready (*dll_PyType_Ready) @@ -305,6 +308,7 @@ static PyObject*(*dll_PyErr_Occurred)(vo static void(*dll_PyErr_SetNone)(PyObject *); static void(*dll_PyErr_SetString)(PyObject *, const char *); static void(*dll_PyErr_SetObject)(PyObject *, PyObject *); +static int(*dll_PyErr_ExceptionMatches)(PyObject *); static void(*dll_PyEval_InitThreads)(void); static void(*dll_PyEval_RestoreThread)(PyThreadState *); static PyThreadState*(*dll_PyEval_SaveThread)(void); @@ -320,7 +324,8 @@ static PyTypeObject* dll_PyBool_Type; static PyTypeObject* dll_PyInt_Type; static PyTypeObject* dll_PyLong_Type; static PyObject*(*dll_PyList_GetItem)(PyObject *, PyInt); -static PyObject*(*dll_PyList_Append)(PyObject *, PyObject *); +static int(*dll_PyList_Append)(PyObject *, PyObject *); +static int(*dll_PyList_Insert)(PyObject *, int, PyObject *); static PyObject*(*dll_PyList_New)(PyInt size); static int(*dll_PyList_SetItem)(PyObject *, PyInt, PyObject *); static PyInt(*dll_PyList_Size)(PyObject *); @@ -366,6 +371,7 @@ static double(*dll_PyFloat_AsDouble)(PyO static PyObject*(*dll_PyFloat_FromDouble)(double); static PyTypeObject* dll_PyFloat_Type; static int(*dll_PySys_SetObject)(char *, PyObject *); +static PyObject *(*dll_PySys_GetObject)(char *); static int(*dll_PySys_SetArgv)(int, char **); static PyTypeObject* dll_PyType_Type; static int (*dll_PyType_Ready)(PyTypeObject *type); @@ -431,6 +437,7 @@ static PyObject *imp_PyExc_KeyboardInter static PyObject *imp_PyExc_TypeError; static PyObject *imp_PyExc_ValueError; static PyObject *imp_PyExc_RuntimeError; +static PyObject *imp_PyExc_ImportError; # define PyExc_AttributeError imp_PyExc_AttributeError # define PyExc_IndexError imp_PyExc_IndexError @@ -439,6 +446,7 @@ static PyObject *imp_PyExc_RuntimeError; # define PyExc_TypeError imp_PyExc_TypeError # define PyExc_ValueError imp_PyExc_ValueError # define PyExc_RuntimeError imp_PyExc_RuntimeError +# define PyExc_ImportError imp_PyExc_ImportError /* * Table of name to function pointer of python. @@ -471,6 +479,7 @@ static struct {"PyErr_SetNone", (PYTHON_PROC*)&dll_PyErr_SetNone}, {"PyErr_SetString", (PYTHON_PROC*)&dll_PyErr_SetString}, {"PyErr_SetObject", (PYTHON_PROC*)&dll_PyErr_SetObject}, + {"PyErr_ExceptionMatches", (PYTHON_PROC*)&dll_PyErr_ExceptionMatches}, {"PyEval_InitThreads", (PYTHON_PROC*)&dll_PyEval_InitThreads}, {"PyEval_RestoreThread", (PYTHON_PROC*)&dll_PyEval_RestoreThread}, {"PyEval_SaveThread", (PYTHON_PROC*)&dll_PyEval_SaveThread}, @@ -487,6 +496,7 @@ static struct {"PyLong_Type", (PYTHON_PROC*)&dll_PyLong_Type}, {"PyList_GetItem", (PYTHON_PROC*)&dll_PyList_GetItem}, {"PyList_Append", (PYTHON_PROC*)&dll_PyList_Append}, + {"PyList_Insert", (PYTHON_PROC*)&dll_PyList_Insert}, {"PyList_New", (PYTHON_PROC*)&dll_PyList_New}, {"PyList_SetItem", (PYTHON_PROC*)&dll_PyList_SetItem}, {"PyList_Size", (PYTHON_PROC*)&dll_PyList_Size}, @@ -532,6 +542,7 @@ static struct {"PyFloat_FromDouble", (PYTHON_PROC*)&dll_PyFloat_FromDouble}, {"PyImport_AddModule", (PYTHON_PROC*)&dll_PyImport_AddModule}, {"PySys_SetObject", (PYTHON_PROC*)&dll_PySys_SetObject}, + {"PySys_GetObject", (PYTHON_PROC*)&dll_PySys_GetObject}, {"PySys_SetArgv", (PYTHON_PROC*)&dll_PySys_SetArgv}, {"PyType_Type", (PYTHON_PROC*)&dll_PyType_Type}, {"PyType_Ready", (PYTHON_PROC*)&dll_PyType_Ready}, @@ -706,6 +717,7 @@ get_exceptions(void) imp_PyExc_TypeError = PyDict_GetItemString(exdict, "TypeError"); imp_PyExc_ValueError = PyDict_GetItemString(exdict, "ValueError"); imp_PyExc_RuntimeError = PyDict_GetItemString(exdict, "RuntimeError"); + imp_PyExc_ImportError = PyDict_GetItemString(exdict, "ImportError"); Py_XINCREF(imp_PyExc_AttributeError); Py_XINCREF(imp_PyExc_IndexError); Py_XINCREF(imp_PyExc_KeyError); @@ -713,6 +725,7 @@ get_exceptions(void) Py_XINCREF(imp_PyExc_TypeError); Py_XINCREF(imp_PyExc_ValueError); Py_XINCREF(imp_PyExc_RuntimeError); + Py_XINCREF(imp_PyExc_ImportError); Py_XDECREF(exmod); } #endif /* DYNAMIC_PYTHON */ @@ -735,6 +748,10 @@ static PyObject *DictionaryGetattr(PyObj static PyObject *ListGetattr(PyObject *, char *); static PyObject *FunctionGetattr(PyObject *, char *); +static PyObject *LoaderLoadModule(PyObject *, PyObject *); +static PyObject *FinderFindModule(PyObject *, PyObject *); +static PyObject *VimPathHook(PyObject *, PyObject *); + #ifndef Py_VISIT # define Py_VISIT(obj) visit(obj, arg) #endif @@ -1359,11 +1376,112 @@ python_tabpage_free(tabpage_T *tab) } #endif + static PyObject * +LoaderLoadModule(PyObject *self, PyObject *args) +{ + char *fullname; + PyObject *path; + PyObject *meta_path; + PyObject *path_hooks; + PyObject *new_path; + PyObject *r; + PyObject *new_list; + + if (!PyArg_ParseTuple(args, "s", &fullname)) + return NULL; + + if (!(new_path = Vim_GetPaths(self))) + return NULL; + + if (!(new_list = PyList_New(0))) + return NULL; + +#define GET_SYS_OBJECT(objstr, obj) \ + obj = PySys_GetObject(objstr); \ + PyErr_Clear(); \ + Py_XINCREF(obj); + + GET_SYS_OBJECT("meta_path", meta_path); + if (PySys_SetObject("meta_path", new_list)) + { + Py_XDECREF(meta_path); + Py_DECREF(new_list); + return NULL; + } + Py_DECREF(new_list); /* Now it becomes a reference borrowed from + sys.meta_path */ + +#define RESTORE_SYS_OBJECT(objstr, obj) \ + if (obj) \ + { \ + PySys_SetObject(objstr, obj); \ + Py_DECREF(obj); \ + } + + GET_SYS_OBJECT("path_hooks", path_hooks); + if (PySys_SetObject("path_hooks", new_list)) + { + RESTORE_SYS_OBJECT("meta_path", meta_path); + Py_XDECREF(path_hooks); + return NULL; + } + + GET_SYS_OBJECT("path", path); + if (PySys_SetObject("path", new_path)) + { + RESTORE_SYS_OBJECT("meta_path", meta_path); + RESTORE_SYS_OBJECT("path_hooks", path_hooks); + Py_XDECREF(path); + return NULL; + } + Py_DECREF(new_path); + + r = PyImport_ImportModule(fullname); + + RESTORE_SYS_OBJECT("meta_path", meta_path); + RESTORE_SYS_OBJECT("path_hooks", path_hooks); + RESTORE_SYS_OBJECT("path", path); + + if (PyErr_Occurred()) + { + Py_XDECREF(r); + return NULL; + } + + return r; +} + + static PyObject * +FinderFindModule(PyObject *self UNUSED, PyObject *args UNUSED) +{ + /* + * Don't bother actually finding the module, it is delegated to the "loader" + * object (which is basically the same object: vim module). + */ + Py_INCREF(vim_module); + return vim_module; +} + + static PyObject * +VimPathHook(PyObject *self UNUSED, PyObject *args) +{ + char *path; + + if (PyArg_ParseTuple(args, "s", &path) + && STRCMP(path, vim_special_path) == 0) + { + Py_INCREF(vim_module); + return vim_module; + } + + PyErr_Clear(); + PyErr_SetNone(PyExc_ImportError); + return NULL; +} + static int PythonMod_Init(void) { - PyObject *mod; - /* The special value is removed from sys.path in Python_Init(). */ static char *(argv[2]) = {"/must>not&exist/foo", NULL}; @@ -1373,10 +1491,17 @@ PythonMod_Init(void) /* Set sys.argv[] to avoid a crash in warn(). */ PySys_SetArgv(1, argv); - mod = Py_InitModule4("vim", VimMethods, (char *)NULL, (PyObject *)NULL, - PYTHON_API_VERSION); + vim_module = Py_InitModule4("vim", VimMethods, (char *)NULL, + (PyObject *)NULL, PYTHON_API_VERSION); - return populate_module(mod, PyModule_AddObject, PyObject_GetAttrString); + if (populate_module(vim_module, PyModule_AddObject, + PyObject_GetAttrString)) + return -1; + + if (init_sys_path()) + return -1; + + return 0; } /************************************************************************* diff --git a/src/if_python3.c b/src/if_python3.c --- a/src/if_python3.c +++ b/src/if_python3.c @@ -134,6 +134,7 @@ # define PyErr_SetNone py3_PyErr_SetNone # define PyErr_SetString py3_PyErr_SetString # define PyErr_SetObject py3_PyErr_SetObject +# define PyErr_ExceptionMatches py3_PyErr_ExceptionMatches # define PyEval_InitThreads py3_PyEval_InitThreads # define PyEval_RestoreThread py3_PyEval_RestoreThread # define PyEval_SaveThread py3_PyEval_SaveThread @@ -143,6 +144,7 @@ # define PyLong_FromLong py3_PyLong_FromLong # define PyList_GetItem py3_PyList_GetItem # define PyList_Append py3_PyList_Append +# define PyList_Insert py3_PyList_Insert # define PyList_New py3_PyList_New # define PyList_SetItem py3_PyList_SetItem # define PyList_Size py3_PyList_Size @@ -177,6 +179,7 @@ # define PyEval_GetLocals py3_PyEval_GetLocals # define PyEval_GetGlobals py3_PyEval_GetGlobals # define PySys_SetObject py3_PySys_SetObject +# define PySys_GetObject py3_PySys_GetObject # define PySys_SetArgv py3_PySys_SetArgv # define PyType_Ready py3_PyType_Ready #undef Py_BuildValue @@ -268,7 +271,9 @@ static PyObject* (*py3_PyList_New)(Py_ss static PyGILState_STATE (*py3_PyGILState_Ensure)(void); static void (*py3_PyGILState_Release)(PyGILState_STATE); static int (*py3_PySys_SetObject)(char *, PyObject *); -static PyObject* (*py3_PyList_Append)(PyObject *, PyObject *); +static PyObject* (*py3_PySys_GetObject)(char *); +static int (*py3_PyList_Append)(PyObject *, PyObject *); +static int (*py3_PyList_Insert)(PyObject *, int, PyObject *); static Py_ssize_t (*py3_PyList_Size)(PyObject *); static int (*py3_PySequence_Check)(PyObject *); static Py_ssize_t (*py3_PySequence_Size)(PyObject *); @@ -284,6 +289,7 @@ static PyObject* (*py3_PyErr_NoMemory)(v static void (*py3_Py_Finalize)(void); static void (*py3_PyErr_SetString)(PyObject *, const char *); static void (*py3_PyErr_SetObject)(PyObject *, PyObject *); +static int (*py3_PyErr_ExceptionMatches)(PyObject *); static int (*py3_PyRun_SimpleString)(char *); static PyObject* (*py3_PyRun_String)(char *, int, PyObject *, PyObject *); static PyObject* (*py3_PyObject_GetAttrString)(PyObject *, const char *); @@ -393,6 +399,7 @@ static PyObject *p3imp_PyExc_KeyboardInt static PyObject *p3imp_PyExc_TypeError; static PyObject *p3imp_PyExc_ValueError; static PyObject *p3imp_PyExc_RuntimeError; +static PyObject *p3imp_PyExc_ImportError; # define PyExc_AttributeError p3imp_PyExc_AttributeError # define PyExc_IndexError p3imp_PyExc_IndexError @@ -401,6 +408,7 @@ static PyObject *p3imp_PyExc_RuntimeErro # define PyExc_TypeError p3imp_PyExc_TypeError # define PyExc_ValueError p3imp_PyExc_ValueError # define PyExc_RuntimeError p3imp_PyExc_RuntimeError +# define PyExc_ImportError p3imp_PyExc_ImportError /* * Table of name to function pointer of python. @@ -428,7 +436,9 @@ static struct {"PyGILState_Ensure", (PYTHON_PROC*)&py3_PyGILState_Ensure}, {"PyGILState_Release", (PYTHON_PROC*)&py3_PyGILState_Release}, {"PySys_SetObject", (PYTHON_PROC*)&py3_PySys_SetObject}, + {"PySys_GetObject", (PYTHON_PROC*)&py3_PySys_GetObject}, {"PyList_Append", (PYTHON_PROC*)&py3_PyList_Append}, + {"PyList_Insert", (PYTHON_PROC*)&py3_PyList_Insert}, {"PyList_Size", (PYTHON_PROC*)&py3_PyList_Size}, {"PySequence_Check", (PYTHON_PROC*)&py3_PySequence_Check}, {"PySequence_Size", (PYTHON_PROC*)&py3_PySequence_Size}, @@ -441,6 +451,7 @@ static struct {"Py_Finalize", (PYTHON_PROC*)&py3_Py_Finalize}, {"PyErr_SetString", (PYTHON_PROC*)&py3_PyErr_SetString}, {"PyErr_SetObject", (PYTHON_PROC*)&py3_PyErr_SetObject}, + {"PyErr_ExceptionMatches", (PYTHON_PROC*)&py3_PyErr_ExceptionMatches}, {"PyRun_SimpleString", (PYTHON_PROC*)&py3_PyRun_SimpleString}, {"PyRun_String", (PYTHON_PROC*)&py3_PyRun_String}, {"PyObject_GetAttrString", (PYTHON_PROC*)&py3_PyObject_GetAttrString}, @@ -664,6 +675,7 @@ get_py3_exceptions() p3imp_PyExc_TypeError = PyDict_GetItemString(exdict, "TypeError"); p3imp_PyExc_ValueError = PyDict_GetItemString(exdict, "ValueError"); p3imp_PyExc_RuntimeError = PyDict_GetItemString(exdict, "RuntimeError"); + p3imp_PyExc_ImportError = PyDict_GetItemString(exdict, "ImportError"); Py_XINCREF(p3imp_PyExc_AttributeError); Py_XINCREF(p3imp_PyExc_IndexError); Py_XINCREF(p3imp_PyExc_KeyError); @@ -671,6 +683,7 @@ get_py3_exceptions() Py_XINCREF(p3imp_PyExc_TypeError); Py_XINCREF(p3imp_PyExc_ValueError); Py_XINCREF(p3imp_PyExc_RuntimeError); + Py_XINCREF(p3imp_PyExc_ImportError); Py_XDECREF(exmod); } #endif /* DYNAMIC_PYTHON3 */ @@ -723,8 +736,13 @@ static PyObject *ListGetattro(PyObject * static int ListSetattro(PyObject *, PyObject *, PyObject *); static PyObject *FunctionGetattro(PyObject *, PyObject *); +static PyObject *VimPathHook(PyObject *, PyObject *); + static struct PyModuleDef vimmodule; +static PyObject *path_finder; +static PyObject *py_find_module = NULL; + #define PY_CAN_RECURSE /* @@ -1585,12 +1603,70 @@ python3_tabpage_free(tabpage_T *tab) #endif static PyObject * +VimPathHook(PyObject *self UNUSED, PyObject *args) +{ + char *path; + + if (PyArg_ParseTuple(args, "s", &path) + && STRCMP(path, vim_special_path) == 0) + { + Py_INCREF(&FinderType); + return (PyObject *) &FinderType; + } + + PyErr_Clear(); + PyErr_SetNone(PyExc_ImportError); + return NULL; +} + + static PyObject * +FinderFindModule(PyObject *cls UNUSED, PyObject *fullname) +{ + PyObject *new_path; + PyObject *r; + + if (!(new_path = Vim_GetPaths(NULL))) + return NULL; + + /* call find_module of the super() class */ + r = PyObject_CallFunctionObjArgs(py_find_module, fullname, new_path, NULL); + + Py_DECREF(new_path); + + return r; +} + +static struct PyMethodDef FinderMethods[] = { + {"find_module", FinderFindModule, METH_CLASS|METH_O, ""}, + {NULL, NULL, 0, NULL} +}; + + static PyObject * Py3Init_vim(void) { - PyObject *mod; - /* The special value is removed from sys.path in Python3_Init(). */ static wchar_t *(argv[2]) = {L"/must>not&exist/foo", NULL}; + PyObject *importlib_machinery; + + if (!(importlib_machinery = PyImport_ImportModule("importlib.machinery"))) + return NULL; + + if (!(path_finder = PyObject_GetAttrString(importlib_machinery, + "PathFinder"))) + { + Py_DECREF(importlib_machinery); + return NULL; + } + + Py_DECREF(importlib_machinery); + + vim_memset(&FinderType, 0, sizeof(FinderObject)); + FinderType.tp_name = "vim.Finder"; + FinderType.tp_basicsize = sizeof(FinderObject); + FinderType.tp_base = (PyTypeObject *) path_finder; + FinderType.tp_flags = Py_TPFLAGS_DEFAULT; + FinderType.tp_doc = "Vim finder class, for use with path hook"; + FinderType.tp_methods = FinderMethods; if (init_types()) return NULL; @@ -1598,14 +1674,16 @@ Py3Init_vim(void) /* Set sys.argv[] to avoid a crash in warn(). */ PySys_SetArgv(1, argv); - mod = PyModule_Create(&vimmodule); - if (mod == NULL) + if ((vim_module = PyModule_Create(&vimmodule)) == NULL) return NULL; - if (populate_module(mod, PyModule_AddObject, PyObject_GetAttrString)) + if (populate_module(vim_module, PyModule_AddObject, PyObject_GetAttrString)) return NULL; - return mod; + if (init_sys_path()) + return NULL; + + return vim_module; } /************************************************************************* diff --git a/src/testdir/test86.in b/src/testdir/test86.in --- a/src/testdir/test86.in +++ b/src/testdir/test86.in @@ -1069,6 +1069,14 @@ ee('vim.current.tabpage = True') ee('vim.current.xxx = True') EOF :" +:" Test import TODO: BROKEN +:"py << EOF +:"vim.options['rtp'] = os.getcwd().replace(',', '\\,').replace('\\', '\\\\') +:"from module import dir as d +:"from modulex import ddir +:"cb.append(d + ',' + ddir) +:"EOF +:" :" Test exceptions :fun Exe(e) : execute a:e diff --git a/src/testdir/test87.in b/src/testdir/test87.in --- a/src/testdir/test87.in +++ b/src/testdir/test87.in @@ -1036,6 +1036,14 @@ ee('vim.current.tabpage = True') ee('vim.current.xxx = True') EOF :" +:" Test import TODO: BROKEN +:"py3 << EOF +:"vim.options['rtp'] = os.getcwd().replace(',', '\\,').replace('\\', '\\\\') +:"from module import dir as d +:"from modulex import ddir +:"cb.append(d + ',' + ddir) +:"EOF +:" :" Test exceptions :fun Exe(e) : execute a:e diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -729,6 +729,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 1163, +/**/ 1162, /**/ 1161,