54

I'm struggling with pyinstaller. Whenever I build this specific script with a kivy GUI and a .kv file, and run the .exe after the build, I get a fatal error:

IOError: [Errno 2] No such file or directory: 'main.kv'

I've tried adding the .kv file, as well as a mdb and dsn file (for pypyodbc) using --add-data, but I get an error: unrecognized arguments: --add-data'main.kv'. (There were more --add-data arguments for the other files mentioned.)

Are there any solutions for this or maybe alternative methods?

badams
  • 98
  • 1
  • 9
staos2
  • 645
  • 1
  • 5
  • 7

5 Answers5

97

As others (@Anson Chan, @schlimmchen) have said:

If you want to add some extra files, you should use Adding Data Files.

Two ways to implement

  • Command Line: add parameter to --add-data
  • Spec file: add parameter to datas=
    • Generated when running pyinstaller the first time.
      • Then later you can edit your *.spec file.
      • Then running pyinstaller will directly use your *.spec file.

Parameter Logic

Parameter in --add-data or datas=:

  • --add-data:
    • format: {source}{os_separator}{destination}
      • os_separator:
        • Windows: ;
        • Mac/Linux/Unix: :
      • source and destination
        • Logic:
          • source: path to single or multiple files, supporting glob syntax. Tells PyInstaller where to find the file(s).
          • destination file or files: destination folder which will contain your source files at run time. * NOTE: NOT the destination file name.
            • folder: destination folder path, which is RELATIVE to the destination root, NOT an absolute path.
    • Examples:
      • Single file: 'src/README.txt:.'
      • multiple files: '/mygame/sfx/*.mp3:sfx'
      • folder: '/mygame/data:data'
  • datas=
    • Format: list or tuple.
    • Examples: see the following.
added_files = [
    ( 'src/README.txt', '.' ),
    ( '/mygame/data', 'data' ),
    ( '/mygame/sfx/*.mp3', 'sfx' )
]

a = Analysis(...
    datas = added_files,
    ...
)

Your case

For your (Windows OS) here is:

  • --add-data in command line
    • pyinstaller -F --add-data "main.kv;." yourtarget.py

OR:

  • datas= in yourtarget.spec file, see following:
a = Analysis(...
    datas = ["main.kv", "."],
    ...
)
Saikat
  • 14,222
  • 20
  • 104
  • 125
crifan
  • 12,947
  • 1
  • 71
  • 56
  • 7
    This is a great answer showing very clearly the equivalencies between the command line parameters and the spec file equivalents. The examples are great. Well done – bearcat Sep 07 '20 at 03:11
  • If it helps anybody else, in my case I had a chromedriver.exe stored in a resources folder. My command --add-data line was thus: `--add-data 'src\resources\chromedriver.exe;resources.'` This then added a resource folder to the temporary MEIPASS folder, mimicking my actual directory. – Nick08 Feb 28 '22 at 23:14
61

If you check pyinstaller -h for help, you can find --add-data option works like this [--add-data <SRC;DEST or SRC:DEST>]. So in your case try

pyinstaller -F --add-data "main.kv;main.kv" yourtarget.py
schlimmchen
  • 210
  • 1
  • 3
  • 7
Anson Chan
  • 635
  • 5
  • 5
  • 38
    Whether to use a semicolon or colon as the separator depends on `os.pathsep`, i.e., on most *nix systems this is a colon and on Windows this should be a semicolon. – schlimmchen Oct 09 '17 at 13:14
  • 1
    with respect to the output dist/ folder, where did main.kv end up? – bw4sz Apr 11 '18 at 16:13
  • 1
    man pyinstaller docs could make that more clear ... all the examples i found used `:` ... usually i just do it in the spec file but i really didnt want a spec file for this project – Joran Beasley May 17 '18 at 00:08
  • 11
    The above will add a folder in the distribution path with main.kv inside of it like so: `distfolder/main/main.kv`. To fix this the command should read: `pyinstaller -F --add-data "main.kv;." yourtarget.py` – Spencer Nov 07 '19 at 22:37
  • 1
    the comment from @Spencer is an essential part of this answer in my opinion, the answer in its current form is a bit misleading – Mark Ch Jul 08 '21 at 15:35
9

The solution is to run: pyi-makespec yourscript.py Then edit the yourscript.spec script and add the files under datas in a= Analysis.

datas=[ ( '/pathToYourFile/main.kv', '.' )]

then run pyinstaller yourscript.spec should be good after that.

kaminsknator
  • 1,135
  • 3
  • 15
  • 26
  • @HarshitAgrawal Add comma then: datas=[ ( '/pathToYourFile/main.kv', '.' )], – Pavel M. Oct 31 '18 at 00:40
  • @pmus I did tried with using commas too but didnt worked and showed the same error when I run the .exe file "too many values to unpack" error. – HarshitMadhav Nov 03 '18 at 11:18
  • 1
    For future users, if you get 'too many values to unpack' make sure you are adding your path to the list as a tuple in the form `('source\dir', 'destination\dir')`. I missed this initially when reading the answer and got the too many values error. – Brandon Barney Jan 02 '19 at 14:40
  • I was experiencing the opposite of @BrandonBarney - Too few values to unpack - after adding my own image files to the package. After looking at the contents of `a.datas` from my autogenerated `.spec` file, I found that each tuple had an additional string `'DATA'`... so I had to use: ```a.datas += [('assets/icon.ico', 'assets/icon.ico', 'DATA')]``` This doesn't appear to be up-to-date in the docs. – tdpu Sep 07 '21 at 23:31
  • Within the spec file, what can one add that's equivalent to the --onefile option at the command line? – fishbacp Feb 08 '22 at 16:23
3

Next -F or --onefile option is assumed when running pyinstaller.

  1. Note that (MacOS Monterey, 12.2 here) the expected folder hierarchy w/in you .app file will be similar to this,

expected_folder_hierarchy

pyinstaller does not add files nor create necessary folders into any of the folders of this folder structure; at least not in any apparent way. You won't find them.

  1. However, when the application runs, a temporary folder is used under /var/folders which is very different from the folder structure in point 1. above. print(os.path.dirname(__file__)) while running the application will reveal which exact temporary folder is used each time it runs. For convenience, let's call it my_app_tmp_folder i.e. your app runs under the folder /var/folder/my_app_tmp_folder

Then, pyinstaller adds data files or creates necessary directories w/in this temporary folder. In other words, when the application runs, all added files will be there and according to the specified folder structure (through --add-data option). print(os.listdir(os.path.dirname(__file__))) will show system and application needed files and folders.

Bottom line: Files specified w/ --add-data option will be visible w/in /var/folder/my_app_tmp_folder when running and not w/in the *.app folder.

Some useful links from documentation:

https://pyinstaller.readthedocs.io/en/stable/runtime-information.html#using-file

https://pyinstaller.readthedocs.io/en/stable/spec-files.html#adding-files-to-the-bundle

https://pyinstaller.readthedocs.io/en/stable/operating-mode.html#bundling-to-one-file

vpap
  • 1,389
  • 2
  • 21
  • 32
-1

My application had this issue and a subsequent issue that is likely, if not inevitable.

1. --add-data for a kv file

Use --add-data as in the answer by crifan.

2. Kivy still can't find the file

Once PyInstaller has the kv file in the correct directory, Kivy still can't find the file.

Possible Symptoms:

  • GUI launches, but screen is black and empty.
  • An AttributeError error that depends on the application code.

AttributeError Examples:

  • This question

  • My own case:

    AttributeError: 'NoneType' object has no attribute 'ids'

Fortunately, this answer solves the problem.

badams
  • 98
  • 1
  • 9