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__))
or more simply:
app_dir = getattr(sys, '_MEIPASS', path.abspath(path.dirname(__file__)))
Then, to read a particular file within the application folder, use app_dir
as follow for example:
config_file = os.path.abspath(os.path.join(app_dir, 'app_config.ini'))
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))