diff --git a/everest-cmake-config-version.cmake b/everest-cmake-config-version.cmake index 410a575..d82b2c1 100644 --- a/everest-cmake-config-version.cmake +++ b/everest-cmake-config-version.cmake @@ -1,9 +1,9 @@ -set(PACKAGE_VERSION 0.2.1) +set(PACKAGE_VERSION 0.3) if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION) set(PACKAGE_VERSION_EXACT TRUE) elseif (PACKAGE_FIND_VERSION_MAJOR STREQUAL "0") - if (PACKAGE_FIND_VERSION_MINOR GREATER "2") + if (PACKAGE_FIND_VERSION_MINOR GREATER "3") set(PACKAGE_VERSION_UNSUITABLE TRUE) else () set(PACKAGE_VERSION_COMPATIBLE TRUE) diff --git a/everest-cmake-config.cmake b/everest-cmake-config.cmake index aa7a549..5d66f46 100644 --- a/everest-cmake-config.cmake +++ b/everest-cmake-config.cmake @@ -22,6 +22,8 @@ if (everest_cmake_FOUND) endif() include("${CMAKE_CURRENT_LIST_DIR}/edm.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/python-package-helpers.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/python-virtualenv.cmake") macro (evc_include FILE) include("${everest-cmake_DIR}/3rd_party/${FILE}.cmake") diff --git a/python-package-helpers.cmake b/python-package-helpers.cmake new file mode 100644 index 0000000..79b28d7 --- /dev/null +++ b/python-package-helpers.cmake @@ -0,0 +1,128 @@ +function (ev_create_pip_install_dist_target) + cmake_parse_arguments( + "EV_CREATE_PIP_INSTALL_DIST_TARGET" + "" + "PACKAGE_NAME" + "DEPENDS" + ${ARGN} + ) + + add_custom_target(${EV_CREATE_PIP_INSTALL_DIST_TARGET_PACKAGE_NAME}_pip_install_dist + # Remove build dir from pip + COMMAND + ${CMAKE_COMMAND} -E remove_directory build + COMMAND + ${Python3_EXECUTABLE} -m pip install --force-reinstall . + WORKING_DIRECTORY + ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS + ${EV_CREATE_PIP_INSTALL_DIST_TARGET_DEPENDS} + COMMENT + "Installing ${EV_CREATE_PIP_INSTALL_DIST_TARGET_PACKAGE_NAME} from distribution" + ) +endfunction() + +function (ev_create_pip_install_local_target) + cmake_parse_arguments( + "EV_CREATE_PIP_INSTALL_LOCAL_TARGET" + "" + "PACKAGE_NAME" + "DEPENDS" + ${ARGN} + ) + + add_custom_target(${EV_CREATE_PIP_INSTALL_LOCAL_TARGET_PACKAGE_NAME}_pip_install_local + # Remove build dir from pip + COMMAND + ${CMAKE_COMMAND} -E remove_directory build + COMMAND + ${Python3_EXECUTABLE} -m pip install --force-reinstall -e . + WORKING_DIRECTORY + ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS + ${EV_CREATE_PIP_INSTALL_LOCAL_TARGET_DEPENDS} + COMMENT + "Installing ${EV_CREATE_PIP_INSTALL_LOCAL_TARGET_PACKAGE_NAME} via user-mode from build" + ) +endfunction() + +function(ev_create_pip_install_targets) + cmake_parse_arguments( + "EV_CREATE_PIP_INSTALL_TARGETS" + "" + "PACKAGE_NAME" + "DIST_DEPENDS;LOCAL_DEPENDS" + ${ARGN} + ) + ev_create_pip_install_dist_target( + PACKAGE_NAME ${EV_CREATE_PIP_INSTALL_TARGETS_PACKAGE_NAME} + DEPENDS ${EV_CREATE_PIP_INSTALL_TARGETS_DIST_DEPENDS} + ) + ev_create_pip_install_local_target( + PACKAGE_NAME ${EV_CREATE_PIP_INSTALL_TARGETS_PACKAGE_NAME} + DEPENDS ${EV_CREATE_PIP_INSTALL_TARGETS_LOCAL_DEPENDS} + ) +endfunction() + +function (ev_create_python_wheel_targets) + cmake_parse_arguments( + "EV_CREATE_PYTHON_WHEEL_TARGETS" + "" + "PACKAGE_NAME" + "DEPENDS;INSTALL_PREFIX" + ${ARGN} + ) + + if (NOT DEFINED ${EV_CREATE_PYTHON_WHEEL_TARGETS_INSTALL_PREFIX}) + if ("${${PROJECT_NAME}_WHEEL_INSTALL_PREFIX}" STREQUAL "") + message(FATAL_ERROR + "No install prefix for wheel specified, please set ${PROJECT_NAME}_WHEEL_INSTALL_PREFIX, use the INSTALL_PREFIX argument or use macro ev_setup_cmake_variables_python_wheel() to set a default value." + ) + else() + set(EV_CREATE_PYTHON_WHEEL_TARGETS_INSTALL_PREFIX ${${PROJECT_NAME}_WHEEL_INSTALL_PREFIX}) + endif() + endif() + + set(WHEEL_OUTDIR ${CMAKE_CURRENT_BINARY_DIR}/dist) + add_custom_target(${EV_CREATE_PYTHON_WHEEL_TARGETS_PACKAGE_NAME}_build_wheel + # Remove build dir from pip + COMMAND + ${CMAKE_COMMAND} -E remove_directory build + COMMAND + ${Python3_EXECUTABLE} -m build --wheel --outdir ${WHEEL_OUTDIR} . + WORKING_DIRECTORY + ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS + ${EV_CREATE_PYTHON_WHEEL_TARGETS_DEPENDS} + COMMENT + "Building wheel for ${EV_CREATE_PYTHON_WHEEL_TARGETS_PACKAGE_NAME}" + ) + + add_custom_target(${EV_CREATE_PYTHON_WHEEL_TARGETS_PACKAGE_NAME}_install_wheel + COMMAND + ${CMAKE_COMMAND} -E make_directory ${EV_CREATE_PYTHON_WHEEL_TARGETS_INSTALL_PREFIX} + COMMAND + ${CMAKE_COMMAND} -E copy_directory ${WHEEL_OUTDIR} ${EV_CREATE_PYTHON_WHEEL_TARGETS_INSTALL_PREFIX}/ + WORKING_DIRECTORY + ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS + ${EV_CREATE_PYTHON_WHEEL_TARGETS_PACKAGE_NAME}_build_wheel + COMMENT + "Copy wheel for ${EV_CREATE_PYTHON_WHEEL_TARGETS_PACKAGE_NAME} to ${EV_CREATE_PYTHON_WHEEL_TARGETS_INSTALL_PREFIX}" + ) +endfunction() + +macro(ev_setup_cmake_variables_python_wheel) + set(${PROJECT_NAME}_WHEEL_INSTALL_PREFIX "" CACHE PATH "Path to install python wheels to") + if (${PROJECT_NAME}_WHEEL_INSTALL_PREFIX STREQUAL "") + if(${WHEEL_INSTALL_PREFIX}) + set(${PROJECT_NAME}_DEFAULT_WHEEL_INSTALL_PREFIX "${WHEEL_INSTALL_PREFIX}") + message(STATUS "${PROJET_NAME}_WHEEL_INSTALL_PREFIX not set, using default: WHEEL_INSTALL_PREFIX=${${PROJECT_NAME}_DEFAULT_WHEEL_INSTALL_PREFIX}") + else() + set(${PROJECT_NAME}_DEFAULT_WHEEL_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}/../dist-wheels") + message(STATUS "${PROJECT_NAME}_WHEEL_INSTALL_PREFIX not set, using default: \${CMAKE_INSTALL_PREFIX}/../dist-wheels=${${PROJECT_NAME}_DEFAULT_WHEEL_INSTALL_PREFIX}") + endif() + set(${PROJECT_NAME}_WHEEL_INSTALL_PREFIX "${${PROJECT_NAME}_DEFAULT_WHEEL_INSTALL_PREFIX}" CACHE PATH "Path to install python wheels to" FORCE) + endif() + message(STATUS "${PROJECT_NAME}_WHEEL_INSTALL_PREFIX=${${PROJECT_NAME}_WHEEL_INSTALL_PREFIX}") +endmacro() diff --git a/python-virtualenv.cmake b/python-virtualenv.cmake new file mode 100644 index 0000000..02dc40f --- /dev/null +++ b/python-virtualenv.cmake @@ -0,0 +1,143 @@ +macro(ev_create_python_venv) + cmake_parse_arguments( + "EV_CREATE_PYTHON_VENV" + "INCLUDE_SYSTEM_SITE_PACKAGES" + "PATH_TO_VENV" + "" + ${ARGN} + ) + ev_is_directory_python_venv( + DIRECTORY "${EV_CREATE_PYTHON_VENV_PATH_TO_VENV}" + RESULT_VAR "EV_CREATE_PYTHON_VENV_IS_DIRECTORY_PYTHON_VENV" + ) + if(${EV_CREATE_PYTHON_VENV_IS_DIRECTORY_PYTHON_VENV}) + message(FATAL_ERROR "Directory is already a python venv: ${EV_CREATE_PYTHON_VENV_PATH_TO_VENV}") + endif() + find_package(Python3 + REQUIRED + COMPONENTS + Interpreter + Development + ) + set(EV_CREATE_PYTHON_VENV_SYSTEM_SITE_PACKAGES_FLAG "") + if(${EV_CREATE_PYTHON_VENV_INCLUDE_SYSTEM_SITE_PACKAGES}) + set(EV_CREATE_PYTHON_VENV_SYSTEM_SITE_PACKAGES_FLAG "--system-site-packages") + endif() + execute_process( + COMMAND + ${Python3_EXECUTABLE} -m venv ${EV_CREATE_PYTHON_VENV_SYSTEM_SITE_PACKAGES_FLAG} ${EV_CREATE_PYTHON_VENV_PATH_TO_VENV} + ) + message(STATUS "Created python venv: ${EV_CREATE_PYTHON_VENV_PATH_TO_VENV}") +endmacro() + +macro(ev_activate_python_venv) + cmake_parse_arguments( + "EV_ACTIVATE_PYTHON_VENV" + "" + "PATH_TO_VENV" + "" + ${ARGN} + ) + ev_is_directory_python_venv( + DIRECTORY "${EV_ACTIVATE_PYTHON_VENV_PATH_TO_VENV}" + RESULT_VAR "EV_CREATE_PYTHON_VENV_IS_DIRECTORY_PYTHON_VENV" + ) + if(NOT ${EV_CREATE_PYTHON_VENV_IS_DIRECTORY_PYTHON_VENV}) + message(FATAL_ERROR "Directory is not a python venv: ${EV_ACTIVATE_PYTHON_VENV_PATH_TO_VENV}") + endif() + set(ENV{VIRTUAL_ENV} "${EV_ACTIVATE_PYTHON_VENV_PATH_TO_VENV}") + set(Python3_FIND_VIRTUALENV FIRST) + unset(Python3_EXECUTABLE) + find_package(Python3 + REQUIRED + COMPONENTS + Interpreter + Development + ) + message(STATUS "Activated python venv: Python3_EXECUTABLE: ${Python3_EXECUTABLE}") +endmacro() + +macro(ev_deactivate_python_venv) + unset(ENV{VIRTUAL_ENV}) + unset(Python3_EXECUTABLE) + set(Python3_FIND_VIRTUALENV STANDARD) + find_package(Python3 + REQUIRED + COMPONENTS + Interpreter + Development + ) + message(STATUS "Deactivated python venv: Python3_EXECUTABLE: ${Python3_EXECUTABLE}") +endmacro() + +function(ev_is_directory_python_venv) + cmake_parse_arguments( + "EV_IS_DIRECTORY_PYTHON_VENV" + "" + "DIRECTORY;RESULT_VAR" + "" + ${ARGN} + ) + message(DEBUG "Checking if directory is a python venv: ${EV_IS_DIRECTORY_PYTHON_VENV_DIRECTORY}") + if(EXISTS "${EV_IS_DIRECTORY_PYTHON_VENV_DIRECTORY}/pyvenv.cfg") + message(DEBUG "Directory is a python venv: ${EV_IS_DIRECTORY_PYTHON_VENV_DIRECTORY}") + set(${EV_IS_DIRECTORY_PYTHON_VENV_RESULT_VAR} TRUE PARENT_SCOPE) + return() + else() + message(DEBUG "Directory is not a python venv: ${EV_IS_DIRECTORY_PYTHON_VENV_DIRECTORY}") + set(${EV_IS_DIRECTORY_PYTHON_VENV_RESULT_VAR} FALSE PARENT_SCOPE) + return() + endif() +endfunction() + +function(ev_is_python_venv_active) + cmake_parse_arguments( + "EV_IS_PYTHON_VENV_ACTIVE" + "" + "RESULT_VAR" + "" + ${ARGN} + ) + if(DEFINED ENV{VIRTUAL_ENV}) + message(DEBUG "Python venv is active: $ENV{VIRTUAL_ENV}") + set(${EV_IS_PYTHON_VENV_ACTIVE_RESULT_VAR} TRUE PARENT_SCOPE) + return() + else() + message(DEBUG "Python venv is not active") + set(${EV_IS_PYTHON_VENV_ACTIVE_RESULT_VAR} FALSE PARENT_SCOPE) + return() + endif() +endfunction() + +macro(ev_setup_python_executable) + cmake_parse_arguments( + "EV_SETUP_PYTHON_EXECUTABLE" + "" + "USE_PYTHON_VENV;PYTHON_VENV_PATH" + "" + ${ARGN} + ) + if(NOT ${EV_SETUP_PYTHON_EXECUTABLE_USE_PYTHON_VENV}) + find_package(Python3 + REQUIRED + COMPONENTS + Interpreter + Development + ) + else() + message(STATUS "Using python venv: ${${PROJECT_NAME}_PYTHON_VENV_PATH}") + ev_is_directory_python_venv( + DIRECTORY ${${PROJECT_NAME}_PYTHON_VENV_PATH} + RESULT_VAR IS_DIRECTORY_PYTHON_VENV + ) + if(NOT ${IS_DIRECTORY_PYTHON_VENV}) + ev_create_python_venv( + PATH_TO_VENV + "${${PROJECT_NAME}_PYTHON_VENV_PATH}" + INCLUDE_SYSTEM_SITE_PACKAGES + ) + endif() + ev_activate_python_venv(PATH_TO_VENV "${${PROJECT_NAME}_PYTHON_VENV_PATH}") + endif() + message(STATUS "Set up python executable with Python3_EXECUTABLE=${Python3_EXECUTABLE}") +endmacro()