nxet
nxet

Reputation: 738

PyInstaller + Wand (ImageMagick) - missing dependencies

I've spent the past couple of days trying to figure out how to build a dep-free application out of a bunch of Python3.4 scripts I have. These include an import of wand, which requires ImageMagick to be installed on the machine.
So far so good, the app ran from source code works fine both my dev environments (Debian8/Win10), finding all the necessary dependencies for the Wand module to properly operate.

Troubles come when I try to package the app with PyInstaller.
The build process works as expected, fetching the ImageMagick resources (.dlls etc) and including them in the output directory alongside the other binaries, but when I run the app on machines without the IM library installed the import fails.

So I went digging into the Wand source code, and realized that dependecies are looked for in global system paths (i.e. C:\ProgramFiles). As a workaround, in my code I simply overridden the os.environ['MAGICK_HOME'] variable with './' instructing the module to fetch the dependencies in place. And it worked, up to a point.

Now I can start the app: imports won't fail, the GUI is drawn and functionality is not affected. Not until I trigger any Wand-involving action at least, because whenever I do the app crashes raising the following exception:

wand.exceptions.ConfigureError: RegistryKeyLookupFailed `CoderModulesPath' @ error/module.c/GetMagickModulePath/666

It seems clear that the lack of a proper installation system-wide results in broken registry calls (now testing on win), but unfortunately I'm not quite familiar with CTypes (or whatever is being used to bind to the ImageMagick library) and I'm not sure how to proceed on properly building a no-deps app.

As always the first question popping into mind is: am I missing something obvious here?
If not, what would be your suggestions?
Thanks

Upvotes: 2

Views: 1173

Answers (2)

jozxyqk
jozxyqk

Reputation: 17314

What set me on the right path was looking through wand's api.py:

    magick_home = os.environ.get('MAGICK_HOME')
    magick_suffix = os.environ.get('WAND_MAGICK_LIBRARY_SUFFIX')

    if system == 'Windows':
        # ImageMagick installers normally install coder and filter DLLs in
        # subfolders, we need to add those folders to PATH, otherwise loading
        # the DLL later will fail.
        ...

Two things are needed:

  1. Add the right ImageMagick DLLs to the pyinstaller .spec's Analysis's binaries so they get packaged (or use pyinstaller --add-binary)
  2. At runtime, check sys._MEIPASS to get pyinstaller's _internal directory, set environment variables MAGICK_HOME, MAGICK_CODER_MODULE_PATH and MAGICK_CODER_FILTER_PATH and add them all to PATH

Armed with this information, I updated my pyinstaller .spec file to read those environment variables and registry entries itself (that would have been the sane solution) run the private library_paths() from wand for the DLL locations and diff PATH before/after to extract the regkey for the coder and filter paths. See here

This of course means the machine running pyinsaller needs to have the ImageMagick DLLs installed in a place wand can find them.

Upvotes: 0

emcconville
emcconville

Reputation: 24439

This is more of a comment, and not really an answer, but I would suggest the following...

MAGICK_HOME

It's wise to set the MAGICK_HOME environment variable. But I would suggest using os.path to resolve the systems absolute path; as opposed to relative.

os.environ['MAGICK_HOME'] = os.path.abspath('.')

Very minor, but helps.

.dll's etc

Remember that IM is it's own ecosystem. It's true that wand only needs MagickWand & MagickCore libraries to run, but IM needs all the delegates/coders to be useful. Ensure everything is present, and double check QuickStart.txt.

On your build system, it might be worth checking identify utility to see other paths that should be included included during PyInstallers bundle stage.

identify -list Configure | grep PATH

On my Mac, I receive the following...

CODER_PATH     /usr/local/lib/ImageMagick-6.9.5/modules-Q16/coders
CONFIGURE_PATH /usr/local/etc/ImageMagick-6/
DOCUMENTATION_PATH /usr/local/share/doc/ImageMagick-6
EXECUTABLE_PATH /usr/local/bin
FILTER_PATH    /usr/local/lib/ImageMagick-6.9.5/modules-Q16/filters
INCLUDE_PATH   /usr/local/include/ImageMagick-6
LIBRARY_PATH   /usr/local/lib/ImageMagick-6.9.5
SHARE_PATH     /usr/local/share/ImageMagick-6
SHAREARCH_PATH /usr/local/lib/ImageMagick-6.9.5/config-Q16

File policy.xml under CONFIGURE_PATH would be ideal to include to ensure your application protects against ImageTragick

Upvotes: 2

Related Questions