Python.cmake 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  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 [source1 [source2 ...]])
  13. # Build the provided source(s) as a Python extension module, linked against the
  14. # Python runtime library.
  15. #
  16. # Note that this also takes care of installation, unlike other target creation
  17. # commands in CMake.
  18. #
  19. function(add_python_target target)
  20. if(NOT HAVE_PYTHON)
  21. return()
  22. endif()
  23. string(REGEX REPLACE "^.*\\." "" basename "${target}")
  24. set(sources ${ARGN})
  25. string(REGEX REPLACE "\\.[^.]+$" "" namespace "${target}")
  26. string(REPLACE "." "_" underscore_namespace "${namespace}")
  27. string(REPLACE "." "/" slash_namespace "${namespace}")
  28. add_library(${target} ${MODULE_TYPE} ${sources})
  29. target_link_libraries(${target} PKG::PYTHON)
  30. if(BUILD_SHARED_LIBS)
  31. set_target_properties(${target} PROPERTIES
  32. LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/${slash_namespace}"
  33. OUTPUT_NAME "${basename}"
  34. PREFIX ""
  35. SUFFIX "${PYTHON_EXTENSION_SUFFIX}")
  36. if(PYTHON_ARCH_INSTALL_DIR)
  37. install(TARGETS ${target} DESTINATION "${PYTHON_ARCH_INSTALL_DIR}/${slash_namespace}")
  38. endif()
  39. else()
  40. set_target_properties(${target} PROPERTIES
  41. OUTPUT_NAME "${basename}"
  42. PREFIX "libpython_${underscore_namespace}_")
  43. install(TARGETS ${target} DESTINATION lib)
  44. endif()
  45. set(keywords OVERWRITE ARCH)
  46. if(NOT underscore_namespace MATCHES ".*_.*")
  47. list(APPEND keywords ROOT)
  48. endif()
  49. ensure_python_init("${PROJECT_BINARY_DIR}/${slash_namespace}" ${keywords})
  50. endfunction(add_python_target)
  51. #
  52. # Function: install_python_package(path [ARCH/LIB])
  53. #
  54. # Installs the Python package which was built at `path`.
  55. #
  56. # Note that this handles more than just installation; it will also invoke
  57. # Python's compileall utility to pregenerate .pyc/.pyo files. This will only
  58. # happen if the Python interpreter is found.
  59. #
  60. # The ARCH or LIB keyword may be used to specify whether this package should be
  61. # installed into Python's architecture-dependent or architecture-independent
  62. # package path. The default, if unspecified, is LIB.
  63. #
  64. function(install_python_package path)
  65. if(ARGN STREQUAL "ARCH")
  66. set(type "ARCH")
  67. elseif(ARGN STREQUAL "LIB")
  68. set(type "LIB")
  69. elseif(ARGN STREQUAL "")
  70. set(type "LIB")
  71. else()
  72. message(FATAL_ERROR "install_python_package got unexpected argument: ${ARGN}")
  73. endif()
  74. get_filename_component(package_name "${path}" NAME)
  75. set(custom_target "bytecompile_${package_name}")
  76. file(RELATIVE_PATH relpath "${PROJECT_BINARY_DIR}" "${path}")
  77. if(PYTHON_EXECUTABLE)
  78. add_custom_target(${custom_target} ALL)
  79. add_custom_command(
  80. TARGET ${custom_target}
  81. WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
  82. COMMAND "${PYTHON_EXECUTABLE}" -m compileall -q "${relpath}")
  83. add_custom_command(
  84. TARGET ${custom_target}
  85. WORKING_DIRECTORY "${PROJECT_BINARY_DIR}"
  86. COMMAND "${PYTHON_EXECUTABLE}" -OO -m compileall -q "${relpath}")
  87. endif()
  88. ensure_python_init("${path}")
  89. set(dir ${PYTHON_${type}_INSTALL_DIR})
  90. if(dir)
  91. install(DIRECTORY "${path}" DESTINATION "${dir}"
  92. FILES_MATCHING REGEX "\\.py[co]?$")
  93. endif()
  94. endfunction(install_python_package)
  95. #
  96. # Function: ensure_python_init(path [ARCH] [ROOT] [OVERWRITE])
  97. #
  98. # Makes sure that the directory - at `path` - contains a file named
  99. # '__init__.py', which is necessary for Python to recognize the directory as a
  100. # package.
  101. #
  102. # ARCH, if specified, means that this is a binary package, and the build tree
  103. # might contain configuration-specific subdirectories. The __init__.py will be
  104. # generated with a function that ensures that the appropriate configuration
  105. # subdirectory is in the path.
  106. #
  107. # ROOT, if specified, means that the directory may sit directly adjacent to a
  108. # 'bin' directory, which should be added to the DLL search path on Windows.
  109. #
  110. # OVERWRITE causes the __init__.py file to be overwritten if one is already
  111. # present.
  112. #
  113. function(ensure_python_init path)
  114. set(arch OFF)
  115. set(root OFF)
  116. set(overwrite OFF)
  117. foreach(arg ${ARGN})
  118. if(arg STREQUAL "ARCH")
  119. set(arch ON)
  120. elseif(arg STREQUAL "ROOT")
  121. set(root ON)
  122. elseif(arg STREQUAL "OVERWRITE")
  123. set(overwrite ON)
  124. else()
  125. message(FATAL_ERROR "ensure_python_init got unexpected argument: ${arg}")
  126. endif()
  127. endforeach(arg)
  128. set(init_filename "${path}/__init__.py")
  129. if(EXISTS "${init_filename}" AND NOT overwrite)
  130. return()
  131. endif()
  132. file(WRITE "${init_filename}" "")
  133. if(arch AND NOT "${CMAKE_CFG_INTDIR}" STREQUAL ".")
  134. # ARCH set, and this is a multi-configuration generator
  135. set(configs "${CMAKE_CONFIGURATION_TYPES}")
  136. # Debug should be at the end (highest preference)
  137. list(REMOVE_ITEM configs "Debug")
  138. list(APPEND configs "Debug")
  139. string(REPLACE ";" "', '" configs "${configs}")
  140. file(APPEND "${init_filename}" "
  141. def _fixup_path():
  142. try:
  143. path = __path__[0]
  144. except (NameError, IndexError):
  145. return # Not a package, or not on filesystem
  146. import os
  147. abspath = os.path.abspath(path)
  148. newpath = None
  149. for config in ['${configs}']:
  150. cfgpath = os.path.join(abspath, config)
  151. if not os.path.isdir(cfgpath):
  152. continue
  153. newpath = cfgpath
  154. if config.lower() == os.environ.get('CMAKE_CONFIGURATION', '').lower():
  155. break
  156. if newpath:
  157. __path__.insert(0, newpath)
  158. _fixup_path()
  159. del _fixup_path
  160. ")
  161. endif()
  162. if(root AND WIN32 AND NOT CYGWIN)
  163. # ROOT set, and this is Windows
  164. file(APPEND "${init_filename}" "
  165. def _fixup_dlls():
  166. try:
  167. path = __path__[0]
  168. except (NameError, IndexError):
  169. return # Not a package, or not on filesystem
  170. import os
  171. relpath = os.path.relpath(path, __path__[-1])
  172. dll_path = os.path.abspath(os.path.join(__path__[-1], '../bin', relpath))
  173. if not os.path.isdir(dll_path):
  174. return
  175. os_path = os.environ.get('PATH', '')
  176. os_path = os_path.split(os.pathsep) if os_path else []
  177. os_path.insert(0, dll_path)
  178. os.environ['PATH'] = os.pathsep.join(os_path)
  179. _fixup_dlls()
  180. del _fixup_dlls
  181. ")
  182. endif()
  183. endfunction(ensure_python_init)