Produce Python packages for multiple versions of Python 3 (Major change)
The Python packages ('wheels') are produced automatically for a given set of Python 3 versions and stored as artifacts.
-
An option
BORNAGAIN_PYTHON_PACKAGE
is introduced in the build scripts to enable building of BornAgain Python packages ('wheels') for arbitrary versions of Python 3. Currently, Python 3.7–3.9 is supported. The required versions are stored inBORNAGAIN_PYTHON_PACKAGE_VERSIONS
.
The root path of the Python platforms are stored in the cached string variableBORNAGAIN_PYTHON_PLATFORMS_PATH
. Different versions of Python are expected to be found in sub-directories namedPython37
,Python38
, etc. -
The required configuration and setup files for building a Python package are stored in
Wrap/PythonPackage
folder. The wheel configuration follows the latest Python standards, PEP 518. An empty C module is added to enforce Pythonsetuptools
to add the proper tags to the wheel file for each platform according to PEP 425. This tag is essential for the PyPI repository and the automatic installation mechanism viapip
. -
The SWIG-produced C++ and Python wrappers are modified so that the wrapper be configurable via CMake to produce the shared libraries for multiple versions of Python. For instance, the produced libraries will have the name
_libBornAgain*_py38
for Python 3.8.- In the C++ wrappers,
_libBornAgain*
is replaced by@_libBornAgain*_PYTAG@
. - In the Python wrappers,
import _libBornAgain*
is replaced byimport @_libBornAgain*_PYTAG@ as _libBornAgain*
.
- In the C++ wrappers,
-
For each version of Python, some build directories are defined in
multipython/PyDirectories
module to produce the packages. The structure of the output subdirectory for a specific version, say Python 3.8, is:python_packages | +--py38 |...{Python setup config files} +--src | +--bornagain |...{Python init files} +--lib |...{BornAgain libraries and their Python API} +--extra |...{Extra dependencies} +--wrap |...{cpp wrappers to produce the libraries}
-
multipython/FindCustomPython3
module (find_custom_python3
function) is introduced to find a given Python3 platform in a custom non-standard folder. The resulting variables are suffixed with a given ‘Python tag’; e.g.,Python3_FOUND_py37
orPython3_NumPy_FOUND_py37
for Python 3.7. Note that the Python tag is thepy_version_nodot
defined in PEP 425. -
For each required version of Python, the development platform is found in
Dependences
module via calls tofind_custom_python3
. The main version of Python installed on the system is automatically added to the required versions. -
multipython/MakeSharedLib
module (make_shared_lib
function) is introduced to define a BornAgain shared library for a given Python version with the necessary compile and link flags and dependencies. The 'main' version of the library corresponds to the library which will be installed via CMakeinstall
.- Linux:
The
RPATH
flag is set for BornAgain shared libraries under Linux so that they could find their dependencies in$ORIGIN/extra
directory. This is needed for the self-contained Python packages, so that the required external libraries (like GSL) could be packed within the Python package. In this way, the Python user does not need to care about such dependencies. - Windows:
Under Windows such a flag does not exist. The libraries are found via searching the directories in
PATH
environmental variable. In Python >= 3.8,os.add_dll_directory
is used to set this path.
- Linux:
The
-
MakeSharedLib
usesmultipython/MakeSWIGLib
module (make_SWIG_lib
function) to define a SWIG API for a shared library with a given Python version. Python-related properties of the libraries are set inmake_SWIG_lib
.make_SWIG_lib
is an internal function, called only from the functionmake_shared_lib
.
An extra internal function_ConfigureSWIG
is used to update and modify the SWIG API, whenever needed. The proper changes to the SWIG-produced C++ and Python wrappers are:- C++ wrappers:
_libBornAgain*
=>@_libBornAgainBase_PYTAG@
- Python wrappers
import _libBornAgain*
=>import @_libBornAgain*_PYTAG@ as _libBornAgain*
- C++ wrappers:
-
multipython/MakePythonWheel
module (make_python_wheel
function) is introduced to build the Python package (wheel) for a given Python version. For each version of Python, the wheel is built via Pythonsetuptools
with all the extra libraries added (likelibgsl
). The wheel configuration follows the latest Python standards, PEP 518. An empty C module is added to enforce Pythonsetuptools
to add the proper tags to the wheel file according to PEP 425.
make_python_wheel
function is called inmake_SWIG_lib
.Current extra libraries are:
- Boost iostreams
- GSL + GSLCBLAS
- FFTW3
- Cerf
- TIFF + TIFFXX, only if
BORNAGAIN_TIFF_SUPPORT
is ON
NOTE: For the Python wheel we do not necessarily need Boost iostreams and the tiff libraries since Python has already well-established I/O modules for many different data formats.
-
multipython/MakeMultiPythonLibs
module (make_multipython_libs
function) is a higher-level interface tomake_shared_lib
which simplifies the definition and configuration of BornAgain shared libraries along with their Python API for all required Python versions.
make_multipython_libs
simply callsmake_shared_lib
several times to build the libraries and the package for a Python version. -
For each BornAgain module,
CMakeLists.txt
is modified to build the shared libraries and the package for each version of Python.
Each module is decomposed into Python-dependent and Python-independent parts. The Python-independent parts are compiled only once into object files (a CMakeOBJECT
library). The Python-dependent parts are compiled for each version of Python due to the incompatibility of the Python headers and libraries across different versions -- ABI incompatibility. This scheme is required for build efficiency, otherwise one has to build the whole library several times. The produced object files are finally used to make the shared library which is then linked with a given version of the Python libraries.Currently,
Base
,Sample
andDevice
modules have Python-dependent parts. Later, the Python-dependent part of these modules should be extracted as a separate module to make the build mechanism easier.The CMake script for each module only declares the properties of that module (e.g., defines the sources and include directories). Furthermore, a function is defined which sets the module's specific dependencies (which could be Python-dependent). The declarations and this function are used later when building multiple versions of the library (corresponding to each Python version).
-
The GitLab CI scripts for Linux, MacOS and MS-Windows are modified to make the Python wheels. The wheels are stored as artifacts in the build subdirectory
python_packages/wheels
for each version of Python; e.g.,python_packages/wheels/py38
. -
The new CMake modules are separated into
cmake/BornAgain/multipython
folder. -
No changes are made to the source code or BornAgain module structure.
-
The standard build procedure for a user who does not wish to build Python packages remains the same as before (as long as
BORNAGAIN_PYTHON_PACKAGE
is OFF).
Closes #96 (closed)