From 3260b827e7ffeaabc2710ac26c2a11a61216c3dc Mon Sep 17 00:00:00 2001 From: Martin Killenberg <martin.killenberg@desy.de> Date: Mon, 15 Mar 2021 10:03:13 +0100 Subject: [PATCH] added helper macros for (boost) python bindings --- HowTo_project-template.md | 3 +- cmake/Modules/pythonBindingHelperMacros.cmake | 263 ++++++++++++++++++ 2 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 cmake/Modules/pythonBindingHelperMacros.cmake diff --git a/HowTo_project-template.md b/HowTo_project-template.md index 307c988..06a298a 100644 --- a/HowTo_project-template.md +++ b/HowTo_project-template.md @@ -13,8 +13,9 @@ merging the head of project template. In your local git repository, issue: ``` $ git fetch https://github.com/ChimeraTK/project-template.git - $ git merge FETCH_HEAD + $ git merge FETCH_HEAD --allow-unrelated-histories ``` +To pull in an update of the project template, you can leave out the `--allow-unrelated-histories` flag. Note: you can access the files provided in the cmake directory from your CMakeLists.txt after appending this location to your `CMAKE_MODULE_PATH` diff --git a/cmake/Modules/pythonBindingHelperMacros.cmake b/cmake/Modules/pythonBindingHelperMacros.cmake new file mode 100644 index 0000000..76e09f1 --- /dev/null +++ b/cmake/Modules/pythonBindingHelperMacros.cmake @@ -0,0 +1,263 @@ +# This file contains helper macros to compile python bindings with boost python. + + +# This is a hack to get escape ${} as the variable expansion in the files copied +# through configure_file. This is handy when copying shell scripts. +# TODO: move this to a better location +set(Dollar "$") + + + +# Call this before find_packages any any function from this file +function(initialize ) + if(PYTHON3) + # + # Unfortunately need this for cmake version on trusty to find PythonLibs + # There also seems to be a bug with cmake on trusty where having this causes + # find_package to always return the python3 libraries even when python 2 + # libraries are specified . For this reason set this variable only on + # python3 builds. + set(Python_ADDITIONAL_VERSIONS 3.4 PARENT_SCOPE) + endif() +endfunction() + +# Returns the python interpreter to be used for teh project. This is indicated +# by the user by invoking cmake with the PYTHON3 bool as True: +# cmake -DPYTHON3=TRUE ../ +function(get_python_interpreter_string python_interpreter) + if(PYTHON3) + set(${python_interpreter} "python3" PARENT_SCOPE) + else() + set(${python_interpreter} "python" PARENT_SCOPE) + endif() + +endfunction() + + +# Returns version number depending on choice of compilation; 3 for python3 and +# 2.7 otherwise +function(get_desired_python_major_release version_num) + if(PYTHON3) + set(${version_num} "3" PARENT_SCOPE) + else() + set(${version_num} "2" PARENT_SCOPE) + endif() +endfunction() + +# os_code_name, empty if not found +function(extract_ubuntu_variant os_code_name) + find_program(lsb_release lsb_release) + execute_process(COMMAND ${lsb_release} -cs + OUTPUT_VARIABLE lsb_release_os_codename + OUTPUT_STRIP_TRAILING_WHITESPACE) + set(${os_code_name} ${lsb_release_os_codename} PARENT_SCOPE) +endfunction() + +# Return os_release_version, empty if not found +function(get_os_distro_version os_release_version) + find_program(lsb_release lsb_release) + execute_process(COMMAND ${lsb_release} -rs + OUTPUT_VARIABLE lsb_release_os_version + OUTPUT_STRIP_TRAILING_WHITESPACE) + set(${os_release_version} ${lsb_release_os_version} PARENT_SCOPE) +endfunction() + +# +# Returns os_vendor as Ubuntu if applicable +function (get_os_distro_vendor os_vendor) + find_program(lsb_release lsb_release) + execute_process(COMMAND ${lsb_release} -is + OUTPUT_VARIABLE lsb_vendor_name + OUTPUT_STRIP_TRAILING_WHITESPACE) + + set(${os_vendor} ${lsb_vendor_name} PARENT_SCOPE) +endfunction() + + +function(get_python_module_install_path python_version_string install_path) + get_os_distro_vendor(os_vendor) + convert_version_string_to_list(${python_version_string} version_list) + list(GET version_list 0 major_version) + list(GET version_list 1 minor_version) + + if((("${os_vendor}" STREQUAL "Ubuntu") OR ("${os_vendor}" STREQUAL "Debian")) AND + (("${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr") OR ("${CMAKE_INSTALL_PREFIX}" STREQUAL "/usr/local"))) + + if(PYTHON3) + set(${install_path} + "lib/python${major_version}/dist-packages" PARENT_SCOPE) + else() + set(${install_path} + "lib/python${major_version}.${minor_version}/dist-packages" + PARENT_SCOPE) + endif() + + else() + set(${install_path} + "lib/python${major_version}.${minor_version}/site-packages" + PARENT_SCOPE) + endif() + +endfunction() + +function (get_boost_python_component_name pythonlib_version boost_version component_name) + + get_os_distro_vendor(os_vendor) + get_os_distro_version(os_distro_version) + + convert_version_string_to_list(${pythonlib_version} version_list) + list(GET version_list 0 py_major_version) + list(GET version_list 1 py_minor_version) + + if(${boost_version} VERSION_LESS 1.67.0) + # This kludge is here because boost python component naming was not + # consistent across distributions before 1.67; hence the special + # handling. + if("${os_vendor}" STREQUAL "Ubuntu") + set(python3_prefix "python3-py") + if (${os_distro_version} VERSION_LESS 18.10) + set(python3_prefix "python-py") + endif() + if(PYTHON3) + set(${component_name} "${python3_prefix}${py_major_version}${py_minor_version}" PARENT_SCOPE) + else() + set(${component_name} "python" PARENT_SCOPE) + endif() + else() + # This is probably the default on Debian before the names were made + # consistent(have not verified though) + if(PYTHON3) + set(${component_name} "python${py_major_version}" PARENT_SCOPE) + else() + set(${component_name} "python" PARENT_SCOPE) + endif() + endif() + else() + set(${component_name} "python${py_major_version}${py_minor_version}" PARENT_SCOPE) + endif() +endfunction() + +function (convert_version_string_to_list version_string version_list) + string(REPLACE "." ";" tmp ${${version_string}}) + set(${version_list} ${tmp} PARENT_SCOPE) +endfunction() + + +# the directory parameter takes in a list of directories within the source +# directory. The macro copies supported file extensions from these specified +# directories into subdirectories in the build folder. The subdirectory will be +# created with the same name as the source directory. +# Eg: +# ${CMAKE_SOURCE_DIR}/a/b/source_dir as input creates +# <project_build_dir>/source_dir and will have the supported files from +# "${CMAKE_SOURCE_DIR}//a/b/source_dir copied to <project_build_dir>/source_dir. +# supported file formats are: +# .sh +# .dmap +# .map +MACRO (COPY_CONTENT_TO_BUILD_DIR directories) + foreach( directory ${directories} ) + COPY_SUPPORTED_CONTENT( "${directory}" ) + endforeach( directory ) +ENDMACRO(COPY_CONTENT_TO_BUILD_DIR) + + + +MACRO( COPY_SUPPORTED_CONTENT directory ) + get_filename_component(parent_directory ${directory} NAME) # Kind of a hack + # as we are actually picking the directory name and not the filename. + # (because ${directory} contains path to a directory and not a file) + set(source_directory "${directory}" ) + set(target_directory "${PROJECT_BINARY_DIR}/${parent_directory}") + file( MAKE_DIRECTORY "${target_directory}" ) + COPY_SOURCE_TO_TARGET( ${source_directory} ${target_directory} ) +ENDMACRO( COPY_SUPPORTED_CONTENT ) + + + +# The macro picks up only these specified formats from the +# source directory : .dmap, .map, .txt, .py, .sh. New formats formats may be added by +# modifying the globbing expression +MACRO( COPY_SOURCE_TO_TARGET source_directory target_directory) + FILE( GLOB list_of_files_to_copy + "${source_directory}/*[!~].py" # <- filter out abc~.py + "${source_directory}/*[!~].dmap" + "${source_directory}/*[!~].map" ) + + foreach( file ${list_of_files_to_copy} ) + get_filename_component(file_name ${file} NAME) + configure_file( "${file}" "${target_directory}/${file_name}" ) + endforeach( file ) +ENDMACRO( COPY_SOURCE_TO_TARGET ) + + +MACRO( ADD_SCRIPTS_AS_TESTS list_of_script_files ) + foreach( script_path ${list_of_script_files} ) + get_filename_component(test_name ${script_path} NAME_WE) + add_test( ${test_name} ${script_path} ) + endforeach( script_path ) +ENDMACRO( ADD_SCRIPTS_AS_TESTS ) + + + + +FUNCTION(CHECK_FOR_SPHINX) + if(NOT SPHINX_BUILD) + # Assume there is no good version of sphinx-build on the build host + set(SUPPORTED_SPHINX_VERSION_AVAILABLE FALSE CACHE INTERNAL + "Variable indicates if a suitable version of sphinx build is available") + + # find if the sphinx-build executable is in the system path + find_program(SPHINX_BUILD sphinx-build) + + # set SUPPORTED_SPHINX_VERSION_AVAILABLE to true if it is a suitable version + if(SPHINX_BUILD) + CHECK_SPHINX_BUILD_VERSION() + endif() + endif() +ENDFUNCTION() + + +FUNCTION(CHECK_SPHINX_BUILD_VERSION) + # set SPHINX_SUPPORTED_BUILD_VERSION in cache as TRUE or NOTFOUND + if(SPHINX_BUILD) + execute_process(COMMAND "${SPHINX_BUILD}" "--version" + RESULT_VARIABLE resultVariable + ERROR_VARIABLE outputOfVersionCommand + OUTPUT_VARIABLE outputOfVersionCommand + OUTPUT_STRIP_TRAILING_WHITESPACE) + # was the command successful. Seems --version is not supported on v1.1 which + # comes with ubuntu! + if(resultVariable STREQUAL "0") # command succesful + EXTRACTVERSION(${outputOfVersionCommand}) + MESSAGE(STATUS "Found Sphinx Build Version: ${extractedVersionNumber}") + if(NOT (extractedVersionNumber LESS 1.3.0)) + set(SUPPORTED_SPHINX_VERSION_AVAILABLE TRUE CACHE INTERNAL + "Variable indicates if a suitable version of sphinx build is available" + FORCE) + endif() + endif() + endif() +ENDFUNCTION() + +FUNCTION(EXTRACTVERSION outputOfVersionCommand) + # convert to a list + string(REGEX REPLACE " " ";" outputlist ${outputOfVersionCommand}) + list(GET outputlist -1 tmp) # <- This is brittle TODO move to regex later + set(extractedVersionNumber ${tmp} PARENT_SCOPE) +endfunction() + +FUNCTION(ADD_HTML_DOCUMENTATION_SUPPORT) + configure_file(${sphinx_build_confg_in} ${sphinx_build_config_file}) + # copy the config file to the build directory + message(STATUS "Html documentation support enabled; use 'make doc'") + get_filename_component(sphinx_config_parent_dir ${sphinx_build_config_file} PATH) + add_custom_target(doc + COMMAND ${SPHINX_BUILD} -c ${sphinx_config_parent_dir} -b html ${location_of_rst_source_files} ${location_of_built_html_files} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating html documentation" VERBATIM) + #unfortunately sphinx needs to scan the library, so we first have to run the build step before we get proper documentation + add_dependencies(doc mtca4udeviceaccess) + + set(DOC_TARGET_ADDED TRUE CACHE INTERNAL "Doc target has been configured") +ENDFUNCTION() -- GitLab