I'm trying to get a wxPython app working as an exe. I've heard that PyInstaller is now superior to py2exe. I'd like to include my .ico and two .png files that the script requires to run. What would the spec file for this look like? I can't seem to find a decent example anywhere. I have PyInstaller installed, but I can't find this "makespec" anywhere.
In my PyInstaller projects, I'd normally just have a runtime check to see if the app's frozen and adjust the paths to the bitmaps accordingly. So something like this to handle PyInstaller and regular Python application:
def app_path():
"""Returns the base application path."""
if hasattr(sys, 'frozen'):
# Handles PyInstaller
return os.path.dirname(sys.executable)
return os.path.dirname(__file__)
Checking for sys.frozen is a really good approach. You can also look into img2py which will let you load the binary data for images into a .py file. Later, instead of having to open files, they can be imported.
a = Analysis(['script.py'],
pathex=['D:\\source-control\\GITHUB\\projectname'],
hiddenimports=[],
hookspath=None,)
a.datas += [( 'images', r'C:\Users\igorl\Pictures\hzgJUXi5l4o.jpg', 'DATA')]
pyz = PYZ(a.pure)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name=os.path.join('dist', 'script.exe'),
debug=False,
strip=None,
upx=True,
console=False )
Related
Mac Big Sur, python 3.9, pyinstaller 4.3.
I've seen this question posted elsewhere, e.g. PyInstaller OS X app runs from command line, but not Finder window, but can't quite understand the proposed solutions. I have a Mac .app created using tkinter and pyinstaller that functions fine at the terminal when I type
./dist/MyApplication.app/Contents/MacOS/MyApplication
However, when I double click on the .app in the Finder, the program icon appears briefly on my computer's dock before disappearing. No error message at all.
Here is my .spec file:
block_cipher = None
a = Analysis(['MyApplication.py'],
pathex=
['/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages',
'/Users/fishbacp/Desktop'],
binaries=[],
datas=[('/Users/fishbacp/Desktop/background.png','.')],
hiddenimports=['_tkinter','PIL'],
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='MyApplication',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='MyApplication')
app =
BUNDLE(coll,name='MyApplication.app',
icon='/Users/fishbacp/Desktop/spectrum.ico',
bundle_identifier=None,
info_plist={'LSEnvironment': {'LANG': 'de_DE.UTF-8',
'LC_CTYPE': 'de_DE.UTF-8'}})
I had same problem (but used one folder mode instead of packaging as an .app), when double click in finder, it will open console and immediately close it.
And my case was due to uncaught exception in my program, which failed to load a file from the folder, and correct the path fixed the problem.
from pathlib import Path
# The file path should be relative to the packaged folder path
file_path = Path(__file__).resolve().with_name("some/path/to/the/file")
It might not be your case but my guess is your program exists due to unexpected condition and that's why it disappears soon, and file path is quite often not handled properly when using PyInstaller.
Hope it can help someone.
I am trying to get Pyinstaller 3.5 to create a "onefile" executable but it keeps generating a "onedir" instead.
My files are all in one directory. There is a main program that imports two other modules which in turn import a third. The program functions properly when run directly in Python 3.7.4. The "onedir" version generated by Pyinstaller also works. I'm running 64 bit Windows 10 Pro on a Surface Book 2.
The command I'm using to generate the file is:
pyinstaller --onefile --windowed --additional-hooks-dir=. qualys_admin.spec
My program uses wx, pubsub, xmltodict, requests, and pandas. The additional hook file is for xlrd which pandas needs to read a xlsx file.
My spec file looks like this:
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
# work-around for https://github.com/pyinstaller/pyinstaller/issues/4064
import distutils
if distutils.distutils_path.endswith('__init__.py'):
distutils.distutils_path = os.path.dirname(distutils.distutils_path)
addedfiles = ('qualys_admin.ini', '.')
a = Analysis(['qualys_admin.py'],
pathex=['C:\\Users\\secops-sw\\Documents\\qualys-
administration\\qualysadmin'],
binaries=[],
datas=[addedfiles],
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='qualys_admin',
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='qualys_admin')
I wrote a tiny "hello world" program to take the complexity of my program off the table. I was able to generate both a "onedir" and a "onefile" successfuly. So I know where to look for the standalone executable.
For my qualys_admin app, that executable is definitely not being generated and I cannot find any warnings to indicate why.
Does anyone have any ideas?
It is not working because your spec file is configured for one directory mode. You need to create the spec file with the one file flag.
pyi-makespec --onefile yourscript.py
Then you can modify your spec sheet and build the app using the custom spec sheet.
I am trying to convert my code into an exe using pyinstaller spec. Pyinstaller initially failed with the following reason:
main__.PyInstallerImportError: Failed to load dynlib/dll
'C:\\Users\\...\\lightgbm\\../compile\\lib_lightgbm.dll'. Most probably this
dynlib/dll was not found when the application was frozen.
I tried to correct it by adding the following line to my pathex list in the spec:
'myenv\\lib\\site-packages\\lightgbm'
Note: myenv is my virtualenv created for this project.
Which led to ImportError for sklearn. I added sklearn to hidden imports. This is my final spec fie:
# -*- mode: python -*-
import sys
sys.setrecursionlimit(5000)
block_cipher = None
a = Analysis(['myscript.py'],
pathex=['C:\\project_folder', 'myenv\\lib\\site-packages\\lightgbm'],
binaries=[],
datas=[('lib_lightgbm.dll', '.')],
hiddenimports=['cython', 'sklearn', 'sklearn.feature_extraction','sklearn.pipeline', 'sklearn.ensemble', 'sklearn.neighbors.typedefs', 'sklearn.neighbors.quad_tree', 'sklearn.tree._utils'],
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='myscript',
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,
name='myscript')
This leads to an error I find very weird:
File "myenv\lib\site-packages\lightgbm\sklearn.py", line 9, in <module>
ImportError: attempted relative import with no known parent package
[12692] Failed to execute script myscript
I have no idea why it would be searching for sklearn.py inside lightgbm?
There is sklearn.py inside lightgbm, but I am not sure how to fix this error. Also, there is no sklearn folder in the dist/myscript folder. Ideally it should have been present. I tried to manually copy the sklearn folder, although, as suspected, it made no difference. Could someone please let me know where the spec file is wrong?
Let's go back to the first error you had. That error suggests lib_lightgbm.dll was not seen when your application was frozen.
Assuming you run pyinstaller from Windows cmd.exe, you can fix this by passing a hook for lightgbm to pyinstaller, so that it knows where to get it from, e.g.
pyinstaller --additional-hooks-dir dir_with_lightgbm_hook --hidden-import lightgbm myscript.py
The name of the hook should be hook-lightgbm.py and for its contents, you can look here: https://github.com/pyinstaller/pyinstaller/blob/develop/PyInstaller/hooks/hook-numpy.py , it should be something similar. Just make sure that the library is added to the binaries list, i.e.
binaries.append((os.path.join(os.path.dirname(
get_module_file_attribute('lightgbm')), "lib_lightgbm.dll"), "lightgbm"))
The second "lightgbm" argument is the location where the .dll will be copied, relative to your distribution directory, so in your case it should end up as dist\myscript\lightgbm\lib_lightgbm.dll.
Note: Adding lib_lightgbm.dll through a hook will make pyinstaller add the dependencies for it to the distribution, so it is better to do it like this than just copying it manually.
Second note: When copying your distribution package to another machine, you might need to install the appropriate Visual Studio redistributable used by the library (e.g. VS 2015 redistributable).
EDIT: Forgot to mention that if you do it like this, you can remove the path you added to your pathex.
I'm trying to distribute the executable of the python code using pyinstaller.
I'm wonder whether there is a way to change directory dynamically depending on the target platform.
For example, if executable is 32bit, target dir is AAAx32, if 64bit, target dir is AAAx64 in my spec file.
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
name='AAA') # <-- Change this dynamically.
Try os and platform
import platform
import os
platform.system()
>'Windows'
platform.machine()
>'AMD64'
For further information, please read this.
So, let's say I got a simple pyqt app main.py:
import sys
from PyQt5 import QtWidgets
def main():
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QWidget()
w.resize(250, 150)
w.move(300, 300)
w.setWindowTitle('Simple')
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
And then i got a main.spec which packages the app in one folder:
# -*- mode: python -*-
block_cipher = None
import inspect, os
current_path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
a = Analysis(['main.py'],
pathex=[current_path],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
exclude_binaries=True,
name='main',
debug=False,
strip=False,
upx=False,
console=True )
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=False,
name='main')
The outcome of doing pyinstaller main.spec will be a working pyqt app which has a mess of files:
So, here's the thing, I don't like the current outcome and I also don't like the option of using --onefile (the idea of extracting files into a temp directory is not my cup of tea).
Now, I've found this interesting article which presents a solution to this problem and I was trying to reproduce it here with this simple mcve but for some reason I've got stuck at certain point. Here's the steps i've followed:
1) I've created a file pyinstaller\use_lib.py:
import sys
import os
sys.path.append(os.path.join(os.path.dirname(sys.argv[0]), "lib"))
2) I've replaced runtime_hooks=[] by runtime_hooks=['.\\pyinstaller\\use_lib.py'] in the above main.spec file
3) I've rerun pyinstaller main.spec, which has generated the bunch of messy files like the above screenshot
4) I've moved manually all dependency files to a lib directory and the results is this:
PROBLEM: When i try to run the app will crash:
Why do you think it's crashing? At which step I've messed up? Could you please explain me how to fix it?
You can't move those dlls away from exe (into another directory). Those dlls are linked statically and should be placed in the same directory as exe.
Anyway. Look into some application folders inside of your C:\Program Files. There are big bunch of files inside each of those directories. It is just the way it goes. And no one cares because users does not look into these folders.
If you want to distribute your application you should act as all other developers. Folder state after using PyInstaller is not the final form of your application but only an initial form: any C/C++ application will start its way to the users from this exact form.
So if you want to distribute your application to the users you should use one of the installer tools. The best form of installation packet for the Windows platform is msi packet (made for "Windows Installer"). To build your msi packet you may use WiX Installer (the simplest way to create msi packets) or MS Visual Studio. Also there is a plenty amount of installation tools which will generate exe form of installation packets (and usually they are much more simple to use than msi-tools): NSIS, Inno Setup, InstallShield (paid!), etc. Also you may search names of this installers through https://pypi.python.org/pypi database: there are some special Python packets which you can use to manage some of this installation tools.