utilities.cmake 14 KB

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