简体   繁体   中英

Pyinstaller onefile doesn't find data files

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 :

  1. Pass the option --add-files and the files as parameters when running pyinstaller <yourscript.py> from your script directory

  2. 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:

  1. In your app root directory and . venv/bin/activate . 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
  2. 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.

  3. 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 .

Using an application like AutoPyToExe can solve most of the challenges you have with PyInstaller. The Interface allows you to address most of the challenges you have listed here. See attached file on how hidden files, data files, clean build and much more by simply selecting the location of the files instead of having to move them around.

在此处输入图片说明

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM