bytedeco / javacpp-presets

The missing Java distribution of native C++ libraries
Other
2.65k stars 736 forks source link

[CPython] Include TCL/TK for Windows binding #1535

Open HannesWell opened 2 weeks ago

HannesWell commented 2 weeks ago

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.

saudet commented 2 weeks ago

Looks like we can use PCbuild/prepare_tcltk.bat for that on Windows: https://github.com/python/cpython/blob/main/PCbuild/readme.txt

HannesWell commented 2 weeks ago

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?

saudet commented 2 weeks ago

I'm guessing somewhere before calling build.bat here: https://github.com/bytedeco/javacpp-presets/blob/master/cpython/cppbuild.sh#L189