Search code examples
pythonlinuxpyinstallergraphvizpygraphviz

PyInstaller: Unable to find '/usr/sbin/neato' when adding binary and data files


I am using PySide6 and pygraphviz to create a simple three widget desktop application. When I use pyinstaller --onefile --noconsole stackedAPP.py, PyInstaller throws me this error: Unable to find '/usr/sbin/neato' when adding binary and data files. It seems that PyInstaller cannot find any of the pygraphviz layouts, not just neato because I tried using other layouts too. This is not an isolated issue, as I found in this older post, but there was no answer so I had to ask again. A similar PyInstaller error here seemed to have been resolved after modifying the hooks for the package, so I tried to look into hook-pygraphviz.py.

It seemed to me that the error lies in the line: graphviz_bindir = os.path.dirname(os.path.realpath(shutil.which("dot"))) which returns /usr/sbin

Upon further investigation, I found that shutil.which('dot') returns /usr/bin/dot which is actually correct (having manually confirmed it). But the result of os.path.realpath('/usr/bin/dot') is actually something else entirely:

>>> import os
>>> import shutil
>>> shutil.which("dot")
'/usr/bin/dot'
>>> os.path.realpath('/usr/bin/dot')
'/usr/sbin/libgvc6-config-update'

So the reason why PyInstaller cannot find neato in /usr/sbin is because it is not in /usr/sbin, and this is why it is searching in /usr/sbin in the first place. So I decided to manually modify the hook and set the path as graphviz_bindir = '/usr/bin' This helped and PyInstaller compiled successfully, but when I use the application, it crashes when it enters the stage where it is using pygraphviz and this is the error message I see:

(stackedAPP:18070): GLib-GIO-CRITICAL **: 22:02:47.490: GFileInfo created without standard::icon

(stackedAPP:18070): GLib-GIO-CRITICAL **: 22:02:47.490: file ../../../gio/gfileinfo.c: line 1766 (g_file_info_get_icon): should not be reached
Traceback (most recent call last):
  File "processUI.py", line 127, in on_finished
  File "processUI.py", line 189, in generate_causal_loop_diagram
  File "pygraphviz/agraph.py", line 1613, in draw
  File "pygraphviz/agraph.py", line 1404, in _run_prog
OSError: Warning: Could not load "/tmp/_MEIsRtebz/graphviz/libgvplugin_pango.so.6" - file not found
Warning: Could not load "/tmp/_MEIsRtebz/graphviz/libgvplugin_pango.so.6" - file not found
Warning: Could not load "/tmp/_MEIsRtebz/graphviz/libgvplugin_pango.so.6" - file not found
Warning: Could not load "/tmp/_MEIsRtebz/graphviz/libgvplugin_pango.so.6" - file not found
Warning: Could not load "/tmp/_MEIsRtebz/graphviz/libgvplugin_gd.so.6" - file not found
Format: "png" not recognized. Use one of: bmp canon cmap cmapx cmapx_np dot dot_json eps fig gd gd2 gif gtk gv ico imap imap_np ismap jpe jpeg jpg json json0 mp pdf pic plain plain-ext png pov ps ps2 svg svgz tif tiff tk vdx vml vmlz vrml wbmp webp x11 xdot xdot1.2 xdot1.4 xdot_json xlib

I'm not sure how to proceed from here. I am using VirtualBox to run Ubuntu-23.10 on which I am running this process. When I installed graphviz and pygraphviz I used:

sudo apt-get graphviz graphviz-dev
pip install pygraphviz

as recommended in this documentation. Any help would be greatly appreciated!


Solution

  • One workaround I found to just get an application is to use cx_freeze

    I ran cxfreeze -c translator.py --target-dir dist and got an application that runs without any errors. I'll have to take a look how cx_Freeze does things differently from PyInstaller.

    It's also possible to use pyinstaller, but I had to modify hooks/stdhooks/hook-pygraphviz.py slightly. I modified the line graphviz_bindir = os.path.dirname(os.path.realpath(shutil.which("dot"))) to graphviz_bindir = shutil.which('dot') and added dynamic graphviz libs by locating where graphviz was and then using --add-binary argument from pyinstaller. For me it was something like pyinstaller --onefile --add-binary /usr/lib/x86_64-linux-gnu/graphviz:graphviz main.py.