utilities.cmake
1 # set_default 2 # 3 # Define a variable to a default value if otherwise unset. 4 # 5 # Priority for new value is: 6 # - Existing cmake value (ie set with cmake -D, or already set in CMakeLists) 7 # - Value of any non-empty environment variable of the same name 8 # - Default value as provided to function 9 # 10 function(set_default variable default_value) 11 if(NOT ${variable}) 12 if(DEFINED ENV{${variable}} AND NOT "$ENV{${variable}}" STREQUAL "") 13 set(${variable} $ENV{${variable}} PARENT_SCOPE) 14 else() 15 set(${variable} ${default_value} PARENT_SCOPE) 16 endif() 17 endif() 18 endfunction() 19 20 # spaces2list 21 # 22 # Take a variable whose value was space-delimited values, convert to a cmake 23 # list (semicolon-delimited) 24 # 25 # Note: if using this for directories, keeps the issue in place that 26 # directories can't contain spaces... 27 # 28 # TODO: look at cmake separate_arguments, which is quote-aware 29 function(spaces2list variable_name) 30 string(REPLACE " " ";" tmp "${${variable_name}}") 31 set("${variable_name}" "${tmp}" PARENT_SCOPE) 32 endfunction() 33 34 # lines2list 35 # 36 # Take a variable with multiple lines of output in it, convert it 37 # to a cmake list (semicolon-delimited), one line per item 38 # 39 function(lines2list variable_name) 40 string(REGEX REPLACE "\r?\n" ";" tmp "${${variable_name}}") 41 string(REGEX REPLACE ";;" ";" tmp "${tmp}") 42 set("${variable_name}" "${tmp}" PARENT_SCOPE) 43 endfunction() 44 45 46 # move_if_different 47 # 48 # If 'source' has different md5sum to 'destination' (or destination 49 # does not exist, move it across. 50 # 51 # If 'source' has the same md5sum as 'destination', delete 'source'. 52 # 53 # Avoids timestamp updates for re-generated files where content hasn't 54 # changed. 55 function(move_if_different source destination) 56 set(do_copy 1) 57 file(GLOB dest_exists ${destination}) 58 if(dest_exists) 59 file(MD5 ${source} source_md5) 60 file(MD5 ${destination} dest_md5) 61 if(source_md5 STREQUAL dest_md5) 62 set(do_copy "") 63 endif() 64 endif() 65 66 if(do_copy) 67 message("Moving ${source} -> ${destination}") 68 file(RENAME ${source} ${destination}) 69 else() 70 message("Not moving ${source} -> ${destination}") 71 file(REMOVE ${source}) 72 endif() 73 74 endfunction() 75 76 # target_add_binary_data adds binary data into the built target, 77 # by converting it to a generated source file which is then compiled 78 # to a binary object as part of the build 79 function(target_add_binary_data target embed_file embed_type) 80 cmake_parse_arguments(_ "" "RENAME_TO" "DEPENDS" ${ARGN}) 81 idf_build_get_property(build_dir BUILD_DIR) 82 idf_build_get_property(idf_path IDF_PATH) 83 84 get_filename_component(embed_file "${embed_file}" ABSOLUTE) 85 86 get_filename_component(name "${embed_file}" NAME) 87 set(embed_srcfile "${build_dir}/${name}.S") 88 89 set(rename_to_arg) 90 if(__RENAME_TO) # use a predefined variable name 91 set(rename_to_arg -D "VARIABLE_BASENAME=${__RENAME_TO}") 92 endif() 93 94 add_custom_command(OUTPUT "${embed_srcfile}" 95 COMMAND "${CMAKE_COMMAND}" 96 -D "DATA_FILE=${embed_file}" 97 -D "SOURCE_FILE=${embed_srcfile}" 98 ${rename_to_arg} 99 -D "FILE_TYPE=${embed_type}" 100 -P "${idf_path}/tools/cmake/scripts/data_file_embed_asm.cmake" 101 MAIN_DEPENDENCY "${embed_file}" 102 DEPENDS "${idf_path}/tools/cmake/scripts/data_file_embed_asm.cmake" ${__DEPENDS} 103 WORKING_DIRECTORY "${build_dir}" 104 VERBATIM) 105 106 set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${embed_srcfile}") 107 108 target_sources("${target}" PRIVATE "${embed_srcfile}") 109 endfunction() 110 111 macro(include_if_exists path) 112 if(EXISTS "${path}") 113 include("${path}") 114 endif() 115 endmacro() 116 117 # Append a single line to the file specified 118 # The line ending is determined by the host OS 119 function(file_append_line file line) 120 if(DEFINED ENV{MSYSTEM} OR CMAKE_HOST_WIN32) 121 set(line_ending "\r\n") 122 else() # unix 123 set(line_ending "\n") 124 endif() 125 file(READ ${file} existing) 126 string(FIND ${existing} ${line_ending} last_newline REVERSE) 127 string(LENGTH ${existing} length) 128 math(EXPR length "${length}-1") 129 if(NOT length EQUAL last_newline) # file doesn't end with a newline 130 file(APPEND "${file}" "${line_ending}") 131 endif() 132 file(APPEND "${file}" "${line}${line_ending}") 133 endfunction() 134 135 # Add one or more linker scripts to the target, including a link-time dependency 136 # 137 # Automatically adds a -L search path for the containing directory (if found), 138 # and then adds -T with the filename only. This allows INCLUDE directives to be 139 # used to include other linker scripts in the same directory. 140 function(target_linker_script target deptype scriptfiles) 141 cmake_parse_arguments(_ "" "PROCESS" "" ${ARGN}) 142 foreach(scriptfile ${scriptfiles}) 143 get_filename_component(abs_script "${scriptfile}" ABSOLUTE) 144 message(STATUS "Adding linker script ${abs_script}") 145 146 if(__PROCESS) 147 get_filename_component(output "${__PROCESS}" ABSOLUTE) 148 __ldgen_process_template(${abs_script} ${output}) 149 set(abs_script ${output}) 150 endif() 151 152 get_filename_component(search_dir "${abs_script}" DIRECTORY) 153 get_filename_component(scriptname "${abs_script}" NAME) 154 155 if(deptype STREQUAL INTERFACE OR deptype STREQUAL PUBLIC) 156 get_target_property(link_libraries "${target}" INTERFACE_LINK_LIBRARIES) 157 else() 158 get_target_property(link_libraries "${target}" LINK_LIBRARIES) 159 endif() 160 161 list(FIND "${link_libraries}" "-L ${search_dir}" found_search_dir) 162 if(found_search_dir EQUAL "-1") # not already added as a search path 163 target_link_libraries("${target}" "${deptype}" "-L ${search_dir}") 164 endif() 165 166 target_link_libraries("${target}" "${deptype}" "-T ${scriptname}") 167 168 # Note: In ESP-IDF, most targets are libraries and libary LINK_DEPENDS don't propagate to 169 # executable(s) the library is linked to. Attach manually to executable once it is known. 170 # 171 # Property INTERFACE_LINK_DEPENDS is available in CMake 3.13 which should propagate link 172 # dependencies. 173 if(NOT __PROCESS) 174 idf_build_set_property(__LINK_DEPENDS ${abs_script} APPEND) 175 endif() 176 endforeach() 177 endfunction() 178 179 # Convert a CMake list to a JSON list and store it in a variable 180 function(make_json_list list variable) 181 string(REPLACE ";" "\", \"" result "[ \"${list}\" ]") 182 set("${variable}" "${result}" PARENT_SCOPE) 183 endfunction() 184 185 # add_prefix 186 # 187 # Adds a prefix to each item in the specified list. 188 # 189 function(add_prefix var prefix) 190 foreach(elm ${ARGN}) 191 list(APPEND newlist "${prefix}${elm}") 192 endforeach() 193 set(${var} "${newlist}" PARENT_SCOPE) 194 endfunction() 195 196 # fail_at_build_time 197 # 198 # Creates a phony target which fails the build and touches CMakeCache.txt to cause a cmake run next time. 199 # 200 # This is used when a missing file is required at CMake runtime, but we can't fail the build if it is not found, 201 # because the "menuconfig" target may be required to fix the problem. 202 # 203 # We cannot use CMAKE_CONFIGURE_DEPENDS instead because it only works for files which exist at CMake runtime. 204 # 205 function(fail_at_build_time target_name message_line0) 206 idf_build_get_property(idf_path IDF_PATH) 207 set(message_lines COMMAND ${CMAKE_COMMAND} -E echo "${message_line0}") 208 foreach(message_line ${ARGN}) 209 set(message_lines ${message_lines} COMMAND ${CMAKE_COMMAND} -E echo "${message_line}") 210 endforeach() 211 # Generate a timestamp file that gets included. When deleted on build, this forces CMake 212 # to rerun. 213 string(RANDOM filename) 214 set(filename "${CMAKE_CURRENT_BINARY_DIR}/${filename}.cmake") 215 file(WRITE "${filename}" "") 216 include("${filename}") 217 set(fail_message "Failing the build (see errors on lines above)") 218 add_custom_target(${target_name} ALL 219 ${message_lines} 220 COMMAND ${CMAKE_COMMAND} -E remove "${filename}" 221 COMMAND ${CMAKE_COMMAND} -E env FAIL_MESSAGE=${fail_message} 222 ${CMAKE_COMMAND} -P ${idf_path}/tools/cmake/scripts/fail.cmake 223 VERBATIM) 224 endfunction() 225 226 # fail_target 227 # 228 # Creates a phony target which fails when invoked. This is used when the necessary conditions 229 # for a target are not met, such as configuration. Rather than ommitting the target altogether, 230 # we fail execution with a helpful message. 231 function(fail_target target_name message_line0) 232 idf_build_get_property(idf_path IDF_PATH) 233 set(message_lines COMMAND ${CMAKE_COMMAND} -E echo "${message_line0}") 234 foreach(message_line ${ARGN}) 235 set(message_lines ${message_lines} COMMAND ${CMAKE_COMMAND} -E echo "${message_line}") 236 endforeach() 237 # Generate a timestamp file that gets included. When deleted on build, this forces CMake 238 # to rerun. 239 set(fail_message "Failed executing target (see errors on lines above)") 240 add_custom_target(${target_name} 241 ${message_lines} 242 COMMAND ${CMAKE_COMMAND} -E env FAIL_MESSAGE=${fail_message} 243 ${CMAKE_COMMAND} -P ${idf_path}/tools/cmake/scripts/fail.cmake 244 VERBATIM) 245 endfunction() 246 247 248 function(check_exclusive_args args prefix) 249 set(_args ${args}) 250 spaces2list(_args) 251 set(only_arg 0) 252 foreach(arg ${_args}) 253 if(${prefix}_${arg} AND only_arg) 254 message(FATAL_ERROR "${args} are exclusive arguments") 255 endif() 256 257 if(${prefix}_${arg}) 258 set(only_arg 1) 259 endif() 260 endforeach() 261 endfunction() 262 263 264 # add_compile_options variant for C++ code only 265 # 266 # This adds global options, set target properties for 267 # component-specific flags 268 function(add_cxx_compile_options) 269 foreach(option ${ARGV}) 270 # note: the Visual Studio Generator doesn't support this... 271 add_compile_options($<$<COMPILE_LANGUAGE:CXX>:${option}>) 272 endforeach() 273 endfunction() 274 275 # add_compile_options variant for C code only 276 # 277 # This adds global options, set target properties for 278 # component-specific flags 279 function(add_c_compile_options) 280 foreach(option ${ARGV}) 281 # note: the Visual Studio Generator doesn't support this... 282 add_compile_options($<$<COMPILE_LANGUAGE:C>:${option}>) 283 endforeach() 284 endfunction() 285 286 287 # add_prebuild_library 288 # 289 # Add prebuilt library with support for adding dependencies on ESP-IDF components. 290 function(add_prebuilt_library target_name lib_path) 291 cmake_parse_arguments(_ "" "" "REQUIRES;PRIV_REQUIRES" ${ARGN}) 292 293 get_filename_component(lib_path "${lib_path}" 294 ABSOLUTE BASE_DIR "${CMAKE_CURRENT_LIST_DIR}") 295 296 add_library(${target_name} STATIC IMPORTED) 297 set_property(TARGET ${target_name} PROPERTY IMPORTED_LOCATION ${lib_path}) 298 299 foreach(req ${__REQUIRES}) 300 idf_component_get_property(req_lib "${req}" COMPONENT_LIB) 301 set_property(TARGET ${target_name} APPEND PROPERTY LINK_LIBRARIES "${req_lib}") 302 set_property(TARGET ${target_name} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "${req_lib}") 303 endforeach() 304 305 foreach(req ${__PRIV_REQUIRES}) 306 idf_component_get_property(req_lib "${req}" COMPONENT_LIB) 307 set_property(TARGET ${target_name} APPEND PROPERTY LINK_LIBRARIES "${req_lib}") 308 set_property(TARGET ${target_name} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "$<LINK_ONLY:${req_lib}>") 309 endforeach() 310 endfunction() 311 312 313 # file_generate 314 # 315 # Utility to generate file and have the output automatically added to cleaned files. 316 function(file_generate output) 317 cmake_parse_arguments(_ "" "INPUT;CONTENT" "" ${ARGN}) 318 319 if(__INPUT) 320 file(GENERATE OUTPUT "${output}" INPUT "${__INPUT}") 321 elseif(__CONTENT) 322 file(GENERATE OUTPUT "${output}" CONTENT "${__CONTENT}") 323 else() 324 message(FATAL_ERROR "Content to generate not specified.") 325 endif() 326 327 set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 328 APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES "${output}") 329 endfunction() 330 331 # add_subdirectory_if_exists 332 # 333 # Like add_subdirectory, but only proceeds if the given source directory exists. 334 function(add_subdirectory_if_exists source_dir) 335 get_filename_component(abs_dir "${source_dir}" 336 ABSOLUTE BASE_DIR "${CMAKE_CURRENT_LIST_DIR}") 337 if(EXISTS "${abs_dir}") 338 add_subdirectory("${source_dir}" ${ARGN}) 339 else() 340 message(STATUS "Subdirectory '${abs_dir}' does not exist, skipped.") 341 endif() 342 endfunction()