Join GitHub today
GitHub is home to over 20 million developers working together to host and review code, manage projects, and build software together.
loading TCL / Tk symbols dynamically #6442
Conversation
mdboom
added the
needs_review
label
May 17, 2016
|
Thanks for this. This is really helpful. @matthew-brett wrote:
I'd prefer to give this loads of testing and just have this as the "one way to do it" if we can. We could eliminate tons of hacks in the build script and tons of pain and support using this approach, at the expense of the very minor dynamic linking at import time.
I don't think so.
I'll read through and try to verify. |
mdboom
commented on an outdated diff
May 17, 2016
| @@ -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 <dlfcn.h> | ||
| + | ||
| +#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))) |
mdboom
Owner
|
mdboom
commented on an outdated diff
May 17, 2016
| +// 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(); |
|
|
mdboom
commented on an outdated diff
May 17, 2016
| +#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; |
mdboom
Owner
|
mdboom
commented on an outdated diff
May 17, 2016
| + 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); |
mdboom
Owner
|
What's wrong with |
Because the point of this change is to replace actual direct function calls with function pointers. Numpy, for example, has a special generated header for this exact purpose ( |
|
I was hoping since it's C++ you could use some |
|
|
|
We don't need runtime typing; we just need the type of the function at compile time so that we can set up a pointer to it. |
|
The return type of |
|
I didn't really mean |
ogrisel
commented on an outdated diff
May 18, 2016
| @@ -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 <dlfcn.h> |
ogrisel
|
|
@mdboom - addressed your comments - I think. I've tested disabling the linker flags for TCL / Tk libraries in |
|
Cool. If this works, we might as well take most of the crazy header-file-hunting out of As for the Windows-specific issues, @cgohlke, any thoughts? |
|
I've done some speculative changes to support Windows, trying to work out how to test them now. |
|
OK - Windows changes are compiling and passing tests on Python 2.7 at least. I propose to remove the not-dynamic branch, and disable linking the TCL / Tk libs in |
matthew-brett
added some commits
May 18, 2016
matthew-brett
changed the title from
WIP: loading TCL / Tk symbols dynamically to MRG: loading TCL / Tk symbols dynamically
May 18, 2016
|
Build-time linking disabled, Appveyor tests passing, failure on travis I believe is unrelated, so from my point of view, I think this is ready. |
|
Maybe I am missing something, but how can this work on Windows where the Tcl/Tk symbols are not exported from >>> from ctypes import *
>>> from ctypes.wintypes import *
>>> windll.kernel32.LoadLibraryW.restype = HMODULE
>>> windll.kernel32.LoadLibraryW.argtypes = [LPCWSTR]
>>> windll.kernel32.GetProcAddress.argtypes = [HMODULE, LPCSTR]
>>> windll.kernel32.GetProcAddress.restype = c_void_p
>>> from tkinter import _tkinter
>>> hdll = windll.kernel32.LoadLibraryW(_tkinter.__file__)
>>> print(windll.kernel32.GetProcAddress(hdll, b"PyInit__tkinter"))
140732219092336
>>> print(windll.kernel32.GetProcAddress(hdll, b"Tcl_CreateCommand"))
None |
|
Quick test with a conda python 3.5 environment on OS X 10.9.5: it works! I was not able to get a working Tk backend without this.
|
|
Christoph - sorry - I'm behind a fierce proxy in Cuba, and I can't compile myself. Google is blocking me from downloading the Appveyor build artifacts with The code at https://github.com/matplotlib/matplotlib/pull/6442/files#diff-c4f5727f595ec7b41e86800bb717a0efR323 should be checking for null pointers at run time, and raising an exception. What do you get if you try and make and show a plot with the tkagg backend using one the build artifacts on Appveyor - e.g. https://ci.appveyor.com/project/mdboom/matplotlib/build/1.0.1553/job/c4lm62wyn1541509/artifacts ? |
|
Using >>> import matplotlib
>>> matplotlib.use('TkAgg')
>>> matplotlib.__version__
'1.5.1+1973.g7257404'
>>> from matplotlib import pyplot
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "matplotlib\pyplot.py", line 113, in <module>
_backend_mod, new_figure_manager, draw_if_interactive, _show = pylab_setup()
File "matplotlib\backends\__init__.py", line 55, in pylab_setup
[backend_name], 0)
File "matplotlib\backends\backend_tkagg.py", line 13, in <module>
import matplotlib.backends.tkagg as tkagg
File "matplotlib\backends\tkagg.py", line 9, in <module>
from matplotlib.backends import _tkagg
RuntimeError: Cannot load function Tcl_CreateCommandAlso, the artifact is missing zlib.dll: >>> from matplotlib import pyplot
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "matplotlib\pyplot.py", line 29, in <module>
import matplotlib.colorbar
File "matplotlib\colorbar.py", line 34, in <module>
import matplotlib.collections as collections
File "matplotlib\collections.py", line 27, in <module>
import matplotlib.backend_bases as backend_bases
File "matplotlib\backend_bases.py", line 63, in <module>
import matplotlib.textpath as textpath
File "matplotlib\textpath.py", line 20, in <module>
from matplotlib.mathtext import MathTextParser
File "matplotlib\mathtext.py", line 62, in <module>
import matplotlib._png as _png
ImportError: DLL load failed: The specified module could not be found. |
|
The png failure must be unrelated. I suppose the GetProcAddress failure is related to this
So, the next question is - do you (Christoph) or does anyone know how to query |
|
Rather than analyze the DLL file and add an extra dependency, I am trying to search through the loaded modules in the current process, looking for the TCL / Tk routines. This seems to be very fast on simple tests. |
|
Christoph - would you mind checking https://ci.appveyor.com/project/mdboom/matplotlib/build/1.0.1558/job/snxq695d8h4sgqli/artifacts |
|
I finally got a Windows test install working - this code works for me. |
mdboom
and 1 other
commented on an outdated diff
May 20, 2016
| @@ -1817,8 +1811,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]) |
mdboom
Owner
|
|
I did some timing on the tcl / tk symbol search on Windows. It's very fast. Package / function to replicate the search at https://github.com/matthew-brett/tktest:
|
|
Test failures seem to be due to freetype source server, not these changes. |
tacaswell
referenced
this pull request
May 21, 2016
Open
Cannot start tkinter-based example on Python 3.5.1 using Mac Homebrew for Python and Tk #5837
matthew-brett
added some commits
May 21, 2016
|
OK - I checked current Tcl / Tk trunk, and I've edited the header excerpts, so they are compatible from Tcl / Tk 8.4 through current Tcl / Tk trunk. I think we should get away with these header excerpts for a long time. I've removed all the Tcl / Tk discovery stuff as no longer needed, and tested on OSX, Linux and Windows. I think this one is ready to merge. |
|
Github seems to be having trouble today - the latest commit for this branch is de99a76 - I don't see that yet in this interface. When it arrives, you should have the changes I referred to in my last message. |
tacaswell
closed this
May 23, 2016
tacaswell
reopened this
May 23, 2016
tacaswell
added needs_review and removed needs_review
labels
May 23, 2016
|
|
tacaswell
added this to the
1.5.2 (Critical bug fix release)
milestone
May 23, 2016
matthew-brett
referenced
this pull request
May 23, 2016
Closed
Test failures testing matplotlib 1.5.1 manylinux wheels #6469
|
Travis passed. Appveyor stalled in the build stage on the Py3.5 job. I didn't see anything indicating the build failure was related to the PR, so I am closing and reopening to try again. |
efiring
closed this
May 23, 2016
efiring
reopened this
May 23, 2016
efiring
added needs_review and removed needs_review
labels
May 23, 2016
|
Well, I don't have any idea what the problem is, but Appveyor failed to build on Python 3.5 just like last time. Very little output is generated. It seems to stall before actually doing any conda installations. |
matthew-brett
referenced
this pull request
May 24, 2016
Closed
Matplotlib manylinux wheel - ready to ship? #6473
|
Appveyor now passing on all Pythons for some reason. Travis failure looks unrelated. Appveyor and Travis tests now testing tkagg import and succeeding. Tests of this branch on OSX at MacPython/matplotlib-wheels finding and importing tkagg correctly (unrelated failures but tests passing on Pythons 2.7 3.4 3.5). |
|
Everything passed and Appveyor showed the tkagg test result correctly, but it looks like Travis either didn't execute the tkagg import test, or swallowed its output:
|
|
OSX builds now green - successful tkagg import checks for Python 2.7 Python 3.4 Python 3.5. |
|
Eric - I think the missing output is just because the command is in a block with other commands. The output is further down after the whole command input block - e.g. https://travis-ci.org/matplotlib/matplotlib/jobs/132588520#L1018 |
efiring
merged commit f3e5576
into matplotlib:master
May 24, 2016
efiring
removed the
needs_review
label
May 24, 2016
efiring
added a commit
to efiring/matplotlib
that referenced
this pull request
May 24, 2016
|
|
efiring |
7069119
|
efiring
referenced
this pull request
May 24, 2016
Closed
TEST: Merge pull request #6442 from matthew-brett/dynamic-tkagg #6474
QuLogic
added the
GUI/tk
label
May 24, 2016
efiring
added a commit
that referenced
this pull request
May 24, 2016
|
|
efiring |
b80e0f1
|
|
backported to v1.5.x as b80e0f1 |
This was referenced May 25, 2016
QuLogic
changed the title from
MRG: loading TCL / Tk symbols dynamically to loading TCL / Tk symbols dynamically
Dec 7, 2016
cournape
commented
Jan 26, 2017
|
When updating matplotlib for canopy, I am seeing the error "Could not find TCL routines" with 1.5.3. At first, I suspect an issue in how we build python for canopy, but I have been able to reproduce the issue with python 2.7.12 from python.org and just installing wheels from Gholke website. The failure appears for both 1.5.3 and 2.0.0. I could also reproduce the issue w/ 1.5.3 using conda (2.7.13 this time) |
cournape
commented
Jan 26, 2017
|
Hm, that should really be an issue on its own, sorry |
matthew-brett commentedMay 17, 2016
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:
the matplotlib source, but not enabled by default, to help building
binary wheels?
the TCL / Tk headers rather than copying them into the _tkagg.cpp
source file (typdefs starting around line 52)?
exceptions and handle references correctly?