Util.cmake 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  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. # REQUIRED - Abort if COMMAND doesn't exist.
  8. #
  9. # Single value arguments:
  10. # TARGET - Name of the target
  11. # COMMAND - Path of the command to be run
  12. # GLOB_PAT - Glob pattern to use. Only used if GLOB_DIRS is specified
  13. # TOUCH_STRATEGY - Specify touch strategy, meaning decide how to group files
  14. # and connect them to a specific touch file.
  15. #
  16. # For example, let us say we have file A and B and that we create a touch file
  17. # for each of them, TA and TB. This would essentially make file A and B
  18. # independent of each other, meaning that if I change file A and run the
  19. # target, then the target will only run its commands for file A and ignore
  20. # file B.
  21. #
  22. # Another example: let's say we have file A and B, but now we create only a
  23. # single touch file T for both of them. This would mean that if I change
  24. # either file A or B, then the target will run its commands on both A and B.
  25. # Meaning that even if I only change file A, the target will still run
  26. # commands on both A and B.
  27. #
  28. # The more touch files we create for a target, the fewer commands we'll need
  29. # to rerun, and by extension, the more time we'll save. Unfortunately, the
  30. # more touch files we create the more intermediary targets will be created,
  31. # one for each touch file. This makes listing all targets with
  32. # `cmake --build build --target help` less useful since each touch file will
  33. # be listed. The tradeoff that needs to be done here is between performance
  34. # and "discoverability". As a general guideline: the more popular a target is
  35. # and the more time it takes to run it, the more granular you want your touch
  36. # files to be. Conversely, if a target rarely needs to be run or if it's fast,
  37. # then you should create fewer targets.
  38. #
  39. # Possible values for TOUCH_STRATEGY:
  40. # "SINGLE": create a single touch file for all files.
  41. # "PER_FILE": create a touch file for each file. Defaults to this if
  42. # TOUCH_STRATEGY isn't specified.
  43. # "PER_DIR": create a touch file for each directory.
  44. #
  45. # List arguments:
  46. # FLAGS - List of flags to use after COMMAND
  47. # FILES - List of files to use COMMAND on. It's possible to combine this
  48. # with GLOB_PAT and GLOB_DIRS; the files found by globbing will
  49. # simple be added to FILES
  50. # GLOB_DIRS - The directories to recursively search for files with extension
  51. # GLOB_PAT
  52. # EXCLUDE - List of paths to skip (regex). Works on both directories and
  53. # files.
  54. function(add_glob_target)
  55. cmake_parse_arguments(ARG
  56. "REQUIRED"
  57. "TARGET;COMMAND;GLOB_PAT;TOUCH_STRATEGY"
  58. "FLAGS;FILES;GLOB_DIRS;EXCLUDE"
  59. ${ARGN}
  60. )
  61. if(NOT ARG_COMMAND)
  62. add_custom_target(${ARG_TARGET})
  63. if(ARG_REQUIRED)
  64. add_custom_command(TARGET ${ARG_TARGET}
  65. COMMAND ${CMAKE_COMMAND} -E echo "${ARG_TARGET}: ${ARG_COMMAND} not found"
  66. COMMAND false)
  67. else()
  68. add_custom_command(TARGET ${ARG_TARGET}
  69. COMMAND ${CMAKE_COMMAND} -E echo "${ARG_TARGET} SKIP: ${ARG_COMMAND} not found")
  70. endif()
  71. return()
  72. endif()
  73. foreach(gd ${ARG_GLOB_DIRS})
  74. file(GLOB_RECURSE globfiles_unnormalized ${PROJECT_SOURCE_DIR}/${gd}/${ARG_GLOB_PAT})
  75. set(globfiles)
  76. foreach(f ${globfiles_unnormalized})
  77. file(TO_CMAKE_PATH "${f}" f)
  78. list(APPEND globfiles ${f})
  79. endforeach()
  80. list(APPEND ARG_FILES ${globfiles})
  81. endforeach()
  82. foreach(exclude_pattern ${ARG_EXCLUDE})
  83. list(FILTER ARG_FILES EXCLUDE REGEX ${exclude_pattern})
  84. endforeach()
  85. if(NOT ARG_TOUCH_STRATEGY)
  86. set(ARG_TOUCH_STRATEGY PER_FILE)
  87. endif()
  88. set(POSSIBLE_TOUCH_STRATEGIES SINGLE PER_FILE PER_DIR)
  89. if(NOT ARG_TOUCH_STRATEGY IN_LIST POSSIBLE_TOUCH_STRATEGIES)
  90. message(FATAL_ERROR "Unrecognized value for TOUCH_STRATEGY: ${ARG_TOUCH_STRATEGY}")
  91. endif()
  92. if(ARG_TOUCH_STRATEGY STREQUAL SINGLE)
  93. set(touch_file ${TOUCHES_DIR}/ran-${ARG_TARGET})
  94. add_custom_command(
  95. OUTPUT ${touch_file}
  96. COMMAND ${CMAKE_COMMAND} -E touch ${touch_file}
  97. COMMAND ${ARG_COMMAND} ${ARG_FLAGS} ${ARG_FILES}
  98. WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
  99. DEPENDS ${ARG_FILES})
  100. list(APPEND touch_list ${touch_file})
  101. elseif(ARG_TOUCH_STRATEGY STREQUAL PER_FILE)
  102. set(touch_dir ${TOUCHES_DIR}/${ARG_TARGET})
  103. file(MAKE_DIRECTORY ${touch_dir})
  104. foreach(f ${ARG_FILES})
  105. string(REGEX REPLACE "^${PROJECT_SOURCE_DIR}/" "" tf ${f})
  106. string(REGEX REPLACE "[/.]" "-" tf ${tf})
  107. set(touch_file ${touch_dir}/ran-${tf})
  108. add_custom_command(
  109. OUTPUT ${touch_file}
  110. COMMAND ${CMAKE_COMMAND} -E touch ${touch_file}
  111. COMMAND ${ARG_COMMAND} ${ARG_FLAGS} ${f}
  112. WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
  113. DEPENDS ${f})
  114. list(APPEND touch_list ${touch_file})
  115. endforeach()
  116. elseif(ARG_TOUCH_STRATEGY STREQUAL PER_DIR)
  117. set(touch_dirs)
  118. foreach(f ${ARG_FILES})
  119. get_filename_component(out ${f} DIRECTORY)
  120. list(APPEND touch_dirs ${out})
  121. endforeach()
  122. list(REMOVE_DUPLICATES touch_dirs)
  123. foreach(touch_dir ${touch_dirs})
  124. set(relevant_files)
  125. foreach(f ${ARG_FILES})
  126. get_filename_component(out ${f} DIRECTORY)
  127. if(${touch_dir} STREQUAL ${out})
  128. list(APPEND relevant_files ${f})
  129. endif()
  130. endforeach()
  131. set(td ${TOUCHES_DIR}/${ARG_TARGET})
  132. file(MAKE_DIRECTORY ${td})
  133. string(REGEX REPLACE "^${PROJECT_SOURCE_DIR}/" "" tf ${touch_dir})
  134. string(REGEX REPLACE "[/.]" "-" tf ${tf})
  135. set(touch_file ${td}/ran-${tf})
  136. add_custom_command(
  137. OUTPUT ${touch_file}
  138. COMMAND ${CMAKE_COMMAND} -E touch ${touch_file}
  139. COMMAND ${ARG_COMMAND} ${ARG_FLAGS} ${relevant_files}
  140. WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
  141. DEPENDS ${relevant_files})
  142. list(APPEND touch_list ${touch_file})
  143. endforeach()
  144. endif()
  145. add_custom_target(${ARG_TARGET} DEPENDS ${touch_list})
  146. endfunction()
  147. # Set default build type to Debug. Also limit the list of allowable build types
  148. # to the ones defined in variable allowableBuildTypes.
  149. #
  150. # The correct way to specify build type (for example Release) for
  151. # single-configuration generators (Make and Ninja) is to run
  152. #
  153. # cmake -B build -D CMAKE_BUILD_TYPE=Release
  154. # cmake --build build
  155. #
  156. # while for multi-configuration generators (Visual Studio, Xcode and Ninja
  157. # Multi-Config) is to run
  158. #
  159. # cmake -B build
  160. # cmake --build build --config Release
  161. #
  162. # Passing CMAKE_BUILD_TYPE for multi-config generators will now not only
  163. # not be used, but also generate a warning for the user.
  164. function(set_default_buildtype)
  165. set(allowableBuildTypes Debug Release MinSizeRel RelWithDebInfo)
  166. get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
  167. if(isMultiConfig)
  168. set(CMAKE_CONFIGURATION_TYPES ${allowableBuildTypes} PARENT_SCOPE)
  169. if(CMAKE_BUILD_TYPE)
  170. message(WARNING "CMAKE_BUILD_TYPE specified which is ignored on \
  171. multi-configuration generators. Defaulting to Debug build type.")
  172. endif()
  173. else()
  174. set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "${allowableBuildTypes}")
  175. if(NOT CMAKE_BUILD_TYPE)
  176. message(STATUS "CMAKE_BUILD_TYPE not specified, default is 'Debug'")
  177. set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build" FORCE)
  178. elseif(NOT CMAKE_BUILD_TYPE IN_LIST allowableBuildTypes)
  179. message(FATAL_ERROR "Invalid build type: ${CMAKE_BUILD_TYPE}")
  180. else()
  181. message(STATUS "CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
  182. endif()
  183. endif()
  184. endfunction()