How to do it

In the following code, we wish to be able to check whether NumPy is available using CMake. We will first need to make sure that the Python interpreter, headers, and libraries are all available on our system. We will then move on to make sure that NumPy is available:

  1. First, we define the minimum CMake version, project name, language, and C++ standard:
cmake_minimum_required(VERSION 3.5 FATAL_ERROR)

project(recipe-03 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
  1. Finding the interpreter, headers, and libraries is achieved exactly as in the previous recipe:
find_package(PythonInterp REQUIRED)
find_package(PythonLibs ${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR} EXACT REQUIRED)
  1. Properly packaged Python modules are aware of their installation location and version. This can be probed by executing a minimal Python script. We can execute this step within our CMakeLists.txt:
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "import re, numpy; print(re.compile('/__init__.py.*').sub('',numpy.__file__))"
RESULT_VARIABLE _numpy_status
OUTPUT_VARIABLE _numpy_location
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
  1. The _numpy_status variable will be an integer if NumPy was found or a string with some error message otherwise, whereas _numpy_location will contain the path to the NumPy module. If NumPy is found, we save its location to a new variable simply called NumPy. Notice that the new variable is cached; this means that CMake creates a persistent variable that can be later modified by the user:
if(NOT _numpy_status)
set(NumPy ${_numpy_location} CACHE STRING "Location of NumPy")
endif()
  1. The next step is to check the version of the module. Once again, we deploy some Python magic in our CMakeLists.txt, saving the version into a _numpy_version variable:
execute_process(
COMMAND
${PYTHON_EXECUTABLE} "-c" "import numpy; print(numpy.__version__)"
OUTPUT_VARIABLE _numpy_version
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
  1. Finally, we let the FindPackageHandleStandardArgs CMake package set up the NumPy_FOUND variable and output status information in the correct format:
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(NumPy
FOUND_VAR NumPy_FOUND
REQUIRED_VARS NumPy
VERSION_VAR _numpy_version
)
  1. Once all dependencies have been correctly found, we can compile the executable and link it to the Python libraries:
add_executable(pure-embedding "")

target_sources(pure-embedding
PRIVATE
Py${PYTHON_VERSION_MAJOR}-pure-embedding.cpp
)

target_include_directories(pure-embedding
PRIVATE
${PYTHON_INCLUDE_DIRS}
)

target_link_libraries(pure-embedding
PRIVATE
${PYTHON_LIBRARIES}
)
  1. We also have to make sure that use_numpy.py is available in the build directory:
add_custom_command(
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py
COMMAND
${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/use_numpy.py
${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/use_numpy.py
)

# make sure building pure-embedding triggers the above custom command
target_sources(pure-embedding
PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/use_numpy.py
)
  1. Now, we can test the detection and embedding of the code:
$ mkdir -p build
$ cd build
$ cmake ..

-- ...
-- Found PythonInterp: /usr/bin/python (found version "3.6.5")
-- Found PythonLibs: /usr/lib/libpython3.6m.so (found suitable exact version "3.6.5")
-- Found NumPy: /usr/lib/python3.6/site-packages/numpy (found version "1.14.3")

$ cmake --build .
$ ./pure-embedding use_numpy print_ones 2 3

[[1. 1. 1.]
[1. 1. 1.]]
Result of call: 6