Linux | macOS | Windows |
---|---|---|
To be compliant with PEP513 a python package should embbed all its C++ shared libraries.
Creating a Python native package containing all .py
and .so
(with good
rpath/loaderpath) is not so easy...
To build the Python wheel package, simply run:
cmake -S. -Bbuild -DBUILD_PYTHON=ON
cmake --build build --target python_package -v
note: Since python_package
is in target all
, you can also omit the
--target
option.
To list tests:
cd build
ctest -N
To only run Python tests:
cd build
ctest -R "python_.*"
First you should take a look at the
ortools/python/README.md to understand the
layout.
Here I will only focus on the CMake/SWIG tips and tricks.
Since Python use the directory name where __init__.py
file is located and we
want to use the
CMAKE_BINARY_DIR
to generate the Python binary package.
We want this layout:
<CMAKE_BINARY_DIR>/python
├── setup.py
└── ortools
├── __init__.py
├── algorithms
│ ├── __init__.py
│ ├── _pywrap.so
│ └── pywrap.py
├── graph
│ ├── __init__.py
│ ├── pywrapgraph.py
│ └── _pywrapgraph.so
├── ...
└── .libs
├── libXXX.so.1.0
└── libXXX.so.1.0
src: tree build --prune -U -P "*.py|*.so*" -I "build"
note: On UNIX you always need $ORIGIN/../../${PROJECT_NAME}/.libs
since
_pywrapXXX.so
will depend on libortools.so
.
note: On APPLE you always need
"@loader_path;@loader_path/../../${PROJECT_NAME}/.libs
since _pywrapXXX.so
will depend on libortools.dylib
.
note: on Windows since we are using static libraries we won't have the .libs
directory...
So we also need to create few __init__.py
files to be able to use the build
directory to generate the Python package.
Actually, the cpython code responsible for loading native libraries expect .so
on all UNIX platforms.
const char *_PyImport_DynLoadFiletab[] = {
#ifdef __CYGWIN__
".dll",
#else /* !__CYGWIN__ */
"." SOABI ".so",
#ifdef ALT_SOABI
"." ALT_SOABI ".so",
#endif
".abi" PYTHON_ABI_STRING ".so",
".so",
#endif /* __CYGWIN__ */
NULL,
};
ref: https://github.com/python/cpython/blob/main/Python/dynload_shlib.c#L37-L49
i.e. pywrapXXX
-> _pywrapXXX.so
-> libortools.dylib
To avoid to put hardcoded path to SWIG .so/.dylib
generated files, we could
use $<TARGET_FILE_NAME:tgt>
to retrieve the file (and also deal with
Mac/Windows suffix, and target dependencies).
In order for setup.py
to use
cmake generator expression
(e.g. $<TARGET_FILE_NAME:_pyFoo>
). We need to generate it at build time (e.g.
using
add_custom_command()).
note: This will also add automatically a dependency between the command and the TARGET !
todo(mizux): try to use
file(GENERATE ...)
instead.