All of the documentation for Pyinstaller talks about including individual files. Is it possible to include a directory, or should I write a function to create the include array by traversing my include directory?
6 Answers
I'm suprised that no one mentioned the official supported option using Tree():
https://stackoverflow.com/a/20677118/2230844
https://pyinstaller.readthedocs.io/en/stable/advanced-topics.html#the-toc-and-tree-classes
- 22,560
- 30
- 103
- 164
- 5,605
- 12
- 71
- 138
-
9Thank you for sharing, using [the Tree class](https://pythonhosted.org/PyInstaller/advanced-topics.html#the-tree-class) solved it for me in an elegant way. Now I just need this one line: `a.datas += Tree('./dir_to_include', prefix='dir_to_include')` – sunyata Sep 21 '17 at 01:23
-
yep, I think it is just not documented well-enough, but this open-source, so PRs are welcome :) – denfromufa Sep 21 '17 at 14:23
Paste the following after a = Analysis() in the spec file to traverse a directory recursively and add all the files in it to the distribution.
##### include mydir in distribution #######
def extra_datas(mydir):
def rec_glob(p, files):
import os
import glob
for d in glob.glob(p):
if os.path.isfile(d):
files.append(d)
rec_glob("%s/*" % d, files)
files = []
rec_glob("%s/*" % mydir, files)
extra_datas = []
for f in files:
extra_datas.append((f, f, 'DATA'))
return extra_datas
###########################################
# append the 'data' dir
a.datas += extra_datas('data')
- 963
- 1
- 7
- 18
-
Excuse me, but I don't really get it. I have a directory called `~/Scripts`. My data is stored in `~/Scripts/Data`. Should I substitute `a.datas += extra_datas('data')` by `a.datas += extra_datas('Data')`? – Exeleration-G Apr 21 '13 at 10:32
-
If your pyInstaller script is also in `Scripts` and you call it with `python mybuildscript.py` from within `Scripts` then yes, you should substitute with `Data`, otherwise use `.` and `..` to navigate the directory tree. – styts Apr 25 '13 at 09:36
The problem is easier than you can imagine
try this:
--add-data="path/to/folder/*;."
hope it helps !!!
- 1,062
- 2
- 8
- 17
-
7This will include all of the files in path/to/folder/, and all files in sub-folders, in the root of your pyinstaller output. However, any sub-folder structure won't be kept - all files will be flattened to the root. If you want to include the files in their same directory structure (but it still won't keep sub-folder structure), you can use `--add-data="path/to/folder/*;path/to/folder/"` – wags1999 May 05 '21 at 14:47
Yes, you can just add directories to the Analysis object and they get copied across.
a = Analysis(['main.py'],
datas = [('test/dir', 'test/dir')],
...)
- 2,503
- 28
- 24
-
At least for pyinstaller 4.2 the datas field need to be added as a tuple. If you want to add multiple folders it needs to be something like: `... datas = [('test/dir', 'test/dir'),('test2/dir', 'test2/dir')] ...` – Joao Antunes Feb 03 '21 at 18:59
What about just using glob?
from glob import glob
datas = []
datas += glob('/path/to/filedir/*')
datas += glob('/path/to/textdir/*.txt')
...
a.datas = datas
- 87,105
- 19
- 159
- 196
The accepted answer works fine if your folder doesn't have any subfolders. However, if you folder does have any subfolders, all data will be collapsed into the your 'dir_to_include' path, which will mess up your imports. The below is an example, where main.py accesses music and pictures from the data folder.
.
├── data
│ ├── music
│ │ ├── music1.mp3
│ ├── pics
│ │ ├── pic1.png
│ │ ├── pic2.png
│ │ ├── numbers
│ │ │ ├── 0.png
│ │ │ ├── 1.png
│ │ │ ├── 2.png
├── main.py
├── main.spec
In this case, after we generated our main.spec file using pyinstaller main.py, we can change some arguents inside the main.spec file.
On the top of the main.spec, our we set our added_files:
# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
added_files = [
('./data/*.*', 'data'),
('./data/pics/*.*', 'data/pics'),
('./data/music/*.*', 'data/music'),
('./data/pics/numbers/*.*', 'data/pics/numbers'),
]
...
Then, we set datas=added_files below:
...
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=added_files,
...
We have to add the path for each individual subfolder, or else they won't get added by pyinstaller automatically. If we simply do added_files = '[(./data/*', data')], then all the pictures and audio will be added to the 'data' directory, which is not what we want. Thus, we use path/*.*, so folders don't get added recursively, and define each subfolder manually.
- 1
- 1