# Python functions for the kwiver project # The following functions are defined: # # kwiver_add_python_library # kwiver_add_python_module # kwiver_create_python_init # # The following variables may be used to control the behavior of the functions: # # copyright_header # The copyright header to place at the top of generated __init__.py files. # # python_both_arch # If set, __init__.py file is created for both the archful and pure-Python # module paths (if in doubt, you probably don't need this; it's necessary # to support CPython and pure Python kwiver plugins). # # Their syntax is: # # kwiver_add_python_library(name modpath [source ...]) # Builds and installs a library to be used as a Python module which may be # imported. It is built as a shared library, installed (use no_install to # not install the module), placed into the proper subdirectory but not # exported. Any other control variables for kwiver_add_library are # available. # # kwiver_add_python_module(name modpath module) # Installs a pure-Python module into the 'modpath' and puts it into the # correct place in the build tree so that it may be used with any built # libraries in any build configuration. # # kwiver_create_python_init(modpath [module ...]) # Creates an __init__.py package file which imports the modules in the # arguments for the package. # # NOTE: Utilities here assume that that `kwiver-setup-python.cmake` has been # included. # if ( NOT TARGET python) add_custom_target(python) endif() source_group("Python Files" REGULAR_EXPRESSION ".*\\.py\\.in$") source_group("Python Files" REGULAR_EXPRESSION ".*\\.py$") # Global collection variables define_property(GLOBAL PROPERTY kwiver_python_modules BRIEF_DOCS "Python modules generated by kwiver" FULL_DOCS "List of Python modules build." ) ### # Change a filesystem path into something that is acceptable in CMake target # names. # # This effectively replaces "/"s with "."s. # macro (_kwiver_create_safe_modpath modpath result) string(REPLACE "/" "." "${result}" "${modpath}") endmacro () ### # kwiver_add_python_library( name modpath ) # # Create a target for a python module library. This is a shared library that may # be imported in the python interpreter. # # This function includes target linking similar to the ``kwiver_add_plugin`` # utility function. Python libraries will automatically be included in the # PRIVATE linkage of the library. # # Options are: # SOURCES - list of source files for this library. # PUBLIC - list of libraries the plugin will publicly link against. # PRIVATE - list of libraries the plugin will privately link against. # function (kwiver_add_python_library name modpath) set(options) set(oneValueArgs) set(multiValueArgs SOURCES PUBLIC PRIVATE) cmake_parse_arguments(PYLIB "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) _kwiver_create_safe_modpath("${modpath}" safe_modpath) string(TOLOWER "${CMAKE_PROJECT_NAME}" project_name) if(SKBUILD) # This is required as libraries are installed into the python package just # as they are installed by CMake. Setting library_* in the following way # causes the python libraries to be placed into the build and install trees # as if the build/install root was the python package root. set(library_dir "") set(library_subdir "${modpath}") else() if(WIN32) # do not prepend "/Lib" . It is already contained in kwiver_python_subdir set(library_dir "") endif() set(library_subdir "/${kwiver_python_subdir}/${python_sitename}/${project_name}/${modpath}") endif() set(component runtime) set(no_export ON) set(no_export_header ON) set(pylib_target_name "python-${safe_modpath}-${name}") kwiver_add_library("${pylib_target_name}" MODULE ${PYLIB_SOURCES}) # Given the documented intention that these are python libraries, we will # implicitly include the python libraries under the PRIVATE linkage. # This assumes ``find_package(Python)`` was performed before invoking this # function, which should be a general requirement anyway. # we link to pybind11::module instead of directly to PYTHON_LIBRARIES. This # is required when building wheel while still makes the code work outside # the wheels. list(INSERT PYLIB_PRIVATE 0 pybind11::module) target_link_libraries( "${pylib_target_name}" PUBLIC ${PYLIB_PUBLIC} PRIVATE ${PYLIB_PRIVATE} ) if(MSVC) # Issues arise with the msvc compiler with some projects where it cannot # compile bindings without the optimizer expanding some inline functions (i.e. debug builds) # So always have the optimizer expand the inline functions in the python bindings projects target_compile_options("${pylib_target_name}" PUBLIC "/Ob2") endif() set(pysuffix "${CMAKE_SHARED_MODULE_SUFFIX}") if (WIN32 AND NOT CYTWIN) set(pysuffix .pyd) endif () set_target_properties("${pylib_target_name}" PROPERTIES OUTPUT_NAME "${name}" PREFIX "" SUFFIX "${pysuffix}" ) add_dependencies(python "${pylib_target_name}") set_property(GLOBAL APPEND PROPERTY kwiver_python_modules ${name}) endfunction () ### # # kwiver_add_python_module(path modpath module) # # Installs a pure-Python module into the 'modpath' and puts it into the # correct place in the build tree so that it may be used with any built # libraries in any build configuration. # # When building with scikit-build, this function does nothing. # # Args: # path: Path to the python source (e.g. kwiver_process.py) # modpath: Python module path (e.g. kwiver/processes) # module: Python module name. This is the name used to import the code. # (e.g. kwiver_process) # # SeeAlso: # kwiver/CMake/utils/kwiver-utils-configuration.cmake # ../../sprokit/conf/sprokit-macro-python.cmake # ../../sprokit/processes/bindings/python/kwiver/CMakeLists.txt # ../../sprokit/processes/bindings/python/kwiver/util/CMakeLists.txt function (kwiver_add_python_module path modpath module) if( SKBUILD ) # Scikit-build and setuptools already have python module installation # handled. We don't need to explicitly install it ourselves. return() endif () _kwiver_create_safe_modpath("${modpath}" safe_modpath) string(TOLOWER "${CMAKE_PROJECT_NAME}" project_name) set(python_arch) set(python_noarchdir) if (WIN32) if (python_noarch) return () endif () else () if (python_noarch) set(python_noarchdir /noarch) set(python_arch u) endif () endif () if (CMAKE_CONFIGURATION_TYPES AND NOT SKBUILD) set(kwiver_configure_cmake_args "\"-Dconfig=${CMAKE_CFG_INTDIR}/\"") set(kwiver_configure_extra_dests "${kwiver_python_output_path}/${python_noarchdir}\${config}/${python_sitename}/${project_name}/${modpath}/${module}.py") endif () set(pyfile_src "${path}") # SKBUILD associates the package root with CMAKE_INSTALL_PREFIX # in source build this would be kwiver_python_install_path/project_name if(SKBUILD) set(pyfile_dst "${CMAKE_BINARY_DIR}/${modpath}/${module}.py") set(mod_dst "${CMAKE_BINARY_DIR}/${modpath}/" PARENT_SCOPE) # installation path for this module set(pypkg_install_path "${CMAKE_INSTALL_PREFIX}/${modpath}") else() set(pyfile_dst "${kwiver_python_output_path}${python_noarchdir}/${python_sitename}/${project_name}/${modpath}/${module}.py") set(mod_dst "${kwiver_python_output_path}${python_noarchdir}/${python_sitename}/${project_name}/${modpath}/" PARENT_SCOPE) # installation path for this module set(pypkg_install_path "${kwiver_python_install_path}/${project_name}/${modpath}") endif() # copy and configure the source file into the binary directory if (KWIVER_SYMLINK_PYTHON) kwiver_symlink_file("python${python_arch}-${safe_modpath}-${module}" "${pyfile_src}" "${pyfile_dst}" PYTHON_EXECUTABLE) else() kwiver_configure_file("python${python_arch}-${safe_modpath}-${module}" "${pyfile_src}" "${pyfile_dst}" PYTHON_EXECUTABLE) endif() # install the configured binary to pypkg_install_path kwiver_install( FILES "${pyfile_dst}" DESTINATION "${pypkg_install_path}" COMPONENT runtime) add_dependencies(python "configure-python${python_arch}-${safe_modpath}-${module}") if (python_both_arch) set(python_both_arch) set(python_noarch TRUE) if (NOT WIN32) # this looks recursive kwiver_add_python_module( "${path}" "${modpath}" "${module}") endif () endif () endfunction () ### # kwiver_create_python_init(modpath [module ...]) # # Creates an __init__.py file for a core package which imports the modules # in the arguments for the package. # # TODO: What is this for? # function (kwiver_create_python_init modpath) set(python_arch) set(python_noarchdir) if (NOT WIN32) if (python_noarch) set(python_noarchdir /noarch) set(python_arch u) endif () endif () string(TOLOWER "${CMAKE_PROJECT_NAME}" project_name) if(SKBUILD) set (init_path "${CMAKE_BINARY_DIR}/${modpath}/__init__.py") else() set (init_path "${kwiver_python_output_path}${python_noarchdir}/${python_sitename}/${project_name}/${modpath}/__init__.py") endif() if (NOT EXISTS "${init_path}") if (NOT copyright_header) set(copyright_header "# Generated by kwiver") endif () file(WRITE "${init_path}" "${copyright_header}\n\n") endif() # Abolute import is generated only when ARGN is used if (${ARGC} GREATER 1) file(READ "${init_path}" init_file) # Check for the string before appending set (abs_import "from __future__ import absolute_import") string(FIND "${init_file}" "${abs_import}" match) message( STATUS "Absolute import ${match}") if (${match} EQUAL -1) file (APPEND "${init_path}" "${abs_import}\n\n") endif() foreach (module IN LISTS ARGN) set (module_import "from .${module} import *") string(FIND "${init_file}" "${module_import}" match) if (${match} EQUAL -1) file (APPEND "${init_path}" "${module_import}\n") endif() endforeach() endif() # SKBUILD associates the package root with CMAKE_INSTALL_PREFIX # in source build this would be kwiver_python_install_path/project_name if(SKBUILD) set ( install_path "${CMAKE_INSTALL_PREFIX}/${modpath}") else() set ( install_path "${kwiver_python_install_path}/${project_name}/${modpath}") endif() kwiver_install( FILES "${init_path}" DESTINATION "${install_path}" COMPONENT runtime) endfunction () macro(get_python_mod_dst) string(REPLACE "${CMAKE_SOURCE_DIR}/python/kwiver/" "" modpath "${CMAKE_CURRENT_SOURCE_DIR}" ) if(SKBUILD) set(mod_dst "${CMAKE_BINARY_DIR}/${modpath}/") else() set(mod_dst "${kwiver_python_output_path}${python_noarchdir}/${python_sitename}/${project_name}/${modpath}/") endif() endmacro(get_python_mod_dst) ### # wrapper around add_custom_target eventually implementing python specific logic # # function (python_target_add_command cmd_name cust_command comment_) add_custom_target(${cmd_name} ALL COMMAND ${cust_command} COMMENT ${comment_}) if(${ARGC} GREATER 3) add_dependencies(${cmd_name} ${ARGN}) endif() endfunction()