python-3.xsetuptools

How to add platform-specific package data in setup.py?


I would need to deploy platform-specific package data in the wheels built with setup.py

For a bit of background, I am trying to create Python bindings for a library using pre-built binaries and ctypesgen. I have a data tree with platform-specific sub-directories that contain a bindings file and a binary each:

data/
    macos-arm64/
        _bindings.py
        binary.dylib
    linux-x64/
        _bindings.py
        binary.so
    windows-x64/
        _bindings.py
        binary.dll
    ...

And I have a source tree:

src/
    package_name/
        __init__.py
        __main__.py
        ...

Question: How can I build platform-specific packages that only contain the one corresponding binary and bindings?

The final directory tree as deployed to the end-user should structurally look like this:

package_name/
    __init__.py
    __main__.py
    _bindings.py
    binary.xyz
    ...

Solution

  • This is the solution I am currently using for pypdfium2:

    class PlatformNames:
        darwin_x64  = "darwin_x64"
        linux_x64   = "linux_x64"
        windows_x64 = "windows_x64"
        # ...
        sourcebuild = "sourcebuild"
    
    # A list of non-python file names to consider for inclusion in the installation, e. g.
    Libnames = (
        "somelib.so",
        "somelib.dll",
        "somelib.dylib",
    )
    
    # _clean() removes possible old binaries/bindings
    # _copy_bindings() copies the new stuff into the source tree
    # _get_bdist() returns a custom `wheel.bdist_wheel` subclass with the `get_tag()` and `finalize_options()` functions overridden so as to tag the wheels according to their target platform.
    
    def mkwheel(pl_name):
        _clean()
        _copy_bindings(pl_name)
        setuptools.setup(
            package_data = {"": Libnames},
            cmdclass = {"bdist_wheel": _get_bdist(pl_name)},
            # ...
        )
        # not cleaning up afterwards so that editable installs work (`pip3 install -e .`)
    

    → If all goes well, the platform-specific wheels and a source distribution will be written into dist/.

    Perhaps this is a lot easier to understand just by looking at pypdfium2's code (especially setup.py, setup_base.py and craft_packages.py).

    Disclaimer: I am not experienced with the setup infrastructure of Python and merely wrote this code out of need. I acknowledge that the approach is a bit "hacky". If there is a possibility to achieve the same goal while using the setuptools API in a more official sort of way, I'd be interested to hear about it.

    Update 1: A negative implication of this concept is that the content wrongly ends up in a purelib folder, although it should be platlib as per PEP 427. I'm not sure how to instruct wheel/setuptools differently.

    Update 2: Found a fix to the purelib problem:

    class BinaryDistribution (setuptools.Distribution):
        def has_ext_modules(self):
            return True
    
    setuptools.setup(
        # ...
        distclass = BinaryDistribution,
    )