From edc80a38e76e6cf61a49a4311b0c5c3101835a44 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Mon, 16 May 2016 23:20:44 -0400 Subject: [PATCH 01/14] WIP: loading TCL / Tk symbols dynamically This is an attempt to load the symbols we need from the Tkinter.tkinter module at run time, rather than by linking at build time. It is one way of building a manylinux wheel that can build on a basic Manylinux docker image, and then run with the TCL / Tk library installed on the installing user's machine. It would also make the situation better on OSX, where we have to build against ActiveState TCL for compatibility with Python.org Python, but we would like to allow run-time TCL from e.g. homebrew. I have tested this on Debian Jessie Python 2.7 and 3.5, and on OSX 10.9 with Python 2.7. Questions: * Would y'all consider carrying something like this approach in the matplotlib source, but not enabled by default, to help building binary wheels? * Do you have any better suggestions about how to do this? * My C fu is weak; is there a way of collecting the typedefs I need from the TCL / Tk headers rather than copying them into the _tkagg.cpp source file (typdefs starting around line 52)? * My fu for Python C extension modules is also weak; did I configure exceptions and handle references correctly? --- src/_tkagg.cpp | 159 +++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 141 insertions(+), 18 deletions(-) diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index a193410a4514..6f5716e54225 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -44,6 +44,38 @@ typedef struct Tcl_Interp *interp; } TkappObject; +#define DYNAMIC_TKINTER + +#ifdef DYNAMIC_TKINTER +// Load TCL / Tk symbols from tkinter extension module at run-time. +// Typedefs, global vars for TCL / Tk library functions. +typedef Tcl_Command (*tcl_cc)(Tcl_Interp *interp, + const char *cmdName, Tcl_CmdProc *proc, + ClientData clientData, + Tcl_CmdDeleteProc *deleteProc); +static tcl_cc TCL_CREATE_COMMAND; +typedef void (*tcl_app_res) (Tcl_Interp *interp, ...); +static tcl_app_res TCL_APPEND_RESULT; +typedef Tk_Window (*tk_mw) (Tcl_Interp *interp); +static tk_mw TK_MAIN_WINDOW; +typedef Tk_PhotoHandle (*tk_fp) (Tcl_Interp *interp, const char *imageName); +static tk_fp TK_FIND_PHOTO; +typedef void (*tk_ppb_nc) (Tk_PhotoHandle handle, + Tk_PhotoImageBlock *blockPtr, int x, int y, + int width, int height); +static tk_ppb_nc TK_PHOTO_PUTBLOCK; +typedef void (*tk_pb) (Tk_PhotoHandle handle); +static tk_pb TK_PHOTO_BLANK; +#else +// Build-time linking against system TCL / Tk functions. +#define TCL_CREATE_COMMAND Tcl_CreateCommand +#define TCL_APPEND_RESULT Tcl_AppendResult +#define TK_MAIN_WINDOW Tk_MainWindow +#define TK_FIND_PHOTO Tk_FindPhoto +#define TK_PHOTO_PUTBLOCK Tk_PhotoPutBlock +#define TK_PHOTO_BLANK Tk_PhotoBlank +#endif + static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, char **argv) { Tk_PhotoHandle photo; @@ -61,25 +93,25 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, long mode; long nval; - if (Tk_MainWindow(interp) == NULL) { + if (TK_MAIN_WINDOW(interp) == NULL) { // Will throw a _tkinter.TclError with "this isn't a Tk application" return TCL_ERROR; } if (argc != 5) { - Tcl_AppendResult(interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL); + TCL_APPEND_RESULT(interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL); return TCL_ERROR; } /* get Tcl PhotoImage handle */ - photo = Tk_FindPhoto(interp, argv[1]); + photo = TK_FIND_PHOTO(interp, argv[1]); if (photo == NULL) { - Tcl_AppendResult(interp, "destination photo must exist", (char *)NULL); + TCL_APPEND_RESULT(interp, "destination photo must exist", (char *)NULL); return TCL_ERROR; } /* get array (or object that can be converted to array) pointer */ if (sscanf(argv[2], SIZE_T_FORMAT, &aggl) != 1) { - Tcl_AppendResult(interp, "error casting pointer", (char *)NULL); + TCL_APPEND_RESULT(interp, "error casting pointer", (char *)NULL); return TCL_ERROR; } bufferobj = (PyObject *)aggl; @@ -88,7 +120,7 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, try { buffer = numpy::array_view(bufferobj); } catch (...) { - Tcl_AppendResult(interp, "buffer is of wrong type", (char *)NULL); + TCL_APPEND_RESULT(interp, "buffer is of wrong type", (char *)NULL); PyErr_Clear(); return TCL_ERROR; } @@ -99,13 +131,13 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, /* get array mode (0=mono, 1=rgb, 2=rgba) */ mode = atol(argv[3]); if ((mode != 0) && (mode != 1) && (mode != 2)) { - Tcl_AppendResult(interp, "illegal image mode", (char *)NULL); + TCL_APPEND_RESULT(interp, "illegal image mode", (char *)NULL); return TCL_ERROR; } /* check for bbox/blitting */ if (sscanf(argv[4], SIZE_T_FORMAT, &bboxl) != 1) { - Tcl_AppendResult(interp, "error casting pointer", (char *)NULL); + TCL_APPEND_RESULT(interp, "error casting pointer", (char *)NULL); return TCL_ERROR; } bboxo = (PyObject *)bboxl; @@ -126,7 +158,7 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, destbuffer = new agg::int8u[deststride * destheight]; if (destbuffer == NULL) { - Tcl_AppendResult(interp, "could not allocate memory", (char *)NULL); + TCL_APPEND_RESULT(interp, "could not allocate memory", (char *)NULL); return TCL_ERROR; } @@ -167,7 +199,7 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, block.pitch = deststride; block.pixelPtr = destbuffer; - Tk_PhotoPutBlock(photo, &block, destx, desty, destwidth, destheight); + TK_PHOTO_PUTBLOCK(photo, &block, destx, desty, destwidth, destheight); delete[] destbuffer; } else { @@ -177,9 +209,9 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, block.pixelPtr = buffer.data(); /* Clear current contents */ - Tk_PhotoBlank(photo); + TK_PHOTO_BLANK(photo); /* Copy opaque block to photo image, and leave the rest to TK */ - Tk_PhotoPutBlock(photo, &block, 0, 0, block.width, block.height); + TK_PHOTO_PUTBLOCK(photo, &block, 0, 0, block.width, block.height); } return TCL_OK; @@ -216,11 +248,11 @@ static PyObject *_tkinit(PyObject *self, PyObject *args) /* This will bomb if interp is invalid... */ - Tcl_CreateCommand(interp, - "PyAggImagePhoto", - (Tcl_CmdProc *)PyAggImagePhoto, - (ClientData)0, - (Tcl_CmdDeleteProc *)NULL); + TCL_CREATE_COMMAND(interp, + "PyAggImagePhoto", + (Tcl_CmdProc *)PyAggImagePhoto, + (ClientData)0, + (Tcl_CmdDeleteProc *)NULL); Py_INCREF(Py_None); return Py_None; @@ -232,6 +264,90 @@ static PyMethodDef functions[] = { { NULL, NULL } /* sentinel */ }; +#ifdef DYNAMIC_TKINTER +// Functions to fill global TCL / Tk function pointers from tkinter module. + +#include + +#if PY3K +#define TKINTER_PKG "tkinter" +#define TKINTER_MOD "_tkinter" +// From module __file__ attribute to char *string for dlopen. +#define FNAME2CHAR(s) (PyBytes_AsString(PyUnicode_EncodeFSDefault(s))) +#else +#define TKINTER_PKG "Tkinter" +#define TKINTER_MOD "tkinter" +// From module __file__ attribute to char *string for dlopen +#define FNAME2CHAR(s) (PyString_AsString(s)) +#endif + +void *_dfunc(void *lib_handle, const char *func_name) +{ + // Load function, unless there has been a previous error. If so, then + // return NULL. If there is an error loading the function, return NULL + // and set error flag. + static int have_error = 0; + void *func = NULL; + if (have_error == 0) { + // reset errors + dlerror(); + func = dlsym(lib_handle, func_name); + const char *error = dlerror(); + if (error != NULL) { + PyErr_SetString(PyExc_RuntimeError, error); + have_error = 1; + } + } + return func; +} + +int _func_loader(void *tkinter_lib) +{ + // Fill global function pointers from dynamic lib. + // Return 0 fur success; 1 otherwise. + TCL_CREATE_COMMAND = (tcl_cc) _dfunc(tkinter_lib, "Tcl_CreateCommand"); + TCL_APPEND_RESULT = (tcl_app_res) _dfunc(tkinter_lib, "Tcl_AppendResult"); + TK_MAIN_WINDOW = (tk_mw) _dfunc(tkinter_lib, "Tk_MainWindow"); + TK_FIND_PHOTO = (tk_fp) _dfunc(tkinter_lib, "Tk_FindPhoto"); + TK_PHOTO_PUTBLOCK = (tk_ppb_nc) _dfunc(tkinter_lib, "Tk_PhotoPutBlock_NoComposite"); + TK_PHOTO_BLANK = (tk_pb) _dfunc(tkinter_lib, "Tk_PhotoBlank"); + return (TK_PHOTO_BLANK == NULL); +} + +int load_tkinter_funcs(void) +{ + // Load tkinter global funcs from tkinter compiled module. + // Return 0 for success, non-zero for failure. + int ret = -1; + PyObject *pModule, *pSubmodule, *pString; + + pModule = PyImport_ImportModule(TKINTER_PKG); + if (pModule != NULL) { + pSubmodule = PyObject_GetAttrString(pModule, TKINTER_MOD); + if (pSubmodule != NULL) { + pString = PyObject_GetAttrString(pSubmodule, "__file__"); + if (pString != NULL) { + char *tkinter_libname = FNAME2CHAR(pString); + void *tkinter_lib = dlopen(tkinter_libname, RTLD_LAZY); + if (tkinter_lib == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "Cannot dlopen tkinter module file"); + } else { + ret = _func_loader(tkinter_lib); + // dlclose probably safe because tkinter has been + // imported. + dlclose(tkinter_lib); + } + Py_DECREF(pString); + } + Py_DECREF(pSubmodule); + } + Py_DECREF(pModule); + } + return ret; +} +#endif + #if PY3K static PyModuleDef _tkagg_module = { PyModuleDef_HEAD_INIT, "_tkagg", "", -1, functions, NULL, NULL, NULL, NULL }; @@ -244,7 +360,11 @@ PyMODINIT_FUNC PyInit__tkagg(void) import_array(); - return m; +#ifdef DYNAMIC_TKINTER + return (load_tkinter_funcs() == 0) ? m : NULL; +#else + return m +#endif } #else PyMODINIT_FUNC init_tkagg(void) @@ -252,5 +372,8 @@ PyMODINIT_FUNC init_tkagg(void) import_array(); Py_InitModule("_tkagg", functions); +#ifdef DYNAMIC_TKINTER + load_tkinter_funcs(); +#endif } #endif From 64f01dd21dbefe1bc1d82fe4c7dc4e638efbbb7b Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 17 May 2016 19:54:37 -0400 Subject: [PATCH 02/14] RF: refactor C code to Michael D's commments Refactoring for style. Check Python 3 filename encoding result. --- src/_tkagg.cpp | 109 ++++++++++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 46 deletions(-) diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index 6f5716e54225..ce738e524966 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -273,45 +273,54 @@ static PyMethodDef functions[] = { #define TKINTER_PKG "tkinter" #define TKINTER_MOD "_tkinter" // From module __file__ attribute to char *string for dlopen. -#define FNAME2CHAR(s) (PyBytes_AsString(PyUnicode_EncodeFSDefault(s))) +char *fname2char(PyObject *fname) +{ + PyObject *bytes = PyUnicode_EncodeFSDefault(fname); + if (bytes == NULL) { + return NULL; + } + return PyBytes_AsString(bytes); +} #else #define TKINTER_PKG "Tkinter" #define TKINTER_MOD "tkinter" // From module __file__ attribute to char *string for dlopen -#define FNAME2CHAR(s) (PyString_AsString(s)) +#define fname2char(s) (PyString_AsString(s)) #endif void *_dfunc(void *lib_handle, const char *func_name) { - // Load function, unless there has been a previous error. If so, then - // return NULL. If there is an error loading the function, return NULL - // and set error flag. - static int have_error = 0; - void *func = NULL; - if (have_error == 0) { - // reset errors - dlerror(); - func = dlsym(lib_handle, func_name); + // Load function `func_name` from `lib_handle`. + // Set Python exception if we can't find `func_name` in `lib_handle`. + // Returns function pointer or NULL if not present. + + // Reset errors. + dlerror(); + void *func = dlsym(lib_handle, func_name); + if (func == NULL) { const char *error = dlerror(); - if (error != NULL) { - PyErr_SetString(PyExc_RuntimeError, error); - have_error = 1; - } + PyErr_SetString(PyExc_RuntimeError, error); } return func; } -int _func_loader(void *tkinter_lib) +int _func_loader(void *lib) { // Fill global function pointers from dynamic lib. - // Return 0 fur success; 1 otherwise. - TCL_CREATE_COMMAND = (tcl_cc) _dfunc(tkinter_lib, "Tcl_CreateCommand"); - TCL_APPEND_RESULT = (tcl_app_res) _dfunc(tkinter_lib, "Tcl_AppendResult"); - TK_MAIN_WINDOW = (tk_mw) _dfunc(tkinter_lib, "Tk_MainWindow"); - TK_FIND_PHOTO = (tk_fp) _dfunc(tkinter_lib, "Tk_FindPhoto"); - TK_PHOTO_PUTBLOCK = (tk_ppb_nc) _dfunc(tkinter_lib, "Tk_PhotoPutBlock_NoComposite"); - TK_PHOTO_BLANK = (tk_pb) _dfunc(tkinter_lib, "Tk_PhotoBlank"); - return (TK_PHOTO_BLANK == NULL); + // Return 1 if any pointer is NULL, 0 otherwise. + return ( + ((TCL_CREATE_COMMAND = (tcl_cc) + _dfunc(lib, "Tcl_CreateCommand")) == NULL) || + ((TCL_APPEND_RESULT = (tcl_app_res) + _dfunc(lib, "Tcl_AppendResult")) == NULL) || + ((TK_MAIN_WINDOW = (tk_mw) + _dfunc(lib, "Tk_MainWindow")) == NULL) || + ((TK_FIND_PHOTO = (tk_fp) + _dfunc(lib, "Tk_FindPhoto")) == NULL) || + ((TK_PHOTO_PUTBLOCK = (tk_ppb_nc) + _dfunc(lib, "Tk_PhotoPutBlock_NoComposite")) == NULL) || + ((TK_PHOTO_BLANK = (tk_pb) + _dfunc(lib, "Tk_PhotoBlank")) == NULL)); } int load_tkinter_funcs(void) @@ -319,31 +328,39 @@ int load_tkinter_funcs(void) // Load tkinter global funcs from tkinter compiled module. // Return 0 for success, non-zero for failure. int ret = -1; - PyObject *pModule, *pSubmodule, *pString; + void *tkinter_lib; + char *tkinter_libname; + PyObject *pModule = NULL, *pSubmodule = NULL, *pString = NULL; pModule = PyImport_ImportModule(TKINTER_PKG); - if (pModule != NULL) { - pSubmodule = PyObject_GetAttrString(pModule, TKINTER_MOD); - if (pSubmodule != NULL) { - pString = PyObject_GetAttrString(pSubmodule, "__file__"); - if (pString != NULL) { - char *tkinter_libname = FNAME2CHAR(pString); - void *tkinter_lib = dlopen(tkinter_libname, RTLD_LAZY); - if (tkinter_lib == NULL) { - PyErr_SetString(PyExc_RuntimeError, - "Cannot dlopen tkinter module file"); - } else { - ret = _func_loader(tkinter_lib); - // dlclose probably safe because tkinter has been - // imported. - dlclose(tkinter_lib); - } - Py_DECREF(pString); - } - Py_DECREF(pSubmodule); - } - Py_DECREF(pModule); + if (pModule == NULL) { + goto exit; + } + pSubmodule = PyObject_GetAttrString(pModule, TKINTER_MOD); + if (pSubmodule == NULL) { + goto exit; + } + pString = PyObject_GetAttrString(pSubmodule, "__file__"); + if (pString == NULL) { + goto exit; + } + tkinter_libname = fname2char(pString); + if (tkinter_libname == NULL) { + goto exit; + } + tkinter_lib = dlopen(tkinter_libname, RTLD_LAZY); + if (tkinter_lib == NULL) { + PyErr_SetString(PyExc_RuntimeError, + "Cannot dlopen tkinter module file"); + goto exit; } + ret = _func_loader(tkinter_lib); + // dlclose probably safe because tkinter has been imported. + dlclose(tkinter_lib); +exit: + Py_XDECREF(pModule); + Py_XDECREF(pSubmodule); + Py_XDECREF(pString); return ret; } #endif From 89535be9cebc41341b8434b578d2f85a989fbc0a Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Wed, 18 May 2016 11:43:58 -0400 Subject: [PATCH 03/14] NF: add Windows support for TCL dynamic loading Try adding defines etc for using LoadLibrary on Windows to get the TCL / Tk routines. --- src/_tkagg.cpp | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index ce738e524966..9bdb5d5172da 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -267,8 +267,6 @@ static PyMethodDef functions[] = { #ifdef DYNAMIC_TKINTER // Functions to fill global TCL / Tk function pointers from tkinter module. -#include - #if PY3K #define TKINTER_PKG "tkinter" #define TKINTER_MOD "_tkinter" @@ -288,6 +286,31 @@ char *fname2char(PyObject *fname) #define fname2char(s) (PyString_AsString(s)) #endif +#if defined(_MSC_VER) +#include +#define LIB_PTR_TYPE HMODULE +#define LOAD_LIB(name) LoadLibrary(name) +#define CLOSE_LIB(name) FreeLibrary(name) +FARPROC _dfunc(LIB_PTR_TYPE lib_handle, const char *func_name) +{ + // Load function `func_name` from `lib_handle`. + // Set Python exception if we can't find `func_name` in `lib_handle`. + // Returns function pointer or NULL if not present. + + char message[100]; + + FARPROC func = GetProcAddress(lib_handle, func_name); + if (func == NULL) { + sprintf(message, "Cannot load function %s", func_name); + PyErr_SetString(PyExc_RuntimeError, message); + } + return func; +} +#else +#include +#define LIB_PTR_TYPE void* +#define LOAD_LIB(name) dlopen(name, RTLD_LAZY) +#define CLOSE_LIB(name) dlclose(name) void *_dfunc(void *lib_handle, const char *func_name) { // Load function `func_name` from `lib_handle`. @@ -303,8 +326,9 @@ void *_dfunc(void *lib_handle, const char *func_name) } return func; } +#endif -int _func_loader(void *lib) +int _func_loader(LIB_PTR_TYPE lib) { // Fill global function pointers from dynamic lib. // Return 1 if any pointer is NULL, 0 otherwise. @@ -328,7 +352,7 @@ int load_tkinter_funcs(void) // Load tkinter global funcs from tkinter compiled module. // Return 0 for success, non-zero for failure. int ret = -1; - void *tkinter_lib; + LIB_PTR_TYPE tkinter_lib; char *tkinter_libname; PyObject *pModule = NULL, *pSubmodule = NULL, *pString = NULL; @@ -348,7 +372,7 @@ int load_tkinter_funcs(void) if (tkinter_libname == NULL) { goto exit; } - tkinter_lib = dlopen(tkinter_libname, RTLD_LAZY); + tkinter_lib = LOAD_LIB(tkinter_libname); if (tkinter_lib == NULL) { PyErr_SetString(PyExc_RuntimeError, "Cannot dlopen tkinter module file"); @@ -356,7 +380,7 @@ int load_tkinter_funcs(void) } ret = _func_loader(tkinter_lib); // dlclose probably safe because tkinter has been imported. - dlclose(tkinter_lib); + CLOSE_LIB(tkinter_lib); exit: Py_XDECREF(pModule); Py_XDECREF(pSubmodule); From fc5d0604e5313e772e77ee217de87120147f0b31 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Wed, 18 May 2016 14:19:28 -0400 Subject: [PATCH 04/14] RF: make dynamic loading of TCL the one true way Remove ifdefs that allowed not-dynamic library resolution of TCL / Tk symbols. --- src/_tkagg.cpp | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index 9bdb5d5172da..428cdf5f3efe 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -44,9 +44,6 @@ typedef struct Tcl_Interp *interp; } TkappObject; -#define DYNAMIC_TKINTER - -#ifdef DYNAMIC_TKINTER // Load TCL / Tk symbols from tkinter extension module at run-time. // Typedefs, global vars for TCL / Tk library functions. typedef Tcl_Command (*tcl_cc)(Tcl_Interp *interp, @@ -66,15 +63,6 @@ typedef void (*tk_ppb_nc) (Tk_PhotoHandle handle, static tk_ppb_nc TK_PHOTO_PUTBLOCK; typedef void (*tk_pb) (Tk_PhotoHandle handle); static tk_pb TK_PHOTO_BLANK; -#else -// Build-time linking against system TCL / Tk functions. -#define TCL_CREATE_COMMAND Tcl_CreateCommand -#define TCL_APPEND_RESULT Tcl_AppendResult -#define TK_MAIN_WINDOW Tk_MainWindow -#define TK_FIND_PHOTO Tk_FindPhoto -#define TK_PHOTO_PUTBLOCK Tk_PhotoPutBlock -#define TK_PHOTO_BLANK Tk_PhotoBlank -#endif static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, char **argv) { @@ -264,7 +252,6 @@ static PyMethodDef functions[] = { { NULL, NULL } /* sentinel */ }; -#ifdef DYNAMIC_TKINTER // Functions to fill global TCL / Tk function pointers from tkinter module. #if PY3K @@ -387,7 +374,6 @@ int load_tkinter_funcs(void) Py_XDECREF(pString); return ret; } -#endif #if PY3K static PyModuleDef _tkagg_module = { PyModuleDef_HEAD_INIT, "_tkagg", "", -1, functions, @@ -401,11 +387,7 @@ PyMODINIT_FUNC PyInit__tkagg(void) import_array(); -#ifdef DYNAMIC_TKINTER return (load_tkinter_funcs() == 0) ? m : NULL; -#else - return m -#endif } #else PyMODINIT_FUNC init_tkagg(void) @@ -413,8 +395,7 @@ PyMODINIT_FUNC init_tkagg(void) import_array(); Py_InitModule("_tkagg", functions); -#ifdef DYNAMIC_TKINTER + load_tkinter_funcs(); -#endif } #endif From 1c85968581a21e009c05e7a0bc17b8145332c869 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Wed, 18 May 2016 14:34:31 -0400 Subject: [PATCH 05/14] RF: disable build-time link against TCL/Tk Disable link to TCL / Tk libraries now we are loading symbols at run-time. --- setupext.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/setupext.py b/setupext.py index 4adbcb32ed30..bb370e992327 100755 --- a/setupext.py +++ b/setupext.py @@ -1726,17 +1726,12 @@ def add_flags(self, ext): # includes are directly in the conda\library\include dir and # libs in DLL or lib ext.include_dirs.extend(['include']) - ext.libraries.extend(['tk85', 'tcl85']) - ext.library_dirs.extend(['dlls']) # or lib? else: major, minor1, minor2, s, tmp = sys.version_info if sys.version_info[0:2] < (3, 4): ext.include_dirs.extend(['win32_static/include/tcl85']) - ext.libraries.extend(['tk85', 'tcl85']) else: ext.include_dirs.extend(['win32_static/include/tcl86']) - ext.libraries.extend(['tk86t', 'tcl86t']) - ext.library_dirs.extend([os.path.join(sys.prefix, 'dlls')]) elif sys.platform == 'darwin': # this config section lifted directly from Imaging - thanks to @@ -1781,10 +1776,7 @@ def add_flags(self, ext): # not found... # tk_include_dirs.append('/usr/X11R6/include') - frameworks = ['-framework', 'Tcl', '-framework', 'Tk'] ext.include_dirs.extend(tk_include_dirs) - ext.extra_link_args.extend(frameworks) - ext.extra_compile_args.extend(frameworks) # you're still here? ok we'll try it this way... else: @@ -1817,8 +1809,6 @@ def add_flags(self, ext): (tcl_lib_dir, tcl_inc_dir, tcl_lib, tk_lib_dir, tk_inc_dir, tk_lib) = result ext.include_dirs.extend([tcl_inc_dir, tk_inc_dir]) - ext.library_dirs.extend([tcl_lib_dir, tk_lib_dir]) - ext.libraries.extend([tcl_lib, tk_lib]) class BackendGtk(OptionalBackendPackage): From 830d7a36894cc053e081882b721d92d4e4bbd1fd Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Thu, 19 May 2016 15:46:23 -0400 Subject: [PATCH 06/14] RF: find TCL / Tk symbols correctly on Windows As Christoph G found, we can't use the tkinter extension module to find the TCL / Tk symbols on Windows, because Windows DLLs do not return the addresses of symbols they import from GetProcAddress. Instead, iterate through the modules loaded in the current process to find the TCL and Tk symbols. See: * https://msdn.microsoft.com/en-us/library/windows/desktop/ms682621(v=vs.85).aspx * https://msdn.microsoft.com/en-us/library/windows/desktop/ms683179(v=vs.85).aspx --- setupext.py | 2 + src/_tkagg.cpp | 148 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 120 insertions(+), 30 deletions(-) diff --git a/setupext.py b/setupext.py index bb370e992327..2358ad0bd183 100755 --- a/setupext.py +++ b/setupext.py @@ -1732,6 +1732,8 @@ def add_flags(self, ext): ext.include_dirs.extend(['win32_static/include/tcl85']) else: ext.include_dirs.extend(['win32_static/include/tcl86']) + # PSAPI library needed for finding TCL / Tk at run time + ext.libraries.extend(['psapi']) elif sys.platform == 'darwin': # this config section lifted directly from Imaging - thanks to diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index 428cdf5f3efe..e98c1d684637 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -252,8 +252,119 @@ static PyMethodDef functions[] = { { NULL, NULL } /* sentinel */ }; -// Functions to fill global TCL / Tk function pointers from tkinter module. +// Functions to fill global TCL / Tk function pointers by dynamic loading +#if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +/* + * On Windows, we can't load the tkinter module to get the TCL or Tk symbols, + * because Windows does not load symbols into the library name-space of + * importing modules. So, knowing that tkinter has already been imported by + * Python, we scan all modules in the running process for the TCL and Tk + * function names. + */ +#include +#define PSAPI_VERSION 1 +#include +// Must be linked with 'psapi' library + +FARPROC _dfunc(HMODULE lib_handle, const char *func_name) +{ + // Load function `func_name` from `lib_handle`. + // Set Python exception if we can't find `func_name` in `lib_handle`. + // Returns function pointer or NULL if not present. + + char message[100]; + + FARPROC func = GetProcAddress(lib_handle, func_name); + if (func == NULL) { + sprintf(message, "Cannot load function %s", func_name); + PyErr_SetString(PyExc_RuntimeError, message); + } + return func; +} + +int get_tcl(HMODULE hMod) +{ + // Try to fill TCL global vars with function pointers. Return 0 for no + // functions found, 1 for all functions found, -1 for some but not all + // functions found. + TCL_CREATE_COMMAND = (tcl_cc) GetProcAddress(hMod, "Tcl_CreateCommand"); + if (TCL_CREATE_COMMAND == NULL) { // Maybe not TCL module + return 0; + } + TCL_APPEND_RESULT = (tcl_app_res) _dfunc(hMod, "Tcl_AppendResult"); + return (TCL_APPEND_RESULT == NULL) ? -1 : 1; +} + +int get_tk(HMODULE hMod) +{ + // Try to fill Tk global vars with function pointers. Return 0 for no + // functions found, 1 for all functions found, -1 for some but not all + // functions found. + TK_MAIN_WINDOW = (tk_mw) GetProcAddress(hMod, "Tk_MainWindow"); + if (TK_MAIN_WINDOW == NULL) { // Maybe not Tk module + return 0; + } + TK_FIND_PHOTO = (tk_fp) _dfunc(hMod, "Tk_FindPhoto"); + TK_PHOTO_PUTBLOCK = (tk_ppb_nc) _dfunc(hMod, + "Tk_PhotoPutBlock_NoComposite"); + TK_PHOTO_BLANK = (tk_pb) _dfunc(hMod, "Tk_PhotoBlank"); + return ((TK_FIND_PHOTO == NULL) || + (TK_PHOTO_PUTBLOCK == NULL) || + (TK_PHOTO_BLANK == NULL)) ? -1 : 1; +} + +int load_tkinter_funcs(void) +{ + // Load TCL and Tk functions by searching all modules in current process. + // Return 0 for success, non-zero for failure. + + HMODULE hMods[1024]; + HANDLE hProcess; + DWORD cbNeeded; + unsigned int i; + int found_tcl = 0; + int found_tk = 0; + + // Returns pseudo-handle that does not need to be closed + hProcess = GetCurrentProcess(); + + // Iterate through modules in this process looking for TCL / Tk names + if (EnumProcessModules(hProcess, hMods, sizeof(hMods), &cbNeeded)) { + for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) { + if (!found_tcl) { + found_tcl = get_tcl(hMods[i]); + if (found_tcl == -1) { + return 1; + } + } + if (!found_tk) { + found_tk = get_tk(hMods[i]); + if (found_tk == -1) { + return 1; + } + } + if (found_tcl && found_tk) { + return 0; + } + } + } + + if (found_tcl == 0) { + PyErr_SetString(PyExc_RuntimeError, "Could not find TCL routines"); + } else { + PyErr_SetString(PyExc_RuntimeError, "Could not find Tk routines"); + } + return 1; +} + +#else // not Windows + +/* + * On Unix, we can get the TCL and Tk synbols from the tkinter module, because + * tkinter uses these symbols, and the symbols are therefore visible in the + * tkinter dynamic library (module). + */ #if PY3K #define TKINTER_PKG "tkinter" #define TKINTER_MOD "_tkinter" @@ -273,31 +384,8 @@ char *fname2char(PyObject *fname) #define fname2char(s) (PyString_AsString(s)) #endif -#if defined(_MSC_VER) -#include -#define LIB_PTR_TYPE HMODULE -#define LOAD_LIB(name) LoadLibrary(name) -#define CLOSE_LIB(name) FreeLibrary(name) -FARPROC _dfunc(LIB_PTR_TYPE lib_handle, const char *func_name) -{ - // Load function `func_name` from `lib_handle`. - // Set Python exception if we can't find `func_name` in `lib_handle`. - // Returns function pointer or NULL if not present. - - char message[100]; - - FARPROC func = GetProcAddress(lib_handle, func_name); - if (func == NULL) { - sprintf(message, "Cannot load function %s", func_name); - PyErr_SetString(PyExc_RuntimeError, message); - } - return func; -} -#else #include -#define LIB_PTR_TYPE void* -#define LOAD_LIB(name) dlopen(name, RTLD_LAZY) -#define CLOSE_LIB(name) dlclose(name) + void *_dfunc(void *lib_handle, const char *func_name) { // Load function `func_name` from `lib_handle`. @@ -313,9 +401,8 @@ void *_dfunc(void *lib_handle, const char *func_name) } return func; } -#endif -int _func_loader(LIB_PTR_TYPE lib) +int _func_loader(void *lib) { // Fill global function pointers from dynamic lib. // Return 1 if any pointer is NULL, 0 otherwise. @@ -339,7 +426,7 @@ int load_tkinter_funcs(void) // Load tkinter global funcs from tkinter compiled module. // Return 0 for success, non-zero for failure. int ret = -1; - LIB_PTR_TYPE tkinter_lib; + void *tkinter_lib; char *tkinter_libname; PyObject *pModule = NULL, *pSubmodule = NULL, *pString = NULL; @@ -359,7 +446,7 @@ int load_tkinter_funcs(void) if (tkinter_libname == NULL) { goto exit; } - tkinter_lib = LOAD_LIB(tkinter_libname); + tkinter_lib = dlopen(tkinter_libname, RTLD_LAZY); if (tkinter_lib == NULL) { PyErr_SetString(PyExc_RuntimeError, "Cannot dlopen tkinter module file"); @@ -367,13 +454,14 @@ int load_tkinter_funcs(void) } ret = _func_loader(tkinter_lib); // dlclose probably safe because tkinter has been imported. - CLOSE_LIB(tkinter_lib); + dlclose(tkinter_lib); exit: Py_XDECREF(pModule); Py_XDECREF(pSubmodule); Py_XDECREF(pString); return ret; } +#endif // end not Windows #if PY3K static PyModuleDef _tkagg_module = { PyModuleDef_HEAD_INIT, "_tkagg", "", -1, functions, From 4c5a3ab85440a669d265e7ee8f4a5b5d3952fc7f Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Thu, 19 May 2016 21:22:00 -0400 Subject: [PATCH 07/14] RF: short-circuit the Tk function tests Stop testing for Tk routines when one is missing. --- src/_tkagg.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index e98c1d684637..fe74405af679 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -305,13 +305,12 @@ int get_tk(HMODULE hMod) if (TK_MAIN_WINDOW == NULL) { // Maybe not Tk module return 0; } - TK_FIND_PHOTO = (tk_fp) _dfunc(hMod, "Tk_FindPhoto"); - TK_PHOTO_PUTBLOCK = (tk_ppb_nc) _dfunc(hMod, - "Tk_PhotoPutBlock_NoComposite"); - TK_PHOTO_BLANK = (tk_pb) _dfunc(hMod, "Tk_PhotoBlank"); - return ((TK_FIND_PHOTO == NULL) || - (TK_PHOTO_PUTBLOCK == NULL) || - (TK_PHOTO_BLANK == NULL)) ? -1 : 1; + return ( // -1 if any are NULL + ((TK_FIND_PHOTO = (tk_fp) _dfunc(hMod, "Tk_FindPhoto")) == NULL) || + ((TK_PHOTO_PUTBLOCK = (tk_ppb_nc) + _dfunc(hMod, "Tk_PhotoPutBlock_NoComposite")) == NULL) || + ((TK_PHOTO_BLANK = (tk_pb) _dfunc(hMod, "Tk_PhotoBlank")) == NULL)) + ? -1 : 1; } int load_tkinter_funcs(void) From 94bb4577e1ca15a567ca27601907c09a558f1a41 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Fri, 20 May 2016 14:34:39 -0400 Subject: [PATCH 08/14] WIP: Add excerpts from TCL / Tk header Add parts of TCL / Tk headers needed to compile. If this header is enough, and correct across platforms, then we should be able to remove the complicated TCL / Tk search algorithms at build time. --- src/_tkagg.cpp | 11 +----- src/_tkmini.h | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 src/_tkmini.h diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index fe74405af679..209fffb9ec3a 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -20,16 +20,7 @@ extern "C" { -#ifdef __APPLE__ -# ifdef TK_FRAMEWORK -# include -# include -# else -# include -# endif -#else -# include -#endif +#include "_tkmini.h" } #if defined(_MSC_VER) diff --git a/src/_tkmini.h b/src/_tkmini.h new file mode 100644 index 000000000000..d314143118c6 --- /dev/null +++ b/src/_tkmini.h @@ -0,0 +1,91 @@ +/* Small excerpts from the Tcl / Tk 8.6 headers + * + * License terms copied from: + * http://www.tcl.tk/software/tcltk/license.html + * as of 20 May 2016. + * + * Copyright (c) 1987-1994 The Regents of the University of California. + * Copyright (c) 1993-1996 Lucent Technologies. + * Copyright (c) 1994-1998 Sun Microsystems, Inc. + * Copyright (c) 1998-2000 by Scriptics Corporation. + * Copyright (c) 2002 by Kevin B. Kenny. All rights reserved. + * + * This software is copyrighted by the Regents of the University + * of California, Sun Microsystems, Inc., Scriptics Corporation, + * and other parties. The following terms apply to all files + * associated with the software unless explicitly disclaimed in + * individual files. + * + * The authors hereby grant permission to use, copy, modify, + * distribute, and license this software and its documentation + * for any purpose, provided that existing copyright notices are + * retained in all copies and that this notice is included + * verbatim in any distributions. No written agreement, license, + * or royalty fee is required for any of the authorized uses. + * Modifications to this software may be copyrighted by their + * authors and need not follow the licensing terms described + * here, provided that the new terms are clearly indicated on + * the first page of each file where they apply. + * + * IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO + * ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR + * CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS + * SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF, EVEN + * IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE IS PROVIDED ON + * AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO + * OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, + * ENHANCEMENTS, OR MODIFICATIONS. + * + * GOVERNMENT USE: If you are acquiring this software on behalf + * of the U.S. government, the Government shall have only + * "Restricted Rights" in the software and related documentation + * as defined in the Federal Acquisition Regulations (FARs) in + * Clause 52.227.19 (c) (2). If you are acquiring the software + * on behalf of the Department of Defense, the software shall be + * classified as "Commercial Computer Software" and the + * Government shall have only "Restricted Rights" as defined in + * Clause 252.227-7013 (c) (1) of DFARs. Notwithstanding the + * foregoing, the authors grant the U.S. Government and others + * acting in its behalf permission to use and distribute the + * software in accordance with the terms specified in this + * license + */ + +/* Tcl header excerpts */ +#define TCL_OK 0 +#define TCL_ERROR 1 + +typedef struct Tcl_Interp +{ + char *result; + void (*freeProc) (char *); + int errorLine; +} Tcl_Interp; + +typedef struct Tcl_Command_ *Tcl_Command; +typedef void *ClientData; + +typedef int (Tcl_CmdProc) (ClientData clientData, Tcl_Interp + *interp, int argc, const char *argv[]); +typedef void (Tcl_CmdDeleteProc) (ClientData clientData); + +/* Tk header excerpts */ +typedef struct Tk_Window_ *Tk_Window; + +typedef void *Tk_PhotoHandle; + +typedef struct Tk_PhotoImageBlock +{ + unsigned char *pixelPtr; + int width; + int height; + int pitch; + int pixelSize; + int offset[4]; +} Tk_PhotoImageBlock; From 3f5407a79b77eb9c66e971bb08b83c095bc3da5b Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Sat, 21 May 2016 13:44:36 -0400 Subject: [PATCH 09/14] RF: check, edit, refactor Tcl / tk mini header Move typedefs into Tcl / tk mini header. Rename typedefs for clarity. Strip older definition of Tcl_Interp. Check all defines compatible with Tcl / Tk 8.5 and current trunk (as of 21 May 2016). --- src/_tkagg.cpp | 78 ++++++++++++++++++++++---------------------------- src/_tkmini.h | 49 +++++++++++++++++++++++++++---- 2 files changed, 77 insertions(+), 50 deletions(-) diff --git a/src/_tkagg.cpp b/src/_tkagg.cpp index 209fffb9ec3a..9191992f8bca 100644 --- a/src/_tkagg.cpp +++ b/src/_tkagg.cpp @@ -6,11 +6,6 @@ * */ -/* This is needed for (at least) Tk 8.4.1, otherwise the signature of -** Tk_PhotoPutBlock changes. -*/ -#define USE_COMPOSITELESS_PHOTO_PUT_BLOCK - #include #include #include @@ -18,10 +13,8 @@ #include "py_converters.h" -extern "C" -{ +// Include our own excerpts from the Tcl / Tk headers #include "_tkmini.h" -} #if defined(_MSC_VER) # define SIZE_T_FORMAT "%Iu" @@ -35,27 +28,17 @@ typedef struct Tcl_Interp *interp; } TkappObject; -// Load TCL / Tk symbols from tkinter extension module at run-time. -// Typedefs, global vars for TCL / Tk library functions. -typedef Tcl_Command (*tcl_cc)(Tcl_Interp *interp, - const char *cmdName, Tcl_CmdProc *proc, - ClientData clientData, - Tcl_CmdDeleteProc *deleteProc); -static tcl_cc TCL_CREATE_COMMAND; -typedef void (*tcl_app_res) (Tcl_Interp *interp, ...); -static tcl_app_res TCL_APPEND_RESULT; -typedef Tk_Window (*tk_mw) (Tcl_Interp *interp); -static tk_mw TK_MAIN_WINDOW; -typedef Tk_PhotoHandle (*tk_fp) (Tcl_Interp *interp, const char *imageName); -static tk_fp TK_FIND_PHOTO; -typedef void (*tk_ppb_nc) (Tk_PhotoHandle handle, - Tk_PhotoImageBlock *blockPtr, int x, int y, - int width, int height); -static tk_ppb_nc TK_PHOTO_PUTBLOCK; -typedef void (*tk_pb) (Tk_PhotoHandle handle); -static tk_pb TK_PHOTO_BLANK; - -static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, char **argv) +// Global vars for Tcl / Tk functions. We load these symbols from the tkinter +// extension module or loaded Tcl / Tk libraries at run-time. +static Tcl_CreateCommand_t TCL_CREATE_COMMAND; +static Tcl_AppendResult_t TCL_APPEND_RESULT; +static Tk_MainWindow_t TK_MAIN_WINDOW; +static Tk_FindPhoto_t TK_FIND_PHOTO; +static Tk_PhotoPutBlock_NoComposite_t TK_PHOTO_PUT_BLOCK_NO_COMPOSITE; +static Tk_PhotoBlank_t TK_PHOTO_BLANK; + +static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int + argc, char **argv) { Tk_PhotoHandle photo; Tk_PhotoImageBlock block; @@ -178,7 +161,8 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, block.pitch = deststride; block.pixelPtr = destbuffer; - TK_PHOTO_PUTBLOCK(photo, &block, destx, desty, destwidth, destheight); + TK_PHOTO_PUT_BLOCK_NO_COMPOSITE(photo, &block, destx, desty, + destwidth, destheight); delete[] destbuffer; } else { @@ -190,7 +174,8 @@ static int PyAggImagePhoto(ClientData clientdata, Tcl_Interp *interp, int argc, /* Clear current contents */ TK_PHOTO_BLANK(photo); /* Copy opaque block to photo image, and leave the rest to TK */ - TK_PHOTO_PUTBLOCK(photo, &block, 0, 0, block.width, block.height); + TK_PHOTO_PUT_BLOCK_NO_COMPOSITE(photo, &block, 0, 0, block.width, + block.height); } return TCL_OK; @@ -279,11 +264,13 @@ int get_tcl(HMODULE hMod) // Try to fill TCL global vars with function pointers. Return 0 for no // functions found, 1 for all functions found, -1 for some but not all // functions found. - TCL_CREATE_COMMAND = (tcl_cc) GetProcAddress(hMod, "Tcl_CreateCommand"); + TCL_CREATE_COMMAND = (Tcl_CreateCommand_t) + GetProcAddress(hMod, "Tcl_CreateCommand"); if (TCL_CREATE_COMMAND == NULL) { // Maybe not TCL module return 0; } - TCL_APPEND_RESULT = (tcl_app_res) _dfunc(hMod, "Tcl_AppendResult"); + TCL_APPEND_RESULT = (Tcl_AppendResult_t) _dfunc(hMod, + "Tcl_AppendResult"); return (TCL_APPEND_RESULT == NULL) ? -1 : 1; } @@ -292,15 +279,18 @@ int get_tk(HMODULE hMod) // Try to fill Tk global vars with function pointers. Return 0 for no // functions found, 1 for all functions found, -1 for some but not all // functions found. - TK_MAIN_WINDOW = (tk_mw) GetProcAddress(hMod, "Tk_MainWindow"); + TK_MAIN_WINDOW = (Tk_MainWindow_t) + GetProcAddress(hMod, "Tk_MainWindow"); if (TK_MAIN_WINDOW == NULL) { // Maybe not Tk module return 0; } - return ( // -1 if any are NULL - ((TK_FIND_PHOTO = (tk_fp) _dfunc(hMod, "Tk_FindPhoto")) == NULL) || - ((TK_PHOTO_PUTBLOCK = (tk_ppb_nc) + return ( // -1 if any remaining symbols are NULL + ((TK_FIND_PHOTO = (Tk_FindPhoto_t) + _dfunc(hMod, "Tk_FindPhoto")) == NULL) || + ((TK_PHOTO_PUT_BLOCK_NO_COMPOSITE = (Tk_PhotoPutBlock_NoComposite_t) _dfunc(hMod, "Tk_PhotoPutBlock_NoComposite")) == NULL) || - ((TK_PHOTO_BLANK = (tk_pb) _dfunc(hMod, "Tk_PhotoBlank")) == NULL)) + ((TK_PHOTO_BLANK = (Tk_PhotoBlank_t) + _dfunc(hMod, "Tk_PhotoBlank")) == NULL)) ? -1 : 1; } @@ -397,17 +387,17 @@ int _func_loader(void *lib) // Fill global function pointers from dynamic lib. // Return 1 if any pointer is NULL, 0 otherwise. return ( - ((TCL_CREATE_COMMAND = (tcl_cc) + ((TCL_CREATE_COMMAND = (Tcl_CreateCommand_t) _dfunc(lib, "Tcl_CreateCommand")) == NULL) || - ((TCL_APPEND_RESULT = (tcl_app_res) + ((TCL_APPEND_RESULT = (Tcl_AppendResult_t) _dfunc(lib, "Tcl_AppendResult")) == NULL) || - ((TK_MAIN_WINDOW = (tk_mw) + ((TK_MAIN_WINDOW = (Tk_MainWindow_t) _dfunc(lib, "Tk_MainWindow")) == NULL) || - ((TK_FIND_PHOTO = (tk_fp) + ((TK_FIND_PHOTO = (Tk_FindPhoto_t) _dfunc(lib, "Tk_FindPhoto")) == NULL) || - ((TK_PHOTO_PUTBLOCK = (tk_ppb_nc) + ((TK_PHOTO_PUT_BLOCK_NO_COMPOSITE = (Tk_PhotoPutBlock_NoComposite_t) _dfunc(lib, "Tk_PhotoPutBlock_NoComposite")) == NULL) || - ((TK_PHOTO_BLANK = (tk_pb) + ((TK_PHOTO_BLANK = (Tk_PhotoBlank_t) _dfunc(lib, "Tk_PhotoBlank")) == NULL)); } diff --git a/src/_tkmini.h b/src/_tkmini.h index d314143118c6..9b730b6c8c1f 100644 --- a/src/_tkmini.h +++ b/src/_tkmini.h @@ -57,16 +57,24 @@ * license */ +/* + * Unless otherwise noted, these definitions are stable from Tcl / Tk 8.5 + * through Tck / Tk master as of 21 May 2016 + */ + +#ifdef __cplusplus +extern "C" { +#endif + /* Tcl header excerpts */ #define TCL_OK 0 #define TCL_ERROR 1 -typedef struct Tcl_Interp -{ - char *result; - void (*freeProc) (char *); - int errorLine; -} Tcl_Interp; +/* + * Users of versions of Tcl >= 8.6 encouraged to tread Tcl_Interp as an opaque + * pointer. The following definition results when TCL_NO_DEPRECATED defined. + */ +typedef struct Tcl_Interp Tcl_Interp; typedef struct Tcl_Command_ *Tcl_Command; typedef void *ClientData; @@ -75,6 +83,15 @@ typedef int (Tcl_CmdProc) (ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[]); typedef void (Tcl_CmdDeleteProc) (ClientData clientData); +/* Typedefs derived from function signatures in Tcl header */ +/* Tcl_CreateCommand */ +typedef Tcl_Command (*Tcl_CreateCommand_t)(Tcl_Interp *interp, + const char *cmdName, Tcl_CmdProc *proc, + ClientData clientData, + Tcl_CmdDeleteProc *deleteProc); +/* Tcl_AppendResult */ +typedef void (*Tcl_AppendResult_t) (Tcl_Interp *interp, ...); + /* Tk header excerpts */ typedef struct Tk_Window_ *Tk_Window; @@ -89,3 +106,23 @@ typedef struct Tk_PhotoImageBlock int pixelSize; int offset[4]; } Tk_PhotoImageBlock; + +/* Typedefs derived from function signatures in Tk header */ +/* Tk_MainWindow */ +typedef Tk_Window (*Tk_MainWindow_t) (Tcl_Interp *interp); +typedef Tk_PhotoHandle (*Tk_FindPhoto_t) (Tcl_Interp *interp, const char + *imageName); +/* Tk_PhotoPutBLock_NoComposite typedef */ +typedef void (*Tk_PhotoPutBlock_NoComposite_t) (Tk_PhotoHandle handle, + Tk_PhotoImageBlock *blockPtr, int x, int y, + int width, int height); +/* Tk_PhotoBlank */ +typedef void (*Tk_PhotoBlank_t) (Tk_PhotoHandle handle); + +/* + * end block for C++ + */ + +#ifdef __cplusplus +} +#endif From 573aeb5dbf71edae8a45b585930446232fcfffe5 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Sat, 21 May 2016 19:39:42 -0400 Subject: [PATCH 10/14] RF: remove Tcl / Tk header / lib discovery We now don't need the Tcl / Tk headers or libraries to build. --- setupext.py | 250 +--------------------------------------------------- 1 file changed, 2 insertions(+), 248 deletions(-) diff --git a/setupext.py b/setupext.py index 2358ad0bd183..0d4ee13cf495 100755 --- a/setupext.py +++ b/setupext.py @@ -1524,9 +1524,6 @@ def get_extension(self): class BackendTkAgg(OptionalBackendPackage): name = "tkagg" - def __init__(self): - self.tcl_tk_cache = None - def check_requirements(self): try: if PY3min: @@ -1541,9 +1538,6 @@ def check_requirements(self): if Tkinter.TkVersion < 8.3: raise CheckFailed("Tcl/Tk v8.3 or later required.") - ext = self.get_extension() - check_include_file(ext.include_dirs, "tk.h", "Tk") - try: tk_v = Tkinter.__version__.split()[-2] except (AttributeError, IndexError): @@ -1566,252 +1560,12 @@ def get_extension(self): LibAgg().add_flags(ext, add_sources=False) return ext - def query_tcltk(self): - """ - Tries to open a Tk window in order to query the Tk object - about its library paths. This should never be called more - than once by the same process, as Tk intricacies may cause the - Python interpreter to hang. The function also has a workaround - if no X server is running (useful for autobuild systems). - """ - # Use cached values if they exist, which ensures this function - # only executes once - if self.tcl_tk_cache is not None: - return self.tcl_tk_cache - - # By this point, we already know that Tkinter imports correctly - if PY3min: - import tkinter as Tkinter - else: - import Tkinter - tcl_lib_dir = '' - tk_lib_dir = '' - # First try to open a Tk window (requires a running X server) - try: - tk = Tkinter.Tk() - except Tkinter.TclError: - # Next, start Tcl interpreter without opening a Tk window - # (no need for X server) This feature is available in - # python version 2.4 and up - try: - tcl = Tkinter.Tcl() - except AttributeError: # Python version not high enough - pass - except Tkinter.TclError: # Something went wrong while opening Tcl - pass - else: - tcl_lib_dir = str(tcl.getvar('tcl_library')) - # Guess Tk location based on Tcl location - (head, tail) = os.path.split(tcl_lib_dir) - tail = tail.replace('Tcl', 'Tk').replace('tcl', 'tk') - tk_lib_dir = os.path.join(head, tail) - if not os.path.exists(tk_lib_dir): - tk_lib_dir = tcl_lib_dir.replace( - 'Tcl', 'Tk').replace('tcl', 'tk') - else: - # Obtain Tcl and Tk locations from Tk widget - tk.withdraw() - tcl_lib_dir = str(tk.getvar('tcl_library')) - tk_lib_dir = str(tk.getvar('tk_library')) - tk.destroy() - - # Save directories and version string to cache - self.tcl_tk_cache = tcl_lib_dir, tk_lib_dir, str(Tkinter.TkVersion)[:3] - return self.tcl_tk_cache - - def parse_tcl_config(self, tcl_lib_dir, tk_lib_dir): - try: - if PY3min: - import tkinter as Tkinter - else: - import Tkinter - except ImportError: - return None - - tcl_poss = [tcl_lib_dir, - os.path.normpath(os.path.join(tcl_lib_dir, '..')), - "/usr/lib/tcl" + str(Tkinter.TclVersion), - "/usr/lib"] - tk_poss = [tk_lib_dir, - os.path.normpath(os.path.join(tk_lib_dir, '..')), - "/usr/lib/tk" + str(Tkinter.TkVersion), - "/usr/lib"] - for ptcl, ptk in zip(tcl_poss, tk_poss): - tcl_config = os.path.join(ptcl, "tclConfig.sh") - tk_config = os.path.join(ptk, "tkConfig.sh") - if (os.path.exists(tcl_config) and os.path.exists(tk_config)): - break - if not (os.path.exists(tcl_config) and os.path.exists(tk_config)): - return None - - def get_var(file, varname): - p = subprocess.Popen( - '. %s ; eval echo ${%s}' % (file, varname), - shell=True, - executable="/bin/sh", - stdout=subprocess.PIPE) - result = p.communicate()[0] - return result.decode('ascii') - - tcl_lib_dir = get_var( - tcl_config, 'TCL_LIB_SPEC').split()[0][2:].strip() - tcl_inc_dir = get_var( - tcl_config, 'TCL_INCLUDE_SPEC')[2:].strip() - tcl_lib = get_var(tcl_config, 'TCL_LIB_FLAG')[2:].strip() - - tk_lib_dir = get_var(tk_config, 'TK_LIB_SPEC').split()[0][2:].strip() - tk_inc_dir = get_var(tk_config, 'TK_INCLUDE_SPEC').strip() - if tk_inc_dir == '': - tk_inc_dir = tcl_inc_dir - else: - tk_inc_dir = tk_inc_dir[2:] - tk_lib = get_var(tk_config, 'TK_LIB_FLAG')[2:].strip() - - if not os.path.exists(os.path.join(tk_inc_dir, 'tk.h')): - return None - - return (tcl_lib_dir, tcl_inc_dir, tcl_lib, - tk_lib_dir, tk_inc_dir, tk_lib) - - def guess_tcl_config(self, tcl_lib_dir, tk_lib_dir, tk_ver): - if not (os.path.exists(tcl_lib_dir) and os.path.exists(tk_lib_dir)): - return None - - tcl_lib = os.path.normpath(os.path.join(tcl_lib_dir, '../')) - tk_lib = os.path.normpath(os.path.join(tk_lib_dir, '../')) - - tcl_inc = os.path.normpath( - os.path.join(tcl_lib_dir, - '../../include/tcl' + tk_ver)) - if not os.path.exists(tcl_inc): - tcl_inc = os.path.normpath( - os.path.join(tcl_lib_dir, - '../../include')) - - tk_inc = os.path.normpath(os.path.join( - tk_lib_dir, - '../../include/tk' + tk_ver)) - if not os.path.exists(tk_inc): - tk_inc = os.path.normpath(os.path.join( - tk_lib_dir, - '../../include')) - - if not os.path.exists(os.path.join(tk_inc, 'tk.h')): - tk_inc = tcl_inc - - if not os.path.exists(tcl_inc): - # this is a hack for suse linux, which is broken - if (sys.platform.startswith('linux') and - os.path.exists('/usr/include/tcl.h') and - os.path.exists('/usr/include/tk.h')): - tcl_inc = '/usr/include' - tk_inc = '/usr/include' - - if not os.path.exists(os.path.join(tk_inc, 'tk.h')): - return None - - return tcl_lib, tcl_inc, 'tcl' + tk_ver, tk_lib, tk_inc, 'tk' + tk_ver - - def hardcoded_tcl_config(self): - tcl_inc = "/usr/local/include" - tk_inc = "/usr/local/include" - tcl_lib = "/usr/local/lib" - tk_lib = "/usr/local/lib" - return tcl_lib, tcl_inc, 'tcl', tk_lib, tk_inc, 'tk' - def add_flags(self, ext): + ext.include_dirs.extend(['src']) if sys.platform == 'win32': - if os.getenv('CONDA_DEFAULT_ENV'): - # We are in conda and conda builds against tcl85 for all versions - # includes are directly in the conda\library\include dir and - # libs in DLL or lib - ext.include_dirs.extend(['include']) - else: - major, minor1, minor2, s, tmp = sys.version_info - if sys.version_info[0:2] < (3, 4): - ext.include_dirs.extend(['win32_static/include/tcl85']) - else: - ext.include_dirs.extend(['win32_static/include/tcl86']) - # PSAPI library needed for finding TCL / Tk at run time + # PSAPI library needed for finding Tcl / Tk at run time ext.libraries.extend(['psapi']) - elif sys.platform == 'darwin': - # this config section lifted directly from Imaging - thanks to - # the effbot! - - # First test for a MacOSX/darwin framework install - from os.path import join, exists - framework_dirs = [ - join(os.getenv('HOME'), '/Library/Frameworks'), - '/Library/Frameworks', - '/System/Library/Frameworks/', - ] - - # Find the directory that contains the Tcl.framework and - # Tk.framework bundles. - tk_framework_found = 0 - for F in framework_dirs: - # both Tcl.framework and Tk.framework should be present - for fw in 'Tcl', 'Tk': - if not exists(join(F, fw + '.framework')): - break - else: - # ok, F is now directory with both frameworks. Continure - # building - tk_framework_found = 1 - break - if tk_framework_found: - # For 8.4a2, we must add -I options that point inside - # the Tcl and Tk frameworks. In later release we - # should hopefully be able to pass the -F option to - # gcc, which specifies a framework lookup path. - - tk_include_dirs = [ - join(F, fw + '.framework', H) - for fw in ('Tcl', 'Tk') - for H in ('Headers', 'Versions/Current/PrivateHeaders') - ] - - # For 8.4a2, the X11 headers are not included. Rather - # than include a complicated search, this is a - # hard-coded path. It could bail out if X11 libs are - # not found... - - # tk_include_dirs.append('/usr/X11R6/include') - ext.include_dirs.extend(tk_include_dirs) - - # you're still here? ok we'll try it this way... - else: - # There are 3 methods to try, in decreasing order of "smartness" - # - # 1. Parse the tclConfig.sh and tkConfig.sh files that have - # all the information we need - # - # 2. Guess the include and lib dirs based on the location of - # Tkinter's 'tcl_library' and 'tk_library' variables. - # - # 3. Use some hardcoded locations that seem to work on a lot - # of distros. - - # Query Tcl/Tk system for library paths and version string - try: - tcl_lib_dir, tk_lib_dir, tk_ver = self.query_tcltk() - except: - tk_ver = '' - result = self.hardcoded_tcl_config() - else: - result = self.parse_tcl_config(tcl_lib_dir, tk_lib_dir) - if result is None: - result = self.guess_tcl_config( - tcl_lib_dir, tk_lib_dir, tk_ver) - if result is None: - result = self.hardcoded_tcl_config() - - # Add final versions of directories and libraries to ext lists - (tcl_lib_dir, tcl_inc_dir, tcl_lib, - tk_lib_dir, tk_inc_dir, tk_lib) = result - ext.include_dirs.extend([tcl_inc_dir, tk_inc_dir]) - class BackendGtk(OptionalBackendPackage): name = "gtk" From 626a1d276463faadc0268722335db093531d8efd Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Sat, 21 May 2016 20:32:29 -0400 Subject: [PATCH 11/14] RF: remove build-time check of Tkinter We don't need Tkinter at build time. We could move this Tkinter version check to `matplotlib/backends/tkagg.py` as a run-time check, but it's very unlikely that any Python we support would be linked against Tcl / Tk < 8.4. For example, the Python 2.5 Python.org OSX install links against Tcl / Tk 8.5, and the default Tcl / Tk on CentOS 5.11 is 8.4. --- setupext.py | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/setupext.py b/setupext.py index 0d4ee13cf495..76ba60a1180a 100755 --- a/setupext.py +++ b/setupext.py @@ -1525,28 +1525,8 @@ class BackendTkAgg(OptionalBackendPackage): name = "tkagg" def check_requirements(self): - try: - if PY3min: - import tkinter as Tkinter - else: - import Tkinter - except ImportError: - raise CheckFailed('TKAgg requires Tkinter.') - except RuntimeError: - raise CheckFailed('Tkinter present but import failed.') - else: - if Tkinter.TkVersion < 8.3: - raise CheckFailed("Tcl/Tk v8.3 or later required.") - - try: - tk_v = Tkinter.__version__.split()[-2] - except (AttributeError, IndexError): - # Tkinter.__version__ has been removed in python 3 - tk_v = 'not identified' - BackendAgg.force = True - - return "version %s" % tk_v + return "" def get_extension(self): sources = [ From 0c2c5a000c4a35509504fc679beaffb50b3b0280 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Mon, 23 May 2016 11:17:20 -0400 Subject: [PATCH 12/14] RF: force TkAgg / Agg extensions with messages Now we can always build TkAgg, so force building of Agg and TkAgg. --- setupext.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/setupext.py b/setupext.py index 76ba60a1180a..e55f9402a534 100755 --- a/setupext.py +++ b/setupext.py @@ -1506,6 +1506,7 @@ def get_install_requires(self): class BackendAgg(OptionalBackendPackage): name = "agg" + force = True def get_extension(self): sources = [ @@ -1523,10 +1524,10 @@ def get_extension(self): class BackendTkAgg(OptionalBackendPackage): name = "tkagg" + force = True - def check_requirements(self): - BackendAgg.force = True - return "" + def check(self): + return "installing; run-time loading from Python Tcl / Tk" def get_extension(self): sources = [ @@ -1658,8 +1659,6 @@ def check(self): return super(BackendGtkAgg, self).check() except: raise - else: - BackendAgg.force = True def get_package_data(self): return {'matplotlib': ['mpl-data/*.glade']} @@ -1735,7 +1734,6 @@ def check_requirements(self): p.join() if success: - BackendAgg.force = True return msg else: raise CheckFailed(msg) @@ -1808,7 +1806,6 @@ def check_requirements(self): p.join() if success: - BackendAgg.force = True return msg else: raise CheckFailed(msg) @@ -1852,8 +1849,6 @@ def check_requirements(self): raise CheckFailed( "Requires wxPython 2.8, found %s" % backend_version) - BackendAgg.force = True - return "version %s" % backend_version @@ -1963,7 +1958,6 @@ def backend_pyside_internal_check(self): except ImportError: raise CheckFailed("PySide not found") else: - BackendAgg.force = True return ("Qt: %s, PySide: %s" % (QtCore.__version__, __version__)) @@ -1980,7 +1974,6 @@ def backend_pyqt4_internal_check(self): except AttributeError: raise CheckFailed('PyQt4 not correctly imported') else: - BackendAgg.force = True return ("Qt: %s, PyQt: %s" % (self.convert_qt_version(qt_version), pyqt_version_str)) @@ -2016,7 +2009,6 @@ def backend_pyside2_internal_check(self): except ImportError: raise CheckFailed("PySide2 not found") else: - BackendAgg.force = True return ("Qt: %s, PySide2: %s" % (QtCore.__version__, __version__)) @@ -2032,7 +2024,6 @@ def backend_pyqt5_internal_check(self): except AttributeError: raise CheckFailed('PyQt5 not correctly imported') else: - BackendAgg.force = True return ("Qt: %s, PyQt: %s" % (self.convert_qt_version(qt_version), pyqt_version_str)) def backend_qt5_internal_check(self): From de99a760351361192aaf5a14fd9d2393b7a2cdc9 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Mon, 23 May 2016 12:17:02 -0400 Subject: [PATCH 13/14] BF: fix goofy double "installing" message Returning 'installing' from Windows check_requirements results in the message '[installing, installing]', because the `check` method default return string is also 'installing'. --- setupext.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setupext.py b/setupext.py index e55f9402a534..22413b206f0f 100755 --- a/setupext.py +++ b/setupext.py @@ -1883,7 +1883,7 @@ def check_requirements(self): config = self.get_config() if config is False: raise CheckFailed("skipping due to configuration") - return "installing" + return "" def get_extension(self): sources = [ From aa275c6971a8dd9047133d273af5746485741736 Mon Sep 17 00:00:00 2001 From: Matthew Brett Date: Tue, 24 May 2016 10:41:51 -0400 Subject: [PATCH 14/14] TST: add test of tkagg backend import Check that we can correctly import tkagg on travis and appveyor. --- .travis.yml | 2 ++ appveyor.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index f70cc010c1ee..674251b9b90d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -123,6 +123,8 @@ script: # Travis VM to run out of memory (since so many copies of inkscape and # ghostscript are running at the same time). - | + echo Testing import of tkagg backend + MPLBACKEND="tkagg" python -c 'import matplotlib.pyplot as plt; print(plt.get_backend())' echo Testing using $NPROC processes echo The following args are passed to nose $NOSE_ARGS if [[ $BUILD_DOCS == false ]]; then diff --git a/appveyor.yml b/appveyor.yml index 32f6b4fde8e9..cfe33ca29944 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -87,6 +87,8 @@ install: test_script: # Now build the thing.. - '%CMD_IN_ENV% python setup.py develop' + # Test import of tkagg backend + - python -c "import matplotlib as m; m.use('tkagg'); import matplotlib.pyplot as plt; print(plt.get_backend())" # tests - python tests.py # remove to get around libpng issue?