How to force a platform wheel using build and pyproject.toml?
Question:
I am trying to force a Python3 non-universal wheel I’m building to be a platform wheel, despite not having any native build steps that happen during the distribution-packaging process.
The wheel will include an OS-specific shared library, but that library is built and copied into my package directory by a larger build system that my package knows nothing about. By the time my Python3 package is ready to be built into a wheel, my build system has already built the native shared library and copied it into the package directory.
This SO post details a solution that works for the now-deprecated setup.py
approach, but I’m unsure how to accomplish the same result using the new and now-standard build
/ pyproject.toml
system:
mypackage/
mypackage.py # Uses platform.system and importlib to load the local OS-specific library
pyproject.toml
mysharedlib.so # Or .dylib on macOS, or .dll on Windows
Based on the host OS performing the build, I would like the resulting wheel to be manylinux
, macos
, or windows
.
I build with python3 -m build --wheel
, and that always emits mypackage-0.1-py3-none-any.whl
.
What do I have to change to force the build to emit a platform wheel?
Answers:
OK, after some research and reading of code, I can present a bit of information and a few solutions that might meet other people’s needs, summarized here:
Firstly, pyproject.toml
is not mutually exclusive from setup.py
. setuptools
will complain about deprecation if you create a distribution package via python3 setup.py ...
and no pyproject.toml
file is present.
However, setup.py
is still around and available, but it’s a mistake to duplicate project configuration values (name
, version
, etc). So, put as much as your package will allow inside your pyproject.toml
file, and use setup.py
for things like overriding the Distribution
class, or overriding the bdist_wheel
module, etc.
As far as creating platform wheels, there are a few approaches that work, with pros and cons:
- Override the
bdist_wheel
command class in setup.py
as described here and set self.root_is_pure
to False in the finalize_options
override. This forces the python tag (e.g. cp39
) to be set, along with the platform tag.
- Override the
Distribution
class in setup.py
as described here and override has_ext_modules()
to simply return True. This also forces the python and platform tags to be set.
- Add an unused minimal extension module to your packaging definition, as described here and here. This lengthens the build process and adds a useless "dummy" shared library to be dragged along wherever your wheel goes.
- Add the argument
-C=--build-option=--plat {your-platform-tag}
to the build invocation (for my case that’s python -m build -w -n
, for example). This leaves the Python tag untouched but you have to supply your own tag; there’s no way to say "use whatever the native platform is". You can discover the exact platform tag with the command wheel.bdist_wheel.get_platform(pathlib.Path('.'))
after importing the pathlib
and wheel.bdist_wheel
packages, but that can be cumbersome because wheel
isn’t a standard library package.
- Simply rename your wheel from
mypkg-py3-none-any.whl
to mypkg-py3-none-macosx_13_0_x86_64.whl
– it appears that the platform tag is only encoded into the filename, and not any of the package metadata that’s generated during the distribution-package process.
In the end I chose option #4 because it required the least amount of work- no setup.py
files need to be introduced solely to accomplish this, and the build logs make it clear that a platform wheel (not a pure wheel) is being created.
I am trying to force a Python3 non-universal wheel I’m building to be a platform wheel, despite not having any native build steps that happen during the distribution-packaging process.
The wheel will include an OS-specific shared library, but that library is built and copied into my package directory by a larger build system that my package knows nothing about. By the time my Python3 package is ready to be built into a wheel, my build system has already built the native shared library and copied it into the package directory.
This SO post details a solution that works for the now-deprecated setup.py
approach, but I’m unsure how to accomplish the same result using the new and now-standard build
/ pyproject.toml
system:
mypackage/
mypackage.py # Uses platform.system and importlib to load the local OS-specific library
pyproject.toml
mysharedlib.so # Or .dylib on macOS, or .dll on Windows
Based on the host OS performing the build, I would like the resulting wheel to be manylinux
, macos
, or windows
.
I build with python3 -m build --wheel
, and that always emits mypackage-0.1-py3-none-any.whl
.
What do I have to change to force the build to emit a platform wheel?
OK, after some research and reading of code, I can present a bit of information and a few solutions that might meet other people’s needs, summarized here:
Firstly, pyproject.toml
is not mutually exclusive from setup.py
. setuptools
will complain about deprecation if you create a distribution package via python3 setup.py ...
and no pyproject.toml
file is present.
However, setup.py
is still around and available, but it’s a mistake to duplicate project configuration values (name
, version
, etc). So, put as much as your package will allow inside your pyproject.toml
file, and use setup.py
for things like overriding the Distribution
class, or overriding the bdist_wheel
module, etc.
As far as creating platform wheels, there are a few approaches that work, with pros and cons:
- Override the
bdist_wheel
command class insetup.py
as described here and setself.root_is_pure
to False in thefinalize_options
override. This forces the python tag (e.g.cp39
) to be set, along with the platform tag. - Override the
Distribution
class insetup.py
as described here and overridehas_ext_modules()
to simply return True. This also forces the python and platform tags to be set. - Add an unused minimal extension module to your packaging definition, as described here and here. This lengthens the build process and adds a useless "dummy" shared library to be dragged along wherever your wheel goes.
- Add the argument
-C=--build-option=--plat {your-platform-tag}
to the build invocation (for my case that’spython -m build -w -n
, for example). This leaves the Python tag untouched but you have to supply your own tag; there’s no way to say "use whatever the native platform is". You can discover the exact platform tag with the commandwheel.bdist_wheel.get_platform(pathlib.Path('.'))
after importing thepathlib
andwheel.bdist_wheel
packages, but that can be cumbersome becausewheel
isn’t a standard library package. - Simply rename your wheel from
mypkg-py3-none-any.whl
tomypkg-py3-none-macosx_13_0_x86_64.whl
– it appears that the platform tag is only encoded into the filename, and not any of the package metadata that’s generated during the distribution-package process.
In the end I chose option #4 because it required the least amount of work- no setup.py
files need to be introduced solely to accomplish this, and the build logs make it clear that a platform wheel (not a pure wheel) is being created.