133 lines
5.0 KiB
Markdown
133 lines
5.0 KiB
Markdown
# pycmake
|
|
|
|
**pycmake** is a CMake macro for testing that we have the necessary Python
|
|
modules in the necessary versions for our systems. The basic assumption of this
|
|
package is PEP 396 -- Module Version Numbers as layed out in
|
|
https://www.python.org/dev/peps/pep-0396/ . Here it says that a Python module
|
|
*should* expose a field `__version__`, that is, to obtain a module's version,
|
|
one may run
|
|
|
|
import module as m
|
|
print(m.__version__)
|
|
|
|
|
|
Unfortunately, not all Python modules expose a version number, like *inspect*.
|
|
Other Python modules expose several version numbers, e.g., one for the
|
|
underlying software and one for the python packaging, like *SQLite* and *PyQt*.
|
|
To handle the different ways of obtaining the version, one may explicitly state
|
|
the version *accessor* to use; `__version__` is the default.
|
|
|
|
For more information on module version numbers, see
|
|
[PEP 396](https://www.python.org/dev/peps/pep-0396/).
|
|
|
|
|
|
|
|
|
|
## Examples
|
|
|
|
The most vanilla example usage is the following, where we require *numpy*
|
|
version at least 1.7.0, and any newer version is acceptable. Consider the
|
|
two CMake lines, whose behavior are identical:
|
|
|
|
python_module( numpy REQUIRED 1.7.0 )
|
|
python_module( numpy REQUIRED 1.7.0 MINIMUM )
|
|
|
|
|
|
However, sometimes we are phasing out an older Python module, in which case, we
|
|
can give the user a warning. By writing
|
|
|
|
python_module( scipy REQUIRED 1.5.1 OPTIONAL )
|
|
|
|
we are telling CMake to output a warning to the user if a *scipy* version
|
|
below 1.5.1 is found, and to exit with an error if *scipy* is not found.
|
|
|
|
Yet other times, our systems do not work with newer versions than a certain
|
|
number. By writing
|
|
|
|
python_module( pandas REQUIRED 0.15.1 EXACT )
|
|
|
|
we ask CMake to fail if *pandas* 0.15.1 is not installed, i.e., even if
|
|
*pandas* 0.15.2 is installed.
|
|
|
|
|
|
More examples:
|
|
|
|
find_python_module( numpy REQUIRED 1.5.3 MINIMUM )
|
|
find_python_module( numpy REQUIRED 1.5.3 EXACT )
|
|
find_python_module( numpy REQUIRED 1.5.3 OPTIONAL )
|
|
find_python_module( numpy REQUIRED 1.5.3 )
|
|
find_python_module( numpy REQUIRED )
|
|
find_python_module( numpy OPTIONAL )
|
|
find_python_module( numpy REQUIRED 1.5.3 MINIMUM "__version__" )
|
|
|
|
|
|
Not every Python module exposes `__version__`, and some module exposes several
|
|
flags, like `version` and `apilevel`.
|
|
|
|
<!--
|
|
*SQLite2* users beware. They expose `sqlite_version_info`, `version`, and
|
|
`version_info`. There is a difference between the SQLite version (e.g. 2
|
|
or 3) and the *python-pysqlite* version, e.g. 1.0.1. *SQLite2*
|
|
exposes `apilevel = '2.0'` and `version = '1.0.1'`. It is therefore possible to
|
|
get both `apilevel` and `version`, as well as `__version__` etc.
|
|
-->
|
|
|
|
|
|
Complex systems like SQLite and Qt exposes more than one version field. For
|
|
instance, SQLite 2 vs *python-pysqlite1*. SQLite has version 2.0,
|
|
whereas the Python package *python-pysqlite1* has version 1.0.1. These are
|
|
accessible by calling
|
|
|
|
apilevel = '2.0'
|
|
version = '1.0.1'
|
|
|
|
It is possible to obtain these properties by invoking `python_module` providing
|
|
a fifth argument, the *version accessor* argument:
|
|
|
|
python_module( sqlite REQUIRED 2.0 MINIMUM "apilevel" )
|
|
python_module( sqlite REQUIRED 1.0.1 MINIMUM "version" )
|
|
python_module( numpy REQUIRED 1.7.1 MINIMUM "__version__" )
|
|
|
|
|
|
*PyQt4* does not export any version field. Importing *PyQt4.Qt* in Python
|
|
reveals fields
|
|
|
|
PYQT_VERSION_STR = '4.10.4'
|
|
QT_VERSION_STR = '4.8.6'
|
|
|
|
|
|
|
|
## Technicalities
|
|
|
|
This repo contains two files of interest. The `CMakeLists.txt` loads the more
|
|
interesting file `FindPythonModule.cmake`, and then provides some use cases.
|
|
`FindPythonModule.cmake` contains two macros, one main and one auxilliary:
|
|
|
|
* `macro( python_module_version module )`
|
|
* `macro( python_module module version )` — this is the interface
|
|
|
|
The first macro, `python_module_version`, checks if `module` is a Python
|
|
importable package, and if so, which version `module` has. The version is found
|
|
simply by the following Python program:
|
|
|
|
import package as py_m
|
|
print(py_m.__version__)
|
|
|
|
|
|
If this program fails, the status flag will be set to `1`, and the package
|
|
will be assumed to not exist. If the program succeeds, the output of the
|
|
program will be stored in the global cmake variable `PY_${package}` where
|
|
`${package}` is the name of the package given to the macro `python_module`.
|
|
|
|
|
|
The macro, `python_module(module version)` calls the function,
|
|
`python_module_version(module)` and checks if
|
|
|
|
1. The variable `PY_${module}` has been set. If the package is `REQUIRED`, then
|
|
the variable must have been set, otherwise we message a `SEND_ERROR`, and
|
|
CMake will fail. If the package is `OPTIONAL` and not found, we issue a
|
|
`WARNING`.
|
|
1. The we compare the version number found (the content of the variable
|
|
`PY_${module}`). The keywords `EXACT`, `MINIMUM` and `OPTIONAL` are
|
|
self-explanatory.
|