I\'m trying to build a one-file EXE with PyInstaller which is to include an image and an icon. I cannot for the life of me get it to work with --onefile
.
I found the existing answers confusing, and took a long time to work out where the problem is. Here's a compilation of everything I found.
When I run my app, I get an error Failed to execute script foo
(if foo.py
is the main file). To troubleshoot this, don't run PyInstaller with --noconsole
(or edit main.spec
to change console=False
=> console=True
). With this, run the executable from a command-line, and you'll see the failure.
The first thing to check is that it's packaging up your extra files correctly. You should add tuples like ('x', 'x')
if you want the folder x
to be included.
After it crashes, don't click OK. If you're on Windows, you can use Search Everything. Look for one of your files (eg. sword.png
). You should find the temporary path where it unpacked the files (eg. C:\Users\ashes999\AppData\Local\Temp\_MEI157682\images\sword.png
). You can browse this directory and make sure it included everything. If you can't find it this way, look for something like main.exe.manifest
(Windows) or python35.dll
(if you're using Python 3.5).
If the installer includes everything, the next likely problem is file I/O: your Python code is looking in the executable's directory, instead of the temp directory, for files.
To fix that, any of the answers on this question work. Personally, I found a mixture of them all to work: change directory conditionally first thing in your main entry-point file, and everything else works as-is:
if hasattr(sys, '_MEIPASS'):
os.chdir(sys._MEIPASS)
pyinstaller unpacks your data into a temporary folder, and stores this directory path in the _MEIPASS2
environment variable. To get the _MEIPASS2
dir in packed-mode and use the local directory in unpacked (development) mode, I use this:
def resource_path(relative):
return os.path.join(
os.environ.get(
"_MEIPASS2",
os.path.abspath(".")
),
relative
)
Output:
# in development
>>> resource_path("app_icon.ico")
"/home/shish/src/my_app/app_icon.ico"
# in production
>>> resource_path("app_icon.ico")
"/tmp/_MEI34121/app_icon.ico"
If you are still trying to put files relative to your executable instead of in the temp directory, you need to copy it yourself. This is how I ended up getting it done.
https://stackoverflow.com/a/59415662/999943
You add a step in the spec file that does a filesystem copy to the DISTPATH variable.
Hope that helps.
Slight modification to the accepted answer.
def resource_path(relative_path):
""" Get absolute path to resource, works for dev and for PyInstaller """
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.join(os.path.abspath("."), relative_path)
The most common complaint/question I've seen wrt PyInstaller is "my code can't find a data file which I definitely included in the bundle, where is it?", and it isn't easy to see what/where your code is searching because the extracted code is in a temp location and is removed when it exits. Add this bit of code to see what's included in your onefile and where it is, using @Jonathon Reinhart's resource_path()
for root, dirs, files in os.walk(resource_path("")):
print(root)
for file in files:
print( " ",file)
Perhaps i missed a step or did something wrong but the methods which are above, didn't bundle data files with PyInstaller into one exe file. Let me share the steps what i have done.
step:Write one of the above methods into your py file with importing sys and os modules. I tried both of them. The last one is:
def resource_path(relative_path):
""" Get absolute path to resource, works for dev and for PyInstaller """
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, relative_path)
step: Write, pyi-makespec file.py, to the console, to create a file.spec file.
step: Open, file.spec with Notepad++ to add the data files like below:
a = Analysis(['C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.py'],
pathex=['C:\\Users\\TCK\\Desktop\\Projeler'],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher)
#Add the file like the below example
a.datas += [('Converter-GUI.ico', 'C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.ico', 'DATA')]
pyz = PYZ(a.pure, a.zipped_data,
cipher=block_cipher)
exe = EXE(pyz,
a.scripts,
exclude_binaries=True,
name='Converter-GUI',
debug=False,
strip=False,
upx=True,
#Turn the console option False if you don't want to see the console while executing the program.
console=False,
#Add an icon to the program.
icon='C:\\Users\\TCK\\Desktop\\Projeler\\Converter-GUI.ico')
coll = COLLECT(exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
name='Converter-GUI')
step: I followed the above steps, then saved the spec file. At last opened the console and write, pyinstaller file.spec (in my case, file=Converter-GUI).
Conclusion: There's still more than one file in the dist folder.
Note: I'm using Python 3.5.
EDIT: Finally it works with Jonathan Reinhart's method.
step: Add the below codes to your python file with importing sys and os.
def resource_path(relative_path):
""" Get absolute path to resource, works for dev and for PyInstaller """
base_path = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__)))
return os.path.join(base_path, relative_path)
step: Call the above function with adding the path of your file:
image_path = resource_path("Converter-GUI.ico")
step: Write the above variable that call the function to where your codes need the path. In my case it's:
self.window.iconbitmap(image_path)
step: Open the console in the same directory of your python file, write the codes like below:
pyinstaller --onefile your_file.py
step: Save and exit the path file. Go to your folder which include the spec and py file. Open again the console window and type the below command:
pyinstaller your_file.spec
After the 6. step your one file is ready to use.