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#
10function(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()
18endfunction()
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
29function(spaces2list variable_name)
30    string(REPLACE " " ";" tmp "${${variable_name}}")
31    set("${variable_name}" "${tmp}" PARENT_SCOPE)
32endfunction()
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#
39function(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)
43endfunction()
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.
55function(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
74endfunction()
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
79function(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}")
109endfunction()
110
111macro(include_if_exists path)
112    if(EXISTS "${path}")
113        include("${path}")
114    endif()
115endmacro()
116
117# Append a single line to the file specified
118# The line ending is determined by the host OS
119function(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}")
133endfunction()
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.
140function(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()
177endfunction()
178
179# Convert a CMake list to a JSON list and store it in a variable
180function(make_json_list list variable)
181    string(REPLACE ";" "\", \"" result "[ \"${list}\" ]")
182    set("${variable}" "${result}" PARENT_SCOPE)
183endfunction()
184
185# add_prefix
186#
187# Adds a prefix to each item in the specified list.
188#
189function(add_prefix var prefix)
190    foreach(elm ${ARGN})
191        list(APPEND newlist "${prefix}${elm}")
192    endforeach()
193    set(${var} "${newlist}" PARENT_SCOPE)
194endfunction()
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#
205function(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)
224endfunction()
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.
231function(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)
245endfunction()
246
247
248function(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()
261endfunction()
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
268function(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()
273endfunction()
274
275# add_compile_options variant for C code only
276#
277# This adds global options, set target properties for
278# component-specific flags
279function(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()
284endfunction()
285
286
287# add_prebuild_library
288#
289# Add prebuilt library with support for adding dependencies on ESP-IDF components.
290function(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()
310endfunction()
311
312
313# file_generate
314#
315# Utility to generate file and have the output automatically added to cleaned files.
316function(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}")
329endfunction()
330
331# add_subdirectory_if_exists
332#
333# Like add_subdirectory, but only proceeds if the given source directory exists.
334function(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()
342endfunction()
343
344
345# add_deprecated_target_alias
346#
347# Creates an alias for exising target and shows deprectation warning
348function(add_deprecated_target_alias old_target new_target)
349    add_custom_target(${old_target}
350        COMMAND ${CMAKE_COMMAND} -E echo
351        "Warning: Command \"${old_target}\" is deprecated and will be removed in the next major release. \
352        Please use \"${new_target}\" instead."
353    )
354    add_dependencies(${old_target} ${new_target})
355endfunction()
356