Sometimes your project has useful internal tools, but building them can bit cumbersome for other teams which can lead to time being wasted trying to instruct how to get build environment setup and the tool compiled. It would be nice to distribute these internal tools as self contained executables for other teams. PyInstaller is a Python package that can create Linux and Windows binaries that extract the package contents and run a script/entrypoint. Here is a short tutorial how to package internal tools with PyInstaller.
If the tool you are packaging is a simple script that does not have external dependencies (for example non Python executables), packaging can be as easy as:
pyinstaller --onefile tool.py --name tool
But many times the tool uses some binaries that have been compiled and need to be added to the package. We can add extra files into to the package with
# pyinstaller --onefile tool.py --name tool --add-data '<source>:<destination in package>' pyinstaller --onefile tool.py --name tool --add-data 'file.txt:.'
Above solution works quite well, but your tool will not automatically have access to the binary/file you included, as it most likely can not find the file.
To fix this, we need to add a wrapper script that modifies
PATH environment variable and adds the extracted package path (or even sub directories) to the
pyinstaller extracts the package content, it uses the
tmp system directory and creates a random sub directory. Unfortunately
__file__ global variable will not point to the correct location, but we need to check the correct location of the wrapper script with a small function:
import os import sys def get_script_location(): if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'): # Running from PyInstaller # Return location of the extracted package location = os.path.realpath(sys._MEIPASS) else: # Normal python run # When running PyInstaller package this would incorrectly return # current working directory location = os.path.dirname(os.path.realpath(__file__)) return location
We can now add the script path to the
def add_to_path(p): os.environ['PATH'] = p + os.pathsep + os.environ['PATH'] add_to_path(get_script_location())
and then run the original script:
import tool tool.main()
Check out https://github.com/buq2/pyinstaller_example for full example and how to cross compile the packages.