How do control what files are included in a wheel? It appears MANIFEST.in
isn\'t used by python setup.py bdist_wheel
.
UPDATE
I had config/ directory with JSON files in it, which I needed to add to the wheel package. So, I've added these lines to MANIFEST.in
:
recursive-include config/ *.json
The following directive to setup.py
:
setup(
...
include_package_data=True,
)
And nothing worked.
Until I've created an empty file called __init__.py
inside config/
directory.
(Python 3.6.7, wheel 3.6.7, setuptools 39.0.1)
Before you make any changes in MANIFEST.in
or setup.py
you must remove old output directories. Setuptools is caching some of the data and this can lead to unexpected results.
rm -rf build *.egg-info
If you don't do this, expect nothing to work correctly.
Now that is out of the way.
If you are building a source distribution (sdist
) then you can use any method below.
If you are building a wheel (bdist_wheel
), then include_package_data
and MANIFEST.in
are ignored and you must use package_data
and data_files
.
This is a good option, but bdist_wheel
does not honor it.
setup(
...
include_package_data=True
)
# MANIFEST.in
include package/data.json
This is most flexible option because you can add any file from your repo to a sdist
or bdist_wheel
setup(
....
data_files=[
('output_dir',['conf/data.json']),
]
# For sdist, output_dir is ignored!
#
# For bdist_wheel, data.json from conf dir in root of your repo
# and stored at `output_dir/` inside of the sdist package.
)
Similar to above, but for a bdist_wheel
let's you put your data files inside of the package. It is identical for sdist
but has more limitations than data_files
because files can only source from your package subdir.
setup(
...
package_data={'package':'data.json'},
# data.json must be inside of your actual package
)
include_package_data
is the way to go, and it works for sdist and wheels.
However you have to do it right, and it took me months to figure this out, so here is what I learned.
The trick is essentially given in the name of the option include_PACKAGE_data
: The data files need to be in a package subfolder
If and only if
include_package_data
is TrueMANIFEST.in
(*see also my note at the end about setuptools_scm
)then the data files will be included.
Given the project has the following structure and files:
|- MANIFEST.in
|- setup.cfg
|- setup.py
|
\---foo
|- __init__.py
|
\---data
- example.png
And the following configuration:
Manifest.in:
recursive-include foo/data *
setup.py
import setuptools
setuptools.setup()
setup.cfg
[metadata]
name = wheel-data-files-example
url = www.example.com
maintainer = None
maintainer_email = none@example.com
[options]
packages =
foo
include_package_data = True
sdist packages and your wheels you build will contain the example.png
datafile as well.
(of course, instead of setup.cfg the config can also be directly specified in setup.py. But this is not relevant for the example.)
This should also work for projects that use a src layout, looking like this:
|- MANIFEST.in
|- setup.cfg
|- setup.py
|
\---src
|
\---foo
|- __init__.py
|
\---data
- example.png
To make it work, tell setuptools about the src directory using package_dir
:
setup.cfg
[metadata]
name = wheel-data-files-example
url = www.example.com
maintainer = None
maintainer_email = none@example.com
[options]
packages =
foo
include_package_data = True
package_dir =
=src
And in the manifest adjust the path:
Manifest.in:
recursive-include src/foo/data *
setuptools_scm
If you happen to use setuptools and add the setuptools_scm
plugin (on pypi), then you don't need to manage a Manifest.in file. Instead setuptools_scm will take care that all files that are tracked by git are added in the package.
So for this case the rule for if or if not a file is added to the sdist/wheel is: If and only if
include_package_data
is Truethen the data files will be included.
You can specify extra files to install using the data_files directive. Is that what you're looking for? Here's a small example:
from setuptools import setup
from glob import glob
setup(
name='extra',
version='0.0.1',
py_modules=['extra'],
data_files=[
('images', glob('assets/*.png')),
],
)
Have you tried using package_data
in your setup.py
? MANIFEST.in
seems targetted for python versions <= 2.6, I'm not sure if higher versions even look at it.
After exploring https://github.com/pypa/sampleproject, their MANIFEST.in
says:
# If using Python 2.6 or less, then have to include package data, even though
# it's already declared in setup.py
include sample/*.dat
which seems to imply this method is outdated. Meanwhile, in setup.py
they declare:
setup(
name='sample',
...
# If there are data files included in your packages that need to be
# installed, specify them here. If using Python 2.6 or less, then these
# have to be included in MANIFEST.in as well.
package_data={
'sample': ['package_data.dat'],
},
...
)
(I'm not sure why they chose a wildcard in MANIFEST.in
and a filename in setup.py
. They refer to the same file)
Which, along with being simpler, again seems to imply that the package_data
route is superior to the MANIFEST.in
method. Well, unless you have to support 2.6 that is, in which case my prayers go out to you.
You can use package_data
and data_files
in setup.py
to specify additional files, but they are ridiculously hard to get right (and buggy).
An alternative is to use MANIFEST.in
and add include_package_data=True
in setup()
of your setup.py
as indicated here.
With this directive, the MANIFEST.in
will be used to specify the files to include not only in source tarball/zip, but also in wheel and win32 installer. This also works with any python version (i tested on a project from py2.6 to py3.6).
UPDATE 2020: it seems the MANIFEST.in is not honored anymore by the wheel in Python 3, although it still is in the tar.gz, even if you set include_package_data=True
.
Here is how to fix that: you need to specify both include_package_data
and packages
.
If your Python module is inside a folder "pymod", here's the adequate setup:
setup( ...
include_package_data = True,
packages = ['pymod'],
)
If your python scripts are at the root, use:
setup( ...
include_package_data = True,
packages = ['.'],
)
Then you can open your .whl file with a zip archival software such as 7-zip to check that all the files you want are indeed inside.