[CPython] Include TCL/TK for Windows binding
HannesWell opened this issue · 3 comments
When using Python's tkinter directly or indirectly a local installation of tcl
and tk
is necessary.
And while JavaCPP's cPypthon includes the tkinter
module it doesn't contain tcl/tk.
Installing tcl/tk via PIP
or alike is not possible. On Windows the CPython can install tcl/tk as part of the python installation and on Linux it can be installed via apt-get and is often already available:
https://stackoverflow.com/questions/69603788/how-to-pip-install-tkinter
Therefore I would like to suggest to include tcl/tk into JavaCPP's cpython-preset for Windows.
Without that the following Python program cannot be executed using javacpp-embedded-python:
Pip.install("matplotlib"); // "numpy"
StringJoiner lines = new StringJoiner("\n");
lines.add("import matplotlib.pyplot as plt");
lines.add("import numpy as np");
lines.add("x = np.linspace(0, 2 * np.pi, 200)");
lines.add("y = np.sin(x)");
lines.add("fig, ax = plt.subplots()");
lines.add("ax.plot(x, y)");
lines.add("fig.savefig('plot.png')");
Python.eval(lines.toString());
org.bytedeco.embeddedpython.PythonException: Failed to execute:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 2 * np.pi, 200)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y)
fig.savefig('plot.png')
Traceback (most recent call last):
File "<string>", line 6, in <module>
File "~\.javacpp\cache\cpython-3.12.1-1.5.10-windows-x86_64.jar\org\bytedeco\cpython\windows-x86_64\lib\site-packages\matplotlib\pyplot.py",
line 1759, in subplots
fig = figure(**fig_kw)
^^^^^^^^^^^^^^^^
File "~\.javacpp\cache\cpython-3.12.1-1.5.10-windows-x86_64.jar\org\bytedeco\cpython\windows-x86_64\lib\site-packages\matplotlib\pyplot.py",
line 1027, in figure
manager = new_figure_manager(
^^^^^^^^^^^^^^^^^^^
File "~\.javacpp\cache\cpython-3.12.1-1.5.10-windows-x86_64.jar\org\bytedeco\cpython\windows-x86_64\lib\site-packages\matplotlib\pyplot.py",
line 550, in new_figure_manager
return _get_backend_mod().new_figure_manager(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "~\.javacpp\cache\cpython-3.12.1-1.5.10-windows-x86_64.jar\org\bytedeco\cpython\windows-x86_64\lib\site-packages\matplotlib\backend_bases.py",
line 3507, in new_figure_manager
return cls.new_figure_manager_given_figure(num, fig)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "~\.javacpp\cache\cpython-3.12.1-1.5.10-windows-x86_64.jar\org\bytedeco\cpython\windows-x86_64\lib\site-packages\matplotlib\backend_bases.py",
line 3512, in new_figure_manager_given_figure
return cls.FigureCanvas.new_manager(figure, num)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "~\.javacpp\cache\cpython-3.12.1-1.5.10-windows-x86_64.jar\org\bytedeco\cpython\windows-x86_64\lib\site-packages\matplotlib\backend_bases.py",
line 1797, in new_manager
return cls.manager_class.create_with_canvas(cls, figure, num)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "~\.javacpp\cache\cpython-3.12.1-1.5.10-windows-x86_64.jar\org\bytedeco\cpython\windows-x86_64\lib\site-packages\matplotlib\backends\_backend_tk.py",
line 483, in create_with_canvas
window = tk.Tk(className="matplotlib")
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "~\.javacpp\cache\cpython-3.12.1-1.5.10-windows-x86_64.jar\org\bytedeco\cpython\windows-x86_64\lib\tkinter\__init__.py",
line 2340, in __init__
self.tk = _tkinter.create(screenName, baseName, className,
interactive, wantobjects, useTk, sync, use)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
_tkinter.TclError: Can't find a usable init.tcl in the following
directories:
~/.javacpp/cache/cpython-3.12.1-1.5.10-windows-x86_64.jar/org/bytedeco/cpython/windows-x86_64/lib/tcl8.6
This probably means that Tcl wasn't installed properly.
A workaround is to wrap the tcl folder of a 'classic' Cpython installation and distribute and cache it along with javacpp-cpython and run the following as first steps after the initialization of python (e.g. via Py_Initialize()
):
File tclFolder = Loader.cacheResource("/foo/bar/tcl/" + Loader.getPlatform());
Path tclLibrary = tclFolder.toPath().resolve("tcl8.6");
Path tkLibrary = tclFolder.toPath().resolve("tk8.6");
Path tixLibrary = tclFolder.toPath().resolve("tix8.4.3");
PyRun_SimpleStringFlags(String.format("""
import os
os.environ['TCL_LIBRARY'] = r'%1$s'
os.environ['TK_LIBRARY'] = r'%2$s'
os.environ['TIX_LIBRARY'] = r'%3$s'
""", tclLibrary, tkLibrary, tixLibrary));
That's possible, but cumbersome and not easy to get to if one is not deeply familiar with the topic.
Unfortunately I cannot tell yet how including tcl/tk could be achieved, since I haven't fully understood yet how the CPython installer bundle tcl/tk.
Looks like we can use PCbuild/prepare_tcltk.bat for that on Windows:
https://github.com/python/cpython/blob/main/PCbuild/readme.txt
Thanks for the pointer. This looks promising. I'll look into it.
Do you have any immediate hints where this could maybe applied in the cpython preset?
I'm guessing somewhere before calling build.bat here:
https://github.com/bytedeco/javacpp-presets/blob/master/cpython/cppbuild.sh#L189