ctkMacroCompilePythonScript.cmake 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. #
  2. # Based on ParaView/VTK/Utilities/vtkTclTest2Py/CMakeLists.txt and
  3. # ParaView/VTK/Wrapping/Python/CMakeLists.txt
  4. #
  5. #
  6. # By globally defining the variable CTK_COMPILE_PYTHON_SCRIPTS_GLOBAL_TARGET_NAME to a
  7. # non-empty string or by specifying the macro option 'GLOBAL_TARGET',
  8. # the following targets will be defined for the whole build system:
  9. # - Copy<GLOBAL_TARGET_NAME>PythonResourceFiles
  10. # - Copy<GLOBAL_TARGET_NAME>PythonScriptFiles
  11. # - Compile<GLOBAL_TARGET_NAME>PythonFiles
  12. #
  13. # For complex projects, this can help reducing the number of targets and
  14. # simplify the manual rebuild of copy and compile targets.
  15. #
  16. include(${CTK_CMAKE_DIR}/ctkMacroParseArguments.cmake)
  17. set(CTK_PYTHON_COMPILE_FILE_SCRIPT_DIR "${CMAKE_BINARY_DIR}/CMakeFiles")
  18. #! \ingroup CMakeAPI
  19. macro(ctkMacroCompilePythonScript)
  20. ctkMacroParseArguments(MY
  21. "TARGET_NAME;SCRIPTS;RESOURCES;SOURCE_DIR;DESTINATION_DIR;INSTALL_DIR"
  22. "NO_INSTALL_SUBDIR;GLOBAL_TARGET"
  23. ${ARGN}
  24. )
  25. # Sanity checks
  26. foreach(varname TARGET_NAME SCRIPTS DESTINATION_DIR INSTALL_DIR)
  27. if(NOT DEFINED MY_${varname})
  28. message(FATAL_ERROR "${varname} is mandatory")
  29. endif()
  30. endforeach()
  31. if(NOT DEFINED MY_SOURCE_DIR)
  32. set(MY_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
  33. endif()
  34. if("${CTK_COMPILE_PYTHON_SCRIPTS_GLOBAL_TARGET_NAME}" STREQUAL "")
  35. set(target ${MY_TARGET_NAME})
  36. else()
  37. set(MY_GLOBAL_TARGET TRUE)
  38. set(target ${CTK_COMPILE_PYTHON_SCRIPTS_GLOBAL_TARGET_NAME})
  39. endif()
  40. # Since 'add_custom_command' doesn't play nicely with path having multiple
  41. # consecutive slashes. Let's make sure there are no trailing slashes.
  42. get_filename_component(MY_SOURCE_DIR ${MY_SOURCE_DIR} REALPATH)
  43. get_filename_component(MY_DESTINATION_DIR ${MY_DESTINATION_DIR} REALPATH)
  44. set(input_python_files)
  45. foreach(file ${MY_SCRIPTS})
  46. # Append "py" extension if needed
  47. get_filename_component(file_ext ${file} EXT)
  48. if(NOT "${file_ext}" MATCHES "py")
  49. set(file "${file}.py")
  50. endif()
  51. set(src "${MY_SOURCE_DIR}/${file}")
  52. set(tgt_file ${file})
  53. if(IS_ABSOLUTE ${file})
  54. set(src ${file})
  55. file(RELATIVE_PATH tgt_file ${CMAKE_CURRENT_BINARY_DIR} ${file})
  56. endif()
  57. set_property(GLOBAL APPEND PROPERTY
  58. _CTK_${target}_PYTHON_SCRIPTS "${src}|${tgt_file}|${MY_DESTINATION_DIR}")
  59. endforeach()
  60. if(DEFINED MY_RESOURCES)
  61. set(resource_input_files)
  62. foreach(file ${MY_RESOURCES})
  63. set(src "${CMAKE_CURRENT_SOURCE_DIR}/${file}")
  64. set_property(GLOBAL APPEND PROPERTY
  65. _CTK_${target}_PYTHON_RESOURCES "${src}|${file}|${MY_DESTINATION_DIR}")
  66. endforeach()
  67. endif()
  68. set(MY_DIRECTORY_TO_INSTALL ${MY_DESTINATION_DIR})
  69. if(MY_NO_INSTALL_SUBDIR)
  70. set(MY_DIRECTORY_TO_INSTALL ${MY_DESTINATION_DIR}/)
  71. endif()
  72. # Install python module / resources directory
  73. install(DIRECTORY "${MY_DIRECTORY_TO_INSTALL}"
  74. DESTINATION "${MY_INSTALL_DIR}" COMPONENT RuntimeLibraries
  75. USE_SOURCE_PERMISSIONS)
  76. if(NOT MY_GLOBAL_TARGET)
  77. ctkFunctionAddCompilePythonScriptTargets(${target})
  78. endif()
  79. endmacro()
  80. function(_ctk_add_copy_python_files_target target type)
  81. # 'type' is expected to be either "Resource" or "Script"
  82. set(target_name Copy${target}Python${type}Files)
  83. if(NOT TARGET ${target_name})
  84. string(TOUPPER ${type} type_upper)
  85. get_property(entries GLOBAL PROPERTY _CTK_${target}_PYTHON_${type_upper}S)
  86. set(input_files)
  87. set(copied_files)
  88. foreach(entry IN LISTS entries)
  89. string(REPLACE "|" ";" tuple "${entry}")
  90. list(GET tuple 0 src)
  91. list(GET tuple 1 tgt_file)
  92. list(GET tuple 2 dest_dir)
  93. set(tgt ${dest_dir}/${tgt_file})
  94. add_custom_command(DEPENDS ${src}
  95. COMMAND ${CMAKE_COMMAND} -E copy ${src} ${tgt}
  96. OUTPUT ${tgt}
  97. COMMENT "Copying python ${type}: ${tgt_file}")
  98. list(APPEND input_files ${src})
  99. list(APPEND copied_files ${tgt})
  100. endforeach()
  101. if(entries)
  102. add_custom_target(${target_name} ALL
  103. DEPENDS
  104. ${input_files}
  105. ${copied_files}
  106. )
  107. endif()
  108. endif()
  109. endfunction()
  110. function(_ctk_add_compile_python_directories_target target)
  111. set(target_name Compile${target}PythonFiles)
  112. if(NOT TARGET ${target_name})
  113. # Byte compile the Python files.
  114. set(compile_all_script "${CMAKE_CURRENT_BINARY_DIR}/compile_${target}_python_scripts.py")
  115. set(_compileall_code )
  116. get_property(entries GLOBAL PROPERTY _CTK_${target}_PYTHON_SCRIPTS)
  117. list(REMOVE_DUPLICATES entries)
  118. foreach(entry IN LISTS entries)
  119. string(REPLACE "|" ";" tuple "${entry}")
  120. list(GET tuple 1 tgt_file)
  121. list(GET tuple 2 dest_dir)
  122. set(tgt ${dest_dir}/${tgt_file})
  123. set(_compileall_code "${_compileall_code}\nctk_compile_file('${tgt}', force=1)")
  124. endforeach()
  125. find_package(PythonInterp REQUIRED)
  126. find_package(PythonLibs REQUIRED)
  127. # Extract python lib path
  128. get_filename_component(PYTHON_LIBRARY_PATH ${PYTHON_LIBRARY} PATH)
  129. # Configure cmake script associated with the custom command
  130. # required to properly update the library path with PYTHON_LIBRARY_PATH
  131. set(compile_all_cmake_script "${CMAKE_CURRENT_BINARY_DIR}/compile_${target}_python_scripts.cmake")
  132. file(WRITE ${compile_all_cmake_script} "
  133. #
  134. # Generated by ctkMacroCompilePythonScript CMAKE macro
  135. #
  136. # Generate compile_${target}_python_scripts.py
  137. file(WRITE \"@compile_all_script@\" \"
  138. #
  139. # Generated by ctkMacroCompilePythonScript CMake macro
  140. #
  141. #
  142. # Copied function 'compileall.compile_file' introduced in python 2.7 so that code compiled
  143. # using python 2.6 works.
  144. #
  145. # This version of the function has been copied from:
  146. # https://github.com/jonashaag/cpython/blob/ce5e5df0c9d8098da05dee26e12ffe2aa331889e/Lib/compileall.py#L61-111
  147. #
  148. import os
  149. import sys
  150. import py_compile
  151. import struct
  152. import imp
  153. def ctk_compile_file(fullname, ddir=None, force=0, rx=None, quiet=0):
  154. \\\"\\\"\\\"Byte-compile one file.
  155. Arguments (only fullname is required):
  156. fullname: the file to byte-compile
  157. ddir: if given, the directory name compiled in to the
  158. byte-code file.
  159. force: if 1, force compilation, even if timestamps are up-to-date
  160. quiet: if 1, be quiet during compilation
  161. \\\"\\\"\\\"
  162. success = 1
  163. name = os.path.basename(fullname)
  164. if ddir is not None:
  165. dfile = os.path.join(ddir, name)
  166. else:
  167. dfile = None
  168. if rx is not None:
  169. mo = rx.search(fullname)
  170. if mo:
  171. return success
  172. if os.path.isfile(fullname):
  173. head, tail = name[:-3], name[-3:]
  174. if tail == '.py':
  175. if not force:
  176. try:
  177. mtime = int(os.stat(fullname).st_mtime)
  178. expect = struct.pack('<4sl', imp.get_magic(), mtime)
  179. cfile = fullname + (__debug__ and 'c' or 'o')
  180. with open(cfile, 'rb') as chandle:
  181. actual = chandle.read(8)
  182. if expect == actual:
  183. return success
  184. except IOError:
  185. pass
  186. if not quiet:
  187. print 'Compiling', fullname, '...'
  188. try:
  189. ok = py_compile.compile(fullname, None, dfile, True)
  190. except py_compile.PyCompileError,err:
  191. if quiet:
  192. print 'Compiling', fullname, '...'
  193. print err.msg
  194. success = 0
  195. except IOError, e:
  196. print "Sorry", e
  197. success = 0
  198. else:
  199. if ok == 0:
  200. success = 0
  201. return success
  202. # Based on paraview/VTK/Wrapping/Python/compile_all_vtk.py.in
  203. @_compileall_code@
  204. file = open('@CMAKE_CURRENT_BINARY_DIR@/python_compile_@target@_complete', 'w')
  205. file.write('Done')
  206. \")
  207. set(ENV{PYTHONPATH} \"@CTK_PYTHON_COMPILE_FILE_SCRIPT_DIR@\")
  208. if(WIN32)
  209. set(ENV{PATH} \"@PYTHON_LIBRARY_PATH@;\$ENV{PATH}\")
  210. elseif(APPLE)
  211. set(ENV{DYLD_LIBRARY_PATH} \"@PYTHON_LIBRARY_PATH@:\$ENV{DYLD_LIBRARY_PATH}\")
  212. else()
  213. set(ENV{LD_LIBRARY_PATH} \"@PYTHON_LIBRARY_PATH@:\$ENV{LD_LIBRARY_PATH}\")
  214. endif()
  215. execute_process(
  216. COMMAND \"@PYTHON_EXECUTABLE@\" \"@compile_all_script@\"
  217. RESULT_VARIABLE result_var
  218. )
  219. if(NOT result_var STREQUAL 0)
  220. message(FATAL_ERROR \"Failed to compile python scripts: ${target} \")
  221. endif()
  222. ")
  223. add_custom_command(
  224. COMMAND ${CMAKE_COMMAND} -P ${compile_all_cmake_script}
  225. DEPENDS Copy${target}PythonScriptFiles
  226. OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/python_compile_${target}_complete"
  227. COMMENT "Compiling python scripts: ${target}"
  228. )
  229. add_custom_target(${target_name} ALL
  230. DEPENDS
  231. ${CMAKE_CURRENT_BINARY_DIR}/python_compile_${target}_complete
  232. ${compile_all_cmake_script}
  233. )
  234. endif()
  235. endfunction()
  236. function(ctkFunctionAddCompilePythonScriptTargets target)
  237. _ctk_add_copy_python_files_target(${target} Script)
  238. _ctk_add_copy_python_files_target(${target} Resource)
  239. _ctk_add_compile_python_directories_target(${target})
  240. endfunction()