Util.cmake 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. # Defines a target that depends on FILES and the files found by globbing
  2. # when using GLOB_PAT and GLOB_DIRS. The target will rerun if any files it
  3. # depends on has changed. Which files the target will run the command on
  4. # depends on the value of TOUCH_STRATEGY.
  5. #
  6. # Options:
  7. #
  8. # Single value arguments:
  9. # TARGET - Name of the target
  10. # COMMAND - Path of the command to be run
  11. # GLOB_PAT - Glob pattern to use. Only used if GLOB_DIRS is specified
  12. # TOUCH_STRATEGY - Specify touch strategy, meaning decide how to group files
  13. # and connect them to a specific touch file.
  14. #
  15. # For example, let us say we have file A and B and that we create a touch file
  16. # for each of them, TA and TB. This would essentially make file A and B
  17. # independent of each other, meaning that if I change file A and run the
  18. # target, then the target will only run its commands for file A and ignore
  19. # file B.
  20. #
  21. # Another example: let's say we have file A and B, but now we create only a
  22. # single touch file T for both of them. This would mean that if I change
  23. # either file A or B, then the target will run its commands on both A and B.
  24. # Meaning that even if I only change file A, the target will still run
  25. # commands on both A and B.
  26. #
  27. # The more touch files we create for a target, the fewer commands we'll need
  28. # to rerun, and by extension, the more time we'll save. Unfortunately, the
  29. # more touch files we create the more intermediary targets will be created,
  30. # one for each touch file. This makes listing all targets with
  31. # `cmake --build build --target help` less useful since each touch file will
  32. # be listed. The tradeoff that needs to be done here is between performance
  33. # and "discoverability". As a general guideline: the more popular a target is
  34. # and the more time it takes to run it, the more granular you want your touch
  35. # files to be. Conversely, if a target rarely needs to be run or if it's fast,
  36. # then you should create fewer targets.
  37. #
  38. # Possible values for TOUCH_STRATEGY:
  39. # "SINGLE": create a single touch file for all files.
  40. # "PER_FILE": create a touch file for each file. Defaults to this if
  41. # TOUCH_STRATEGY isn't specified.
  42. # "PER_DIR": create a touch file for each directory.
  43. #
  44. # List arguments:
  45. # FLAGS - List of flags to use after COMMAND
  46. # FILES - List of files to use COMMAND on. It's possible to combine this
  47. # with GLOB_PAT and GLOB_DIRS; the files found by globbing will
  48. # simple be added to FILES
  49. # GLOB_DIRS - The directories to recursively search for files with extension
  50. # GLOB_PAT
  51. # EXCLUDE - List of paths to skip (regex). Works on both directories and
  52. # files.
  53. function(add_glob_target)
  54. cmake_parse_arguments(ARG
  55. ""
  56. "TARGET;COMMAND;GLOB_PAT;TOUCH_STRATEGY"
  57. "FLAGS;FILES;GLOB_DIRS;EXCLUDE"
  58. ${ARGN}
  59. )
  60. if(NOT ARG_COMMAND)
  61. add_custom_target(${ARG_TARGET})
  62. add_custom_command(TARGET ${ARG_TARGET}
  63. POST_BUILD
  64. COMMAND ${CMAKE_COMMAND} -E echo "${ARG_TARGET} SKIP: ${ARG_COMMAND} not found")
  65. return()
  66. endif()
  67. foreach(gd ${ARG_GLOB_DIRS})
  68. file(GLOB_RECURSE globfiles_unnormalized ${PROJECT_SOURCE_DIR}/${gd}/${ARG_GLOB_PAT})
  69. set(globfiles)
  70. foreach(f ${globfiles_unnormalized})
  71. file(TO_CMAKE_PATH "${f}" f)
  72. list(APPEND globfiles ${f})
  73. endforeach()
  74. list(APPEND ARG_FILES ${globfiles})
  75. endforeach()
  76. list(APPEND ARG_EXCLUDE runtime/lua/vim/_meta) # only generated files, always ignore
  77. foreach(exclude_pattern ${ARG_EXCLUDE})
  78. list(FILTER ARG_FILES EXCLUDE REGEX ${exclude_pattern})
  79. endforeach()
  80. if(NOT ARG_TOUCH_STRATEGY)
  81. set(ARG_TOUCH_STRATEGY PER_FILE)
  82. endif()
  83. set(POSSIBLE_TOUCH_STRATEGIES SINGLE PER_FILE PER_DIR)
  84. if(NOT ARG_TOUCH_STRATEGY IN_LIST POSSIBLE_TOUCH_STRATEGIES)
  85. message(FATAL_ERROR "Unrecognized value for TOUCH_STRATEGY: ${ARG_TOUCH_STRATEGY}")
  86. endif()
  87. if(ARG_TOUCH_STRATEGY STREQUAL SINGLE)
  88. set(touch_file ${TOUCHES_DIR}/${ARG_TARGET})
  89. add_custom_command(
  90. OUTPUT ${touch_file}
  91. COMMAND ${CMAKE_COMMAND} -E touch ${touch_file}
  92. COMMAND ${ARG_COMMAND} ${ARG_FLAGS} ${ARG_FILES}
  93. WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
  94. DEPENDS ${ARG_FILES})
  95. list(APPEND touch_list ${touch_file})
  96. elseif(ARG_TOUCH_STRATEGY STREQUAL PER_FILE)
  97. set(touch_dir ${TOUCHES_DIR}/${ARG_TARGET})
  98. file(MAKE_DIRECTORY ${touch_dir})
  99. foreach(f ${ARG_FILES})
  100. string(REGEX REPLACE "^${PROJECT_SOURCE_DIR}/" "" tf ${f})
  101. string(REGEX REPLACE "[/.]" "-" tf ${tf})
  102. set(touch_file ${touch_dir}/${tf})
  103. add_custom_command(
  104. OUTPUT ${touch_file}
  105. COMMAND ${CMAKE_COMMAND} -E touch ${touch_file}
  106. COMMAND ${ARG_COMMAND} ${ARG_FLAGS} ${f}
  107. WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
  108. DEPENDS ${f})
  109. list(APPEND touch_list ${touch_file})
  110. endforeach()
  111. elseif(ARG_TOUCH_STRATEGY STREQUAL PER_DIR)
  112. set(touch_dirs)
  113. foreach(f ${ARG_FILES})
  114. get_filename_component(out ${f} DIRECTORY)
  115. list(APPEND touch_dirs ${out})
  116. endforeach()
  117. list(REMOVE_DUPLICATES touch_dirs)
  118. foreach(touch_dir ${touch_dirs})
  119. set(relevant_files)
  120. foreach(f ${ARG_FILES})
  121. get_filename_component(out ${f} DIRECTORY)
  122. if(${touch_dir} STREQUAL ${out})
  123. list(APPEND relevant_files ${f})
  124. endif()
  125. endforeach()
  126. set(td ${TOUCHES_DIR}/${ARG_TARGET})
  127. file(MAKE_DIRECTORY ${td})
  128. string(REGEX REPLACE "^${PROJECT_SOURCE_DIR}/" "" tf ${touch_dir})
  129. string(REGEX REPLACE "[/.]" "-" tf ${tf})
  130. set(touch_file ${td}/${tf})
  131. add_custom_command(
  132. OUTPUT ${touch_file}
  133. COMMAND ${CMAKE_COMMAND} -E touch ${touch_file}
  134. COMMAND ${ARG_COMMAND} ${ARG_FLAGS} ${relevant_files}
  135. WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
  136. DEPENDS ${relevant_files})
  137. list(APPEND touch_list ${touch_file})
  138. endforeach()
  139. endif()
  140. add_custom_target(${ARG_TARGET} DEPENDS ${touch_list})
  141. endfunction()
  142. # A wrapper function that combines add_custom_command and add_custom_target. It
  143. # essentially models the "make" dependency where a target is only rebuilt if
  144. # any dependencies have been changed.
  145. #
  146. # Important to note is that `DEPENDS` is a bit misleading; it should not only
  147. # specify dependencies but also the files that are being generated/output
  148. # files in order to work correctly.
  149. function(add_target)
  150. cmake_parse_arguments(ARG
  151. ""
  152. ""
  153. "COMMAND;DEPENDS;CUSTOM_COMMAND_ARGS"
  154. ${ARGN}
  155. )
  156. set(target ${ARGV0})
  157. set(touch_file ${TOUCHES_DIR}/${target})
  158. add_custom_command(
  159. OUTPUT ${touch_file}
  160. COMMAND ${CMAKE_COMMAND} -E touch ${touch_file}
  161. COMMAND ${CMAKE_COMMAND} -E env "VIMRUNTIME=${NVIM_RUNTIME_DIR}" ${ARG_COMMAND}
  162. WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
  163. DEPENDS ${ARG_DEPENDS}
  164. ${ARG_CUSTOM_COMMAND_ARGS})
  165. add_custom_target(${target} DEPENDS ${touch_file})
  166. endfunction()
  167. # Set default build type to BUILD_TYPE.
  168. #
  169. # The correct way to specify build type (for example Release) for
  170. # single-configuration generators (Make and Ninja) is to run
  171. #
  172. # cmake -B build -D CMAKE_BUILD_TYPE=Release
  173. # cmake --build build
  174. #
  175. # while for multi-configuration generators (Visual Studio, Xcode and Ninja
  176. # Multi-Config) is to run
  177. #
  178. # cmake -B build
  179. # cmake --build build --config Release
  180. #
  181. # Passing CMAKE_BUILD_TYPE for multi-config generators will not only not be
  182. # used, but also generate a warning for the user.
  183. function(set_default_buildtype BUILD_TYPE)
  184. set(defaultBuildTypes Debug Release MinSizeRel RelWithDebInfo)
  185. get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
  186. if(isMultiConfig)
  187. # Multi-config generators use the first element in
  188. # CMAKE_CONFIGURATION_TYPES as the default build type
  189. list(INSERT defaultBuildTypes 0 ${BUILD_TYPE})
  190. list(REMOVE_DUPLICATES defaultBuildTypes)
  191. set(CMAKE_CONFIGURATION_TYPES ${defaultBuildTypes} PARENT_SCOPE)
  192. if(CMAKE_BUILD_TYPE)
  193. message(WARNING "CMAKE_BUILD_TYPE specified which is ignored on \
  194. multi-configuration generators. Defaulting to ${BUILD_TYPE} build type.")
  195. endif()
  196. else()
  197. set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "${defaultBuildTypes}")
  198. if(NOT CMAKE_BUILD_TYPE)
  199. message(STATUS "CMAKE_BUILD_TYPE not specified, default is '${BUILD_TYPE}'")
  200. set(CMAKE_BUILD_TYPE ${BUILD_TYPE} CACHE STRING "Choose the type of build" FORCE)
  201. else()
  202. message(STATUS "CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
  203. endif()
  204. endif()
  205. endfunction()
  206. # Check if a module is available in Lua
  207. function(check_lua_module LUA_PRG_PATH MODULE RESULT_VAR)
  208. execute_process(COMMAND ${LUA_PRG_PATH} -l "${MODULE}" -e ""
  209. RESULT_VARIABLE module_missing)
  210. if(module_missing)
  211. set(${RESULT_VAR} FALSE PARENT_SCOPE)
  212. else()
  213. set(${RESULT_VAR} TRUE PARENT_SCOPE)
  214. endif()
  215. endfunction()