Util.cmake (8861B)
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 61 if(NOT ARG_COMMAND) 62 add_custom_target(${ARG_TARGET}) 63 add_custom_command(TARGET ${ARG_TARGET} 64 POST_BUILD 65 COMMAND ${CMAKE_COMMAND} -E echo "${ARG_TARGET} SKIP: ${ARG_COMMAND} not found") 66 return() 67 endif() 68 69 foreach(gd ${ARG_GLOB_DIRS}) 70 file(GLOB_RECURSE globfiles_unnormalized ${PROJECT_SOURCE_DIR}/${gd}/${ARG_GLOB_PAT}) 71 set(globfiles) 72 foreach(f ${globfiles_unnormalized}) 73 file(TO_CMAKE_PATH "${f}" f) 74 list(APPEND globfiles ${f}) 75 endforeach() 76 list(APPEND ARG_FILES ${globfiles}) 77 endforeach() 78 79 list(APPEND ARG_EXCLUDE runtime/lua/vim/_meta) # only generated files, always ignore 80 foreach(exclude_pattern ${ARG_EXCLUDE}) 81 list(FILTER ARG_FILES EXCLUDE REGEX ${exclude_pattern}) 82 endforeach() 83 84 if(NOT ARG_TOUCH_STRATEGY) 85 set(ARG_TOUCH_STRATEGY PER_FILE) 86 endif() 87 set(POSSIBLE_TOUCH_STRATEGIES SINGLE PER_FILE PER_DIR) 88 if(NOT ARG_TOUCH_STRATEGY IN_LIST POSSIBLE_TOUCH_STRATEGIES) 89 message(FATAL_ERROR "Unrecognized value for TOUCH_STRATEGY: ${ARG_TOUCH_STRATEGY}") 90 endif() 91 92 if(ARG_TOUCH_STRATEGY STREQUAL SINGLE) 93 set(touch_file ${TOUCHES_DIR}/${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}/${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 124 foreach(touch_dir ${touch_dirs}) 125 set(relevant_files) 126 foreach(f ${ARG_FILES}) 127 get_filename_component(out ${f} DIRECTORY) 128 if(${touch_dir} STREQUAL ${out}) 129 list(APPEND relevant_files ${f}) 130 endif() 131 endforeach() 132 133 set(td ${TOUCHES_DIR}/${ARG_TARGET}) 134 file(MAKE_DIRECTORY ${td}) 135 string(REGEX REPLACE "^${PROJECT_SOURCE_DIR}/" "" tf ${touch_dir}) 136 string(REGEX REPLACE "[/.]" "-" tf ${tf}) 137 set(touch_file ${td}/${tf}) 138 139 add_custom_command( 140 OUTPUT ${touch_file} 141 COMMAND ${CMAKE_COMMAND} -E touch ${touch_file} 142 COMMAND ${ARG_COMMAND} ${ARG_FLAGS} ${relevant_files} 143 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} 144 DEPENDS ${relevant_files}) 145 list(APPEND touch_list ${touch_file}) 146 endforeach() 147 endif() 148 149 add_custom_target(${ARG_TARGET} DEPENDS ${touch_list}) 150 endfunction() 151 152 # A wrapper function that combines add_custom_command and add_custom_target. It 153 # essentially models the "make" dependency where a target is only rebuilt if 154 # any dependencies have been changed. 155 # 156 # Important to note is that `DEPENDS` is a bit misleading; it should not only 157 # specify dependencies but also the files that are being generated/output 158 # files in order to work correctly. 159 function(add_target) 160 cmake_parse_arguments(ARG 161 "" 162 "" 163 "COMMAND;DEPENDS;CUSTOM_COMMAND_ARGS" 164 ${ARGN} 165 ) 166 set(target ${ARGV0}) 167 168 set(touch_file ${TOUCHES_DIR}/${target}) 169 add_custom_command( 170 OUTPUT ${touch_file} 171 COMMAND ${CMAKE_COMMAND} -E touch ${touch_file} 172 COMMAND ${CMAKE_COMMAND} -E env "VIMRUNTIME=${NVIM_RUNTIME_DIR}" ${ARG_COMMAND} 173 WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} 174 DEPENDS ${ARG_DEPENDS} 175 ${ARG_CUSTOM_COMMAND_ARGS}) 176 add_custom_target(${target} DEPENDS ${touch_file}) 177 endfunction() 178 179 # Set default build type to BUILD_TYPE. 180 # 181 # The correct way to specify build type (for example Release) for 182 # single-configuration generators (Make and Ninja) is to run 183 # 184 # cmake -B build -D CMAKE_BUILD_TYPE=Release 185 # cmake --build build 186 # 187 # while for multi-configuration generators (Visual Studio, Xcode and Ninja 188 # Multi-Config) is to run 189 # 190 # cmake -B build 191 # cmake --build build --config Release 192 # 193 # Passing CMAKE_BUILD_TYPE for multi-config generators will not only not be 194 # used, but also generate a warning for the user. 195 function(set_default_buildtype BUILD_TYPE) 196 set(defaultBuildTypes Debug Release MinSizeRel RelWithDebInfo) 197 198 get_property(isMultiConfig GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 199 if(isMultiConfig) 200 # Multi-config generators use the first element in 201 # CMAKE_CONFIGURATION_TYPES as the default build type 202 list(INSERT defaultBuildTypes 0 ${BUILD_TYPE}) 203 list(REMOVE_DUPLICATES defaultBuildTypes) 204 set(CMAKE_CONFIGURATION_TYPES ${defaultBuildTypes} PARENT_SCOPE) 205 if(CMAKE_BUILD_TYPE) 206 message(WARNING "CMAKE_BUILD_TYPE specified which is ignored on \ 207 multi-configuration generators. Defaulting to ${BUILD_TYPE} build type.") 208 endif() 209 else() 210 set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "${defaultBuildTypes}") 211 if(NOT CMAKE_BUILD_TYPE) 212 message(STATUS "CMAKE_BUILD_TYPE not specified, default is '${BUILD_TYPE}'") 213 set(CMAKE_BUILD_TYPE ${BUILD_TYPE} CACHE STRING "Choose the type of build" FORCE) 214 else() 215 message(STATUS "CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") 216 endif() 217 endif() 218 endfunction() 219 220 # Check if a module is available in Lua 221 function(check_lua_module LUA_PRG_PATH MODULE RESULT_VAR) 222 execute_process(COMMAND ${LUA_PRG_PATH} -l "${MODULE}" -e "" 223 RESULT_VARIABLE module_missing) 224 if(module_missing) 225 set(${RESULT_VAR} FALSE PARENT_SCOPE) 226 else() 227 set(${RESULT_VAR} TRUE PARENT_SCOPE) 228 endif() 229 endfunction()