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_PACKAGEis 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/PythonPackagefolder. The wheel configuration follows the latest Python standards, PEP 518. An empty C module is added to enforce Pythonsetuptoolsto 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*_py38for 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/PyDirectoriesmodule 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/FindCustomPython3module (find_custom_python3function) 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_py37orPython3_NumPy_FOUND_py37for Python 3.7. Note that the Python tag is thepy_version_nodotdefined in PEP 425. -
For each required version of Python, the development platform is found in
Dependencesmodule via calls tofind_custom_python3. The main version of Python installed on the system is automatically added to the required versions. -
multipython/MakeSharedLibmodule (make_shared_libfunction) 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
RPATHflag is set for BornAgain shared libraries under Linux so that they could find their dependencies in$ORIGIN/extradirectory. 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
PATHenvironmental variable. In Python >= 3.8,os.add_dll_directoryis used to set this path.
- Linux:
The
-
MakeSharedLibusesmultipython/MakeSWIGLibmodule (make_SWIG_libfunction) 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_libis an internal function, called only from the functionmake_shared_lib.
An extra internal function_ConfigureSWIGis 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/MakePythonWheelmodule (make_python_wheelfunction) is introduced to build the Python package (wheel) for a given Python version. For each version of Python, the wheel is built via Pythonsetuptoolswith all the extra libraries added (likelibgsl). The wheel configuration follows the latest Python standards, PEP 518. An empty C module is added to enforce Pythonsetuptoolsto add the proper tags to the wheel file according to PEP 425.
make_python_wheelfunction is called inmake_SWIG_lib.Current extra libraries are:
- Boost iostreams
- GSL + GSLCBLAS
- FFTW3
- Cerf
- TIFF + TIFFXX, only if
BORNAGAIN_TIFF_SUPPORTis 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/MakeMultiPythonLibsmodule (make_multipython_libsfunction) is a higher-level interface tomake_shared_libwhich simplifies the definition and configuration of BornAgain shared libraries along with their Python API for all required Python versions.
make_multipython_libssimply callsmake_shared_libseveral times to build the libraries and the package for a Python version. -
For each BornAgain module,
CMakeLists.txtis 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 CMakeOBJECTlibrary). 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,SampleandDevicemodules 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/wheelsfor each version of Python; e.g.,python_packages/wheels/py38. -
The new CMake modules are separated into
cmake/BornAgain/multipythonfolder. -
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_PACKAGEis OFF).
Closes #96 (closed)