PyInstaller
Quick guide to build a standalone application from a python script with pyinstaller.
Install pyinstaller:
pip install pyinstaller
Run pyinstaller:
pyinstaller yourprogram.py
That's it!
The bundled application will be generated in a subdirectory called dist.
Warning
It is not recommanded to use the flag --onefile to bundle the application in a single .exe file. In fact, when exectued, the application will be first extracted in a temporary directoy somewhere in the user's profile and that takes time.
During the process, pyinstaller create a directory called build which can be deleted thereafter.
A .spec file is equally generated. This file contain all information to build the application and can be tweaked
for more grained results (files to include, exclude, ...).
Normally, all parameters can be passed as arguments when calling pyinstaller. Thoses parameter will be refleced
in the .spec file. Therefore, the second time, one can call pyinstaller directly with the .spec file as follow:
pyinstaller yourprogram.spec
Note
One may think that the Spec file is a temporary file and should not be versionned and provided along the application code. But it is most easier to store the build information in this file and to provide this latter with the application code for subsequent builds.
This file is treated as a python script and one can import python module and write more complex code within this file.
For example, with shutil module we can copy folders and files in the dist folder when the build finish:
import shutil
shutil.copyfile('filetocopy.ini', '{0}/{1}'.format(DISTPATH, 'filetocopy.ini'))
DISTPATH is a global variable available in the Spec file. All global variables available are listed in the
official documentation
Access the application folder
When accessing files located within the application folder, one must use the code bellow to find the current execution folder of the application that works both in bundled or live code:
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
# running as bundle (aka frozen)
app_dir = sys._MEIPASS
else:
# running live
app_dir = os.path.dirname(os.path.abspath(__file__))
app_dir = getattr(sys, '_MEIPASS', path.abspath(path.dirname(__file__)))
app_dir as follow for example:
config_file = os.path.abspath(os.path.join(app_dir, 'app_config.ini'))
Official doc (Using __file__) <https://pyinstaller.readthedocs.io/en/stable/runtime-information.html#using-file>_
Complete example of Spec file
In this example, pyinstaller couldn't find and copy the DLL files of the pyzbar module.
We added them manually.
# -*- mode: python ; coding: utf-8 -*-
from os import path, environ
import shutil
block_cipher = None
project_name = 'QRCodeScanner'
project_folder = path.dirname(path.abspath('.'))
venv_folder = environ['VIRTUAL_ENV']
pyzbar_folder = path.join(venv_folder, 'Lib\site-packages\pyzbar')
# For some reason pyzbar binaries files aren't find automatically.
# We add them manually here.
a = Analysis(['main.py'],
pathex=[project_folder],
binaries=[(path.join(pyzbar_folder, 'libzbar-64.dll'), '.'),
(path.join(pyzbar_folder, 'libiconv.dll'), '.')],
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,
[],
exclude_binaries=True,
name=project_name,
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True)
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name=project_name)
shutil.copyfile('app_config.ini', '{0}/{1}/app_config.ini'.format(DISTPATH, project_name))