/ tools / cmake / utilities.cmake
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()