Search code examples
pythonpython-3.xpyinstallergnupgpgp

How to freeze python app with GnuPG (gpg) executable and dependencies (PyInstaller)


How do I include gpg (and all its dependencies) when I build my app with PyInstaller?

I'm releasing my python app as standalone executables for Linux, Windows, and MacOS using PyInstaller. I need to use gpg, so I'm using the python-gnupg module -- which basically just shells out to the locally-installed gpg binary on the system.

Because my app is designed to be a "portable" executable (I use PyInstaller to ship it with the python interpreter and all the other dependencies), I want to include gpg with my releases so the user doesn't have to install it on their machine.

How can I add the gpg binary and all its dependencies for the python-gnupg module using PyInstaller targeting Linux, Windows, and MacOS?


Solution

  • You can package GnuPG with your PyInstaller-built app by adding the gpg binary and all its dependencies using the Analysis.datas key.

    Windows

    On windows, I got this to work with trial and error. First I just searched the entire system for the gpg.exe binary. In powershell:

    > Get-Command gpg
    0.0.0.0    C:\Program Files\Git\usr\bin\gpg.exe
    > 
    

    Then I added it to my PyInstaller .spec file by changing this section

    a = Analysis(['..\\src\\main.py'],
                 pathex=['.\\'],
                 binaries=[],
                 datas=[],
                 hiddenimports=['pkg_resources.py2_warn', 'libusb1'],
                 hookspath=[],
                 runtime_hooks=[],
                 excludes=[],
                 win_no_prefer_redirects=False,
                 win_private_assemblies=False,
                 cipher=block_cipher,
                 noarchive=False)
    
    

    Into this

    a = Analysis(['..\\src\\main.py'],
                 pathex=['.\\'],
                 binaries=[],
                 datas=
                  [
                   ('C:\\Program Files\\Git\\usr\\bin\\gpg.exe', '.'),              ],
                 hiddenimports=['pkg_resources.py2_warn', 'libusb1'],
                 hookspath=[],
                 runtime_hooks=[],
                 excludes=[],
                 win_no_prefer_redirects=False,
                 win_private_assemblies=False,
                 cipher=block_cipher,
                 noarchive=False)
    
    

    After the above change, my app would popup a warning error, such as:

    The code execution cannot proceed because msys-bz2-1.dll was not found. Reinstalling the program may fix this problem.
    

    One-by-one, I kept finding (using Get-Command in powershell as shown above) & adding the .dll files it complained about until my datas in the spec file above had the following contents, which worked:

                 datas=
                  [
                   ( '..\\KEYS', '.' ),
                   ('C:\\Program Files\\Git\\usr\\bin\\gpg.exe', '.'),
                   ('C:\\msys64\\usr\\bin\\msys-bz2-1.dll', '.'),
                   ('C:\\msys64\\usr\\bin\\msys-assuan-0.dll', '.'),
                   ('C:\\msys64\\usr\\bin\\msys-gcrypt-20.dll', '.'),
                   ('C:\\msys64\\usr\\bin\\msys-gpg-error-0.dll', '.'),
                   ('C:\\msys64\\usr\\bin\\msys-2.0.dll', '.'),
                   ('C:\\msys64\\usr\\bin\\msys-readline8.dll', '.'),
                   ('C:\\msys64\\usr\\bin\\msys-z.dll', '.'),
                   ('C:\\msys64\\usr\\bin\\msys-sqlite3-0.dll', '.'),
                   ('C:\\msys64\\usr\\bin\\msys-iconv-2.dll', '.'),
                   ('C:\\msys64\\usr\\bin\\msys-intl-8.dll', '.'),
                   ('C:\\msys64\\usr\\bin\\msys-ncursesw6.dll', '.'),
                  ],
    

    You can see my full build script and .spec file here:

    MacOS

    In MacOS, I found that I didn't have to add any dependencies--only the executable.

                 datas=[ ('/usr/local/bin/gpg', '.') ],
    

    You can see my full build script and .spec file here:

    Linux

    I actually didn't build a release for Linux using PyInstaller as I'm using AppImage instead.

    To see my full build script for Linux, see: