book
Some introduction
The end goal is to be able to make our code installable with package managers such as conda
or pip
without the end user having to worry about compiling code, setting up paths for shared libraries etc.
Packaging Fortran code with f2py
is embarrasingly easy thanks to numpy.distutils
. We can tell setuptools to build Fortran source files by using the numpy.distutils
drop-in replacements for the regular Extension
and setup
.
Let's start with our function that does addition.
We now write a minimal setup.py
that will install our function within a Python package.
We install with pip install .
and can now import into Python:
It does not get easier than this.
:warning: Do not forget to uninstall with
pip uninstall f2py-example
when finished.
ctypes
does not provide any tools for building or distribution.
The most pragmatic solution is to package the shared library directly with the code using the package_data
keyword in setup.py
.
We use the following package structure:
and this setup.py
:
The package_data
keyword will instruct setuptools to copy the lib/mod_add.so
into the built Python package.
We have created the add.py
module that loads mod_add.so
using the pathlib
library and exports the addition function to the module namespace:
After installing with pip install .
, we can import the function from our new Python package.
Shared libraries need to be compiled separately for each platform that you want to support.
One option is to distribute all of them with the package and use sys.platform
to load the right one at runtime. Alternatively, tools like cibuildwheel can be used to set up an automatic build of (1) the shared library and (2) the Python package for multiple platforms.
We start with the following project structure.
The files c_mod_sum.pxd
, mod_sum.pyx
, mod_sum.h
and libmod_sum.so
are the same files that we have worked with previously. Here is the setup.py
:
There are some changes to have we worked previously:
library_dirs
is set to lib
include
directory and link to it via the include_dirs
keyword.sum
, in the cython_example
package.The pyproject.toml
now also lists Cython as a dependency:
With everything set up, we can create a binary wheels file with setuptools:
If we try to install the wheels with
and import the package, we encounter the following error:
The problem is that the shared library is not available to Python at runtime. We could solve it by setting the LD_LIBRARY_PATH
variable before running Python, but that option would not be available to the end user who installs our package from PyPi. Instead, we will patch the wheels.
To package the shared library with the wheel file, we need to use external and platform-specific tools.
OS | Tool |
---|---|
Linux | auditwheel |
macOS | delocate |
Windows | delvewheel (experimental) |
Patching the wheels is a very platform-specific procedure, and we illustrate it here exemplified using delocate
on MacOS.
Guides for how to use the other tools for Linux and Windows can reached through the links in the table above.
First we need to change the install name of our shared library if it was generated with gfortran
:
:bulb: We switched the suffix from
.so
to.dylib
as we are working on MacOS here
We then go into the dist
directory and patch the wheel file with delocate
.
We use the DYLD_LIBRARY_PATH
environment variable to tell delocate
where it can find our shared library.
Now we are ready to install the patched wheels:
The Python package is now ready to use and could be uploaded to PyPi.