简体   繁体   中英

How to include license file in setup.py script?

I have written a Python extension module in C++. I plan to distribute the module with setuptools. There will be binary distributions for 32- and 64-bit Windows (built with setup.py bdist_egg ) and a source distribution for UNIX-like platforms (built with setup.py sdist ).

I plan to license the module under the BSD license. In my source tree, the file LICENSE.txt is in the top folder along with setup.py. How should I include it in the installation package?

I tried the following setup.py script:

from setuptools import setup, Extension
from glob import glob

setup(
    name = 'Foo',
    version = '0.1.0',
    ext_modules = [Extension('Foo', glob('Source/*.cpp'))],
    package_data = {'': ['LICENSE.txt']}
)

It did not work, the license file is not included in the installation package. Maybe because the setup.py file does not define any packages, only a single extension module.

How do I fix this?

Write a setup.cfg file and in there specify:

[metadata]
license_files = LICENSE.txt

For this to work it seems like wheel is required to be installed. That is:

pip install wheel

If you have wheel already installed and it doesn't work, try to update it:

pip install --upgrade wheel

Then when installing the package via pip install <path> the LICENSE file gets included.

Use data_files :

setup(
    name = "Foo",
    version = "0.1.0",
    ext_modules = [Extension("Foo", glob("Source/*.cpp"))],
    data_files = [("", ["LICENSE.txt"])]
)

Two remarks:

  1. There is no direct need to ship a license with your product, you can use the license metadata in distutils to specify this.

  2. Don't use mixed single-quote and double-quotes in your code :)

Since setuptools 42.0.0 you can use the license_files key to specify a list of license files to be included into a distribution. Since version 56.0.0 it supports pattern matching and defaults to ('LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*') .

Note that due to implementation details there's actually no need to put this key into setup.cfg file (as another answer suggests). You could supply it as an argument to setup() function instead:
(documentation was unclear on this at the time of writing)

from setuptools import setup

setup(
    ...
    license_files = ('LICENSE.txt',),
    ...
)

Also note that while these files will be included in both binary (wheel) and source distributions, they won't be installed with your package from setup.py -style source distribution if the user doesn't have a wheel package installed!
To ensure the license files will be installed along with your package you need to make some additional modifications to your setup script:

from setuptools import setup
from setuptools.command.egg_info import egg_info


class egg_info_ex(egg_info):
    """Includes license file into `.egg-info` folder."""

    def run(self):
        # don't duplicate license into `.egg-info` when building a distribution
        if not self.distribution.have_run.get('install', True):
            # `install` command is in progress, copy license
            self.mkpath(self.egg_info)
            self.copy_file('LICENSE.txt', self.egg_info)

        egg_info.run(self)


setup(
    ...
    license_files = ('LICENSE.txt',),
    cmdclass = {'egg_info': egg_info_ex},
    ...
)

If your project is a pyproject.toml -style project and you think it will be installed by PEP 517-compatible frontend (eg pip>=19 ), a wheel will be forcibly built from your sources and the license files will be installed into .dist-info folder automatically.

Since version 61.0.0 you could specify project metadata and other configuration options in pyproject.toml file instead.

Using a METADATA.in file, the license can be included both the source package and wheels automatically:

METADATA.in include README.md include COPYING

Check out an example here: https://github.com/node40/smsh

New setuptools (40.x) allows metadata, including license, to be stored in the setup.cfg's "metadata" section . If you use older setuptools you could provide license using the "license" named argument in your setup():

def read_text(file_name: str):
    return open(os.path.join(base_path, file_name)).read()


setup(
    name = 'Foo',
    version = '0.1.0',
    ext_modules = [Extension('Foo', glob('Source/*.cpp'))],
    # package_data = {'': ['LICENSE.txt']}
    license=read_text("LICENSE.txt")
)

You have to move the LICENSE.txt file into the package directory for your project. It cannot reside the top level. Python directories get deployed, not the deployment artifact. If you create a python package, that package actually contains a number of subpackages. Each subpackage must contain ALL the files relevant to deployment.

Do not use data_files as it will actually distribute the files as a separate package. (I've heard package_files works, but I have yet to see a working example to do this).

For example:

setup(
    ...
    license="ZPL",
    classifiers=[
        ...
        'License :: OSI Approved :: Zope Public License',
        ...
        ],
     ...)

additionally you can insert your licence text into 'long_description':

setup(
    ...
    long_description="Package description. \nLicense Text",
    ...)

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