I too am trying for the first time to build a simple --onefile exe which includes data files, but Pyinstaller doesn't seem to find them when building the .exe. A --onedir build seems to work fine.
I'm using the --debug switch at this point as well. I am able to run the onefile executable and can see that it appears to start working. The program finds the (sys._MEIPASS) temp directory ok (prints the needed directory name as directed) but reports a "no such file or directory" error when it looks for the first data file from the temp directory. I used archiveviewer.py on the .exe and DIDN'T find the needed data files there-- which seems to be the problem, but I can't figure out why. Data files for the build are in the directory the spec file describes. My complete spec file is
# -*- mode: python -*-
a = Analysis(['develop6.py'],
pathex=['C:\\PYINST20'],
hiddenimports=[],
hookspath=None)
a.datas += [ ('conlist.txt', 'C:\\pyinst20\\conlist.txt', 'DATA'), ('imageblank.gif', 'C:\\pyinst20\\imageblank.gif', 'DATA')]
pyz = PYZ(a.pure)
exe = EXE(pyz,
a.scripts,
a.binaries,
a.zipfiles,
a.datas,
name=os.path.join('dist', 'develop6.exe'),
debug=True,
strip=None,
upx=True,
console=True )
(this question is old but a it is one of the only sources I found to solve the same problem, I will share my solution here in case it could help someone)
There is two main things to do to add data files to your script in --onefile mode.
1. Adapt the paths
In your script, adapt your paths to find the datafiles in the bundle.
According to PyInstaller documentation here,the executable is launched from a temporary file, so your path must take care of this dynamic part :
For a file with the following relative path :
./your/file/is/here.ext
The code will be :
import sys
wd = sys._MEIPASS
file_path = os.path.join(wd,<your>,<file>,<is>,<here>)
Note : to make your code also work on other contexts, you can do the following :
import sys
import os
try:
wd = sys._MEIPASS
except AttributeError:
wd = os.getcwd()
file_path = os.path.join(wd,<your>,<file>,<is>,<here>)
2. Add the data files paths in the PyInstaller specs
According to PyInstaller documentation here, there is two ways to add data files to the bundle :
Pass the option --add-files
and the files as parameters when running pyinstaller <yourscript.py>
from your script directory
First generate a spec file by navigating to your script directory and running pyi-makespec <yourscript.py>
, then add your files to the list of tuples data=[]
.
The tuples contains the actual path to the file and the path within
your bundle. If you followed the first point, this should look like
datas = [('/your/file/is/here.ext','/your/file/is/')]
Then run PyInstall <yourscript.spec>
to build the bundle, based on your specs file.
After going back and forward between confusing stack overflow threads and the Pyinstaller documentation I was able to finally bundle my app.
Step by step:
In your app root directory and . venv/bin/activate
d:
➜ ~ pip install pyinstaller
➜ ~ pyi-makespec --windowed --onedir --i ./resources/img/icn.icns \
--osx-bundle-identifier "com.myname.macOS.myappname" app.py
Now, modify your app.spec
just a tiny bit:
added_files = [
('resources/img', 'resources/img'),
( 'README.md', '.' )
]
initialize a dictionary added_files
with the relative path to your resources and set datas = added_files
. In my application I used images located at ./resources/img
relative to my main.py file.
And to finilize, this is perhaps the easiest to forget step and not-so obvious:
➜ ~ pyinstaller --onefile app.spec
Notice this last step is taken from here.
I believe I found the problem, and it isn't related to the body of the spec file. Looks like my command line syntax was wrong when running pyinstaller with the spec file. When run properly:
pyinstaller.py [options] <my_specfile.spec>
Appears to work.
We can follow the document to set different file paths when the script is running as a bundle or not. However, my work-around is to set up a stable top level directory where all non-script files live, and share this top level directory with all modules. Then within each module, I can create file path based on the top level directory.
For instance, in this layout, all the module code live in src
folder. These modules require access to conf
folder, database
folder, and otherconfig.yaml
.
├── conf
│ └── config.ini
├── database
├── entry_point.py
├── otherconfig.yaml
├── root_path.py
└── src
├── pkg1
└── pkg2
root_path.py
would look like this:
import os
path = os.getcwd()
In each module, I can create the file path for the config files like this:
import root_path
config_path = f"{root_path.path}/conf/config.ini"
otherconfig_path = f"{root_path.path}/otherconfig.yaml"
Then, after creating a one-file executable via pyinstaller -F entry_point.py
(without adding any files), we just need to recreate the same layout, and the file paths in each module would still work.
├── bundled_executable
├── conf
│ └── config.ini
├── database
└── otherconfig.yaml
Of course, this work-around cannot hide the config files. But for my use case, I need to give users freedom to modify the config files, so not hiding the files actually work for me.