Create an executable from Python code with gmsh import

540 Views Asked by At

I'm trying to package up my Python package into an executable using pyinstaller. The script name is called "run-jointbuilder.py" The package has a number of dependancies (such as numpy), but importantly gmsh.

When using pyinstaller to compile my code, it appears to be successful, but then when I try to run the executable I get the following errors:

import gmsh # PyInstaller PYZ\
import ctypes.util # PyInstaller PYZ\
import 'ctypes.util' # <pyimod03_importers.FrozenImporter object at 0x000001BD783FC910>\
Traceback (most recent call last):\
  File "PyInstaller\loader\pyiboot01_bootstrap.py", line 144, in __init__
  File "ctypes\__init__.py", line 381, in __init__\
FileNotFoundError: Could not find module 'C:\Users\willber\Anaconda3\Scripts\gmsh' (or one of its dependencies). Try using the full path with constructor syntax.

I then get this error:

__main__.PyInstallerImportError: Failed to load dynlib/dll

'C:\\Users\\willber\\Anaconda3\\Scripts\\gmsh'. Most probably this dynlib/dll was not found when the application was frozen.

[18612] Failed to execute script run-jointbuilder

Has anybody tried to compile some Python code that imports the gmsh package? I'd really appreciate an example .spec file, for use with pyinstaller if so!

1

There are 1 best solutions below

1
On

The gmsh python package wraps a bunch of compiled libraries which contain the implementations of the methods you call from python. When you import gmsh.py into your script then gmsh loads these libraries in the background giving you access to their functionality through python methods. So it's essential that these libraries are embedded in the pyinstaller output for your code to function as it does when you run it directly through the python interpreter.

It's difficult for pyinstaller to consistently find these libraries since they're not accessed in the same way as normal python imports, they are loaded using the cytpes package. There's some description of how pyinstaller does this in the docs. Since you're seeing a dynlib/dll loading error when you run the compiled python script, this suggests that pyinstaller isn't finding the gmsh library during compile and hence it is missing from the executable.

If you look in the gmsh.py source, you can see that gmsh.py loads up a .dll library called gmsh-4.9.dll for Windows OS. You can use the binaries input of the pyinstaller .spec file to point the compiler to the gmsh-4.9.dll.

Here's an example .spec file which dynamically locates the gmsh-4.9.dll at the time of compile so it picks up the right .dll for your active environment. You could make this more generic by filtering all *.dll in the gmsh directory, but I've hardcoded for clarity:

# -*- mode: python ; coding: utf-8 -*-
from pathlib import Path
import gmsh

# get the location of the gmsh dll which sits next to the gmsh.py file
libname = 'gmsh-4.9.dll'
libpath = Path(gmsh.__file__).parent / libname
print('Adding {} to binaries'.format(libpath))

block_cipher = None

a = Analysis(['gmsh-test.py'],
         pathex=['C:\\Users\\user\\dev\\gmsh'],
         # tell pyinstaller to add the binary to the compiled path
         binaries=[(str(libpath), '.')],
         datas=[],
         hiddenimports=[],
         hookspath=[],
         runtime_hooks=[],
         excludes=[],
         win_no_prefer_redirects=False,
         win_private_assemblies=False,
         cipher=block_cipher,
         noarchive=False)

pyz = PYZ(a.pure, a.zipped_data,
         cipher=block_cipher)

exe = EXE(pyz,
      a.scripts,
      a.binaries,
      a.zipfiles,
      a.datas,
      name='gmsh-test',
      debug=False,
      bootloader_ignore_signals=False,
      strip=False,
      upx=True,
      console=True,
      runtime_tmpdir=None,
      )