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
...
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"
setuptools.setup() with a function that takes the platform name as argument and copies platform-dependent files into the source tree as required:# 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 .`)
setup.py, query for a custom environment variable defining the target platform (e. g. $PYP_TARGET_PLATFORM).
sdist), run the raw setuptools.setup() function without copying in any build artifacts.sysconfig.get_platform() and call mkwheel() with the corresponding PlatformNames member.
data/sourcebuild/ and runs mkwheel(PlatformNames.sourcebuild).python3 -m build --no-isolation --skip-dependency-check --wheel for each. Also invoke build once with --sdist instead of --wheel and the environment variable set to the value for source distribution.ā 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,
)