Python.cmake 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. # Filename: Python.cmake
  2. #
  3. # Description: This file provides support functions for building/installing
  4. # Python extension modules and/or pure-Python packages.
  5. #
  6. # Functions:
  7. # add_python_target(target [source1 [source2 ...]])
  8. # install_python_package(path [ARCH/LIB])
  9. # ensure_python_init(path [ARCH] [ROOT] [OVERWRITE])
  10. #
  11. #
  12. # Function: add_python_target(target [EXPORT exp] [COMPONENT comp]
  13. # [source1 [source2 ...]])
  14. # Build the provided source(s) as a Python extension module, linked against the
  15. # Python runtime library.
  16. #
  17. # Note that this also takes care of installation, unlike other target creation
  18. # commands in CMake. The EXPORT and COMPONENT keywords allow passing the
  19. # corresponding options to install(), but default to "Python" otherwise.
  20. #
  21. function(add_python_target target)
  22. if(NOT HAVE_PYTHON)
  23. return()
  24. endif()
  25. string(REGEX REPLACE "^.*\\." "" basename "${target}")
  26. set(sources)
  27. set(component "Python")
  28. set(export "Python")
  29. foreach(arg ${ARGN})
  30. if(arg STREQUAL "COMPONENT")
  31. set(keyword "component")
  32. elseif(arg STREQUAL "EXPORT")
  33. set(keyword "export")
  34. elseif(keyword)
  35. set(${keyword} "${arg}")
  36. unset(keyword)
  37. else()
  38. list(APPEND sources "${arg}")
  39. endif()
  40. endforeach(arg)
  41. string(REGEX REPLACE "\\.[^.]+$" "" namespace "${target}")
  42. string(REPLACE "." "/" slash_namespace "${namespace}")
  43. add_library(${target} ${MODULE_TYPE} ${sources})
  44. target_link_libraries(${target} PKG::PYTHON)
  45. if(BUILD_SHARED_LIBS)
  46. set_target_properties(${target} PROPERTIES
  47. LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${slash_namespace}"
  48. OUTPUT_NAME "${basename}"
  49. PREFIX ""
  50. SUFFIX "${PYTHON_EXTENSION_SUFFIX}")
  51. if(PYTHON_ARCH_INSTALL_DIR)
  52. install(TARGETS ${target} EXPORT "${export}" COMPONENT "${component}" DESTINATION "${PYTHON_ARCH_INSTALL_DIR}/${slash_namespace}")
  53. endif()
  54. else()
  55. set_target_properties(${target} PROPERTIES
  56. OUTPUT_NAME "${basename}"
  57. PREFIX "libpy.${namespace}.")
  58. install(TARGETS ${target} EXPORT "${export}" COMPONENT "${component}" DESTINATION lib)
  59. endif()
  60. set(keywords OVERWRITE ARCH)
  61. if(NOT slash_namespace MATCHES ".*/.*")
  62. list(APPEND keywords ROOT)
  63. endif()
  64. ensure_python_init("${PROJECT_BINARY_DIR}/${slash_namespace}" ${keywords})
  65. endfunction(add_python_target)
  66. #
  67. # Function: install_python_package(name [SOURCE path] [ARCH/LIB] [COMPONENT component])
  68. #
  69. # Installs the Python package `name` (which may have its source at `path`).
  70. #
  71. # The package is copied to (or created in) the build directory so that the user
  72. # may import it before the install step.
  73. #
  74. # Note that this handles more than just installation; it will also invoke
  75. # Python's compileall utility to pregenerate .pyc/.pyo files. This will only
  76. # happen if the Python interpreter is found.
  77. #
  78. # The ARCH or LIB keyword may be used to specify whether this package should be
  79. # installed into Python's architecture-dependent or architecture-independent
  80. # package path. The default, if unspecified, is LIB.
  81. #
  82. # The COMPONENT keyword overrides the install component (see CMake's
  83. # documentation for more information on what this does). The default is
  84. # "Python".
  85. #
  86. function(install_python_package package_name)
  87. set(type "LIB")
  88. unset(keyword)
  89. set(component "Python")
  90. unset(src_path)
  91. foreach(arg ${ARGN})
  92. if(arg STREQUAL "ARCH")
  93. set(type "ARCH")
  94. elseif(arg STREQUAL "LIB")
  95. set(type "LIB")
  96. elseif(arg STREQUAL "COMPONENT")
  97. set(keyword "${arg}")
  98. elseif(keyword STREQUAL "COMPONENT")
  99. set(component "${arg}")
  100. unset(keyword)
  101. elseif(arg STREQUAL "SOURCE")
  102. set(keyword "${arg}")
  103. elseif(keyword STREQUAL "SOURCE")
  104. set(src_path "${arg}")
  105. unset(keyword)
  106. else()
  107. message(FATAL_ERROR "install_python_package got unexpected argument: ${ARGN}")
  108. endif()
  109. endforeach(arg)
  110. set(path "${PROJECT_BINARY_DIR}/${package_name}")
  111. set(args -D "OUTPUT_DIR=${path}")
  112. if(src_path)
  113. list(APPEND args -D "SOURCE_DIR=${src_path}")
  114. endif()
  115. if(PYTHON_EXECUTABLE)
  116. list(APPEND args -D "PYTHON_EXECUTABLES=${PYTHON_EXECUTABLE}")
  117. endif()
  118. add_custom_target(${package_name} ALL
  119. COMMAND ${CMAKE_COMMAND}
  120. ${args}
  121. -P "${CMAKE_SOURCE_DIR}/cmake/scripts/CopyPython.cmake")
  122. set(dir "${PYTHON_${type}_INSTALL_DIR}")
  123. if(dir)
  124. install(DIRECTORY "${path}" DESTINATION "${dir}"
  125. COMPONENT "${component}"
  126. FILES_MATCHING REGEX "\\.py[co]?$")
  127. endif()
  128. endfunction(install_python_package)
  129. #
  130. # Function: ensure_python_init(path [ARCH] [ROOT] [OVERWRITE])
  131. #
  132. # Makes sure that the directory - at `path` - contains a file named
  133. # '__init__.py', which is necessary for Python to recognize the directory as a
  134. # package.
  135. #
  136. # ARCH, if specified, means that this is a binary package, and the build tree
  137. # might contain configuration-specific subdirectories. The __init__.py will be
  138. # generated with a function that ensures that the appropriate configuration
  139. # subdirectory is in the path.
  140. #
  141. # ROOT, if specified, means that the directory may sit directly adjacent to a
  142. # 'bin' directory, which should be added to the DLL search path on Windows.
  143. #
  144. # OVERWRITE causes the __init__.py file to be overwritten if one is already
  145. # present.
  146. #
  147. function(ensure_python_init path)
  148. set(arch OFF)
  149. set(root OFF)
  150. set(overwrite OFF)
  151. foreach(arg ${ARGN})
  152. if(arg STREQUAL "ARCH")
  153. set(arch ON)
  154. elseif(arg STREQUAL "ROOT")
  155. set(root ON)
  156. elseif(arg STREQUAL "OVERWRITE")
  157. set(overwrite ON)
  158. else()
  159. message(FATAL_ERROR "ensure_python_init got unexpected argument: ${arg}")
  160. endif()
  161. endforeach(arg)
  162. set(init_filename "${path}/__init__.py")
  163. if(EXISTS "${init_filename}" AND NOT overwrite)
  164. return()
  165. endif()
  166. file(WRITE "${init_filename}" "")
  167. if(arch AND NOT "${CMAKE_CFG_INTDIR}" STREQUAL ".")
  168. # ARCH set, and this is a multi-configuration generator
  169. set(configs "${CMAKE_CONFIGURATION_TYPES}")
  170. # Debug should be at the end (highest preference)
  171. list(REMOVE_ITEM configs "Debug")
  172. list(APPEND configs "Debug")
  173. string(REPLACE ";" "', '" configs "${configs}")
  174. file(APPEND "${init_filename}" "
  175. def _fixup_path():
  176. try:
  177. path = __path__[0]
  178. except (NameError, IndexError):
  179. return # Not a package, or not on filesystem
  180. import os
  181. abspath = os.path.abspath(path)
  182. newpath = None
  183. for config in ['${configs}']:
  184. cfgpath = os.path.join(abspath, config)
  185. if not os.path.isdir(cfgpath):
  186. continue
  187. newpath = cfgpath
  188. if config.lower() == os.environ.get('CMAKE_CONFIGURATION', '').lower():
  189. break
  190. if newpath:
  191. __path__.insert(0, newpath)
  192. _fixup_path()
  193. del _fixup_path
  194. ")
  195. endif()
  196. if(root AND WIN32 AND NOT CYGWIN)
  197. # ROOT set, and this is Windows
  198. file(APPEND "${init_filename}" "
  199. def _fixup_dlls():
  200. try:
  201. path = __path__[0]
  202. except (NameError, IndexError):
  203. return # Not a package, or not on filesystem
  204. import os
  205. relpath = os.path.relpath(path, __path__[-1])
  206. dll_path = os.path.abspath(os.path.join(__path__[-1], '../bin', relpath))
  207. if not os.path.isdir(dll_path):
  208. return
  209. os_path = os.environ.get('PATH', '')
  210. os_path = os_path.split(os.pathsep) if os_path else []
  211. os_path.insert(0, dll_path)
  212. os.environ['PATH'] = os.pathsep.join(os_path)
  213. _fixup_dlls()
  214. del _fixup_dlls
  215. ")
  216. endif()
  217. endfunction(ensure_python_init)