1# idf_build_get_property
2#
3# @brief Retrieve the value of the specified property related to ESP-IDF build.
4#
5# @param[out] var the variable to store the value in
6# @param[in] property the property to get the value of
7#
8# @param[in, optional] GENERATOR_EXPRESSION (option) retrieve the generator expression for the property
9#                   instead of actual value
10function(idf_build_get_property var property)
11    cmake_parse_arguments(_ "GENERATOR_EXPRESSION" "" "" ${ARGN})
12    if(__GENERATOR_EXPRESSION)
13        set(val "$<TARGET_PROPERTY:__idf_build_target,${property}>")
14    else()
15        get_property(val TARGET __idf_build_target PROPERTY ${property})
16    endif()
17    set(${var} ${val} PARENT_SCOPE)
18endfunction()
19
20# idf_build_set_property
21#
22# @brief Set the value of the specified property related to ESP-IDF build. The property is
23#        also added to the internal list of build properties if it isn't there already.
24#
25# @param[in] property the property to set the value of
26# @param[out] value value of the property
27#
28# @param[in, optional] APPEND (option) append the value to the current value of the
29#                     property instead of replacing it
30function(idf_build_set_property property value)
31    cmake_parse_arguments(_ "APPEND" "" "" ${ARGN})
32
33    if(__APPEND)
34        set_property(TARGET __idf_build_target APPEND PROPERTY ${property} ${value})
35    else()
36        set_property(TARGET __idf_build_target PROPERTY ${property} ${value})
37    endif()
38
39    # Keep track of set build properties so that they can be exported to a file that
40    # will be included in early expansion script.
41    idf_build_get_property(build_properties __BUILD_PROPERTIES)
42    if(NOT property IN_LIST build_properties)
43        idf_build_set_property(__BUILD_PROPERTIES "${property}" APPEND)
44    endif()
45endfunction()
46
47# idf_build_unset_property
48#
49# @brief Unset the value of the specified property related to ESP-IDF build. Equivalent
50#        to setting the property to an empty string; though it also removes the property
51#        from the internal list of build properties.
52#
53# @param[in] property the property to unset the value of
54function(idf_build_unset_property property)
55    idf_build_set_property(${property} "") # set to an empty value
56    idf_build_get_property(build_properties __BUILD_PROPERTIES) # remove from tracked properties
57    list(REMOVE_ITEM build_properties ${property})
58    idf_build_set_property(__BUILD_PROPERTIES "${build_properties}")
59endfunction()
60
61#
62# Retrieve the IDF_PATH repository's version, either using a version
63# file or Git revision. Sets the IDF_VER build property.
64#
65function(__build_get_idf_git_revision)
66    idf_build_get_property(idf_path IDF_PATH)
67    git_describe(idf_ver_git "${idf_path}")
68    if(EXISTS "${idf_path}/version.txt")
69        file(STRINGS "${idf_path}/version.txt" idf_ver_t)
70        set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${idf_path}/version.txt")
71    else()
72        set(idf_ver_t ${idf_ver_git})
73    endif()
74    # cut IDF_VER to required 32 characters.
75    string(SUBSTRING "${idf_ver_t}" 0 31 idf_ver)
76    idf_build_set_property(COMPILE_DEFINITIONS "-DIDF_VER=\"${idf_ver}\"" APPEND)
77    git_submodule_check("${idf_path}")
78    idf_build_set_property(IDF_VER ${idf_ver})
79endfunction()
80
81#
82# Sets initial list of build specifications (compile options, definitions, etc.) common across
83# all library targets built under the ESP-IDF build system. These build specifications are added
84# privately using the directory-level CMake commands (add_compile_options, include_directories, etc.)
85# during component registration.
86#
87function(__build_set_default_build_specifications)
88    unset(compile_definitions)
89    unset(compile_options)
90    unset(c_compile_options)
91    unset(cxx_compile_options)
92
93    list(APPEND compile_definitions "-D_GNU_SOURCE")
94
95    list(APPEND compile_options     "-ffunction-sections"
96                                    "-fdata-sections"
97                                    # warning-related flags
98                                    "-Wall"
99                                    "-Werror=all"
100                                    "-Wno-error=unused-function"
101                                    "-Wno-error=unused-variable"
102                                    "-Wno-error=deprecated-declarations"
103                                    "-Wextra"
104                                    "-Wno-unused-parameter"
105                                    "-Wno-sign-compare"
106                                    # always generate debug symbols (even in release mode, these don't
107                                    # go into the final binary so have no impact on size
108                                    "-ggdb")
109
110    list(APPEND c_compile_options   "-std=gnu99"
111                                    "-Wno-old-style-declaration")
112
113    list(APPEND cxx_compile_options "-std=gnu++11")
114
115    idf_build_set_property(COMPILE_DEFINITIONS "${compile_definitions}" APPEND)
116    idf_build_set_property(COMPILE_OPTIONS "${compile_options}" APPEND)
117    idf_build_set_property(C_COMPILE_OPTIONS "${c_compile_options}" APPEND)
118    idf_build_set_property(CXX_COMPILE_OPTIONS "${cxx_compile_options}" APPEND)
119endfunction()
120
121#
122# Initialize the build. This gets called upon inclusion of idf.cmake to set internal
123# properties used for the processing phase of the build.
124#
125function(__build_init idf_path)
126    # Create the build target, to which the ESP-IDF build properties, dependencies are attached to.
127    # Must be global so as to be accessible from any subdirectory in custom projects.
128    add_library(__idf_build_target STATIC IMPORTED GLOBAL)
129
130    set_default(python "python")
131
132    idf_build_set_property(PYTHON ${python})
133    idf_build_set_property(IDF_PATH ${idf_path})
134
135    idf_build_set_property(__PREFIX idf)
136    idf_build_set_property(__CHECK_PYTHON 1)
137
138    __build_set_default_build_specifications()
139
140    # Add internal components to the build
141    idf_build_get_property(idf_path IDF_PATH)
142    idf_build_get_property(prefix __PREFIX)
143    file(GLOB component_dirs ${idf_path}/components/*)
144    foreach(component_dir ${component_dirs})
145        get_filename_component(component_dir ${component_dir} ABSOLUTE)
146        __component_dir_quick_check(is_component ${component_dir})
147        if(is_component)
148            __component_add(${component_dir} ${prefix})
149        endif()
150    endforeach()
151
152
153    idf_build_get_property(target IDF_TARGET)
154    if(NOT target STREQUAL "linux")
155        # Set components required by all other components in the build
156        #
157        # - lwip is here so that #include <sys/socket.h> works without any special provisions
158        # - esp_hw_support is here for backward compatibility
159        set(requires_common cxx newlib freertos esp_hw_support heap log lwip soc hal esp_rom esp_common esp_system)
160        idf_build_set_property(__COMPONENT_REQUIRES_COMMON "${requires_common}")
161    endif()
162
163    __build_get_idf_git_revision()
164    __kconfig_init()
165endfunction()
166
167# idf_build_component
168#
169# @brief Present a directory that contains a component to the build system.
170#        Relative paths are converted to absolute paths with respect to current directory.
171#        All calls to this command must be performed before idf_build_process.
172#
173# @note  This command does not guarantee that the component will be processed
174#        during build (see the COMPONENTS argument description for command idf_build_process)
175#
176# @param[in] component_dir directory of the component
177function(idf_build_component component_dir)
178    idf_build_get_property(prefix __PREFIX)
179    __component_add(${component_dir} ${prefix} 0)
180endfunction()
181
182#
183# Resolve the requirement component to the component target created for that component.
184#
185function(__build_resolve_and_add_req var component_target req type)
186    __component_get_target(_component_target ${req})
187    __component_get_property(_component_registered ${component_target} __COMPONENT_REGISTERED)
188    if(NOT _component_target OR NOT _component_registered)
189        message(FATAL_ERROR "Failed to resolve component '${req}'.")
190    endif()
191    __component_set_property(${component_target} ${type} ${_component_target} APPEND)
192    set(${var} ${_component_target} PARENT_SCOPE)
193endfunction()
194
195#
196# Build a list of components (in the form of component targets) to be added to the build
197# based on public and private requirements. This list is saved in an internal property,
198# __BUILD_COMPONENT_TARGETS.
199#
200function(__build_expand_requirements component_target)
201    # Since there are circular dependencies, make sure that we do not infinitely
202    # expand requirements for each component.
203    idf_build_get_property(component_targets_seen __COMPONENT_TARGETS_SEEN)
204    __component_get_property(component_registered ${component_target} __COMPONENT_REGISTERED)
205    if(component_target IN_LIST component_targets_seen OR NOT component_registered)
206        return()
207    endif()
208
209    idf_build_set_property(__COMPONENT_TARGETS_SEEN ${component_target} APPEND)
210
211    get_property(reqs TARGET ${component_target} PROPERTY REQUIRES)
212    get_property(priv_reqs TARGET ${component_target} PROPERTY PRIV_REQUIRES)
213
214    foreach(req ${reqs})
215        __build_resolve_and_add_req(_component_target ${component_target} ${req} __REQUIRES)
216        __build_expand_requirements(${_component_target})
217    endforeach()
218
219    foreach(req ${priv_reqs})
220        __build_resolve_and_add_req(_component_target ${component_target} ${req} __PRIV_REQUIRES)
221        __build_expand_requirements(${_component_target})
222    endforeach()
223
224    idf_build_get_property(build_component_targets __BUILD_COMPONENT_TARGETS)
225    if(NOT component_target IN_LIST build_component_targets)
226        idf_build_set_property(__BUILD_COMPONENT_TARGETS ${component_target} APPEND)
227
228        __component_get_property(component_lib ${component_target} COMPONENT_LIB)
229        idf_build_set_property(__BUILD_COMPONENTS ${component_lib} APPEND)
230
231        idf_build_get_property(prefix __PREFIX)
232        __component_get_property(component_prefix ${component_target} __PREFIX)
233
234        __component_get_property(component_alias ${component_target} COMPONENT_ALIAS)
235
236        idf_build_set_property(BUILD_COMPONENT_ALIASES ${component_alias} APPEND)
237
238        # Only put in the prefix in the name if it is not the default one
239        if(component_prefix STREQUAL prefix)
240            __component_get_property(component_name ${component_target} COMPONENT_NAME)
241            idf_build_set_property(BUILD_COMPONENTS ${component_name} APPEND)
242        else()
243            idf_build_set_property(BUILD_COMPONENTS ${component_alias} APPEND)
244        endif()
245    endif()
246endfunction()
247
248#
249# Write a CMake file containing set build properties, owing to the fact that an internal
250# list of properties is maintained in idf_build_set_property() call. This is used to convert
251# those set properties to variables in the scope the output file is included in.
252#
253function(__build_write_properties output_file)
254    idf_build_get_property(build_properties __BUILD_PROPERTIES)
255    foreach(property ${build_properties})
256        idf_build_get_property(val ${property})
257        set(build_properties_text "${build_properties_text}\nset(${property} ${val})")
258    endforeach()
259    file(WRITE ${output_file} "${build_properties_text}")
260endfunction()
261
262#
263# Check if the Python interpreter used for the build has all the required modules.
264#
265function(__build_check_python)
266    idf_build_get_property(check __CHECK_PYTHON)
267    if(check)
268        idf_build_get_property(python PYTHON)
269        idf_build_get_property(idf_path IDF_PATH)
270        message(STATUS "Checking Python dependencies...")
271        execute_process(COMMAND "${python}" "${idf_path}/tools/check_python_dependencies.py"
272            RESULT_VARIABLE result)
273        if(NOT result EQUAL 0)
274            message(FATAL_ERROR "Some Python dependencies must be installed. Check above message for details.")
275        endif()
276    endif()
277endfunction()
278
279#
280# Prepare for component processing expanding each component's project include
281#
282macro(__build_process_project_includes)
283    # Include the sdkconfig cmake file, since the following operations require
284    # knowledge of config values.
285    idf_build_get_property(sdkconfig_cmake SDKCONFIG_CMAKE)
286    include(${sdkconfig_cmake})
287
288    # Make each build property available as a read-only variable
289    idf_build_get_property(build_properties __BUILD_PROPERTIES)
290    foreach(build_property ${build_properties})
291        idf_build_get_property(val ${build_property})
292        set(${build_property} "${val}")
293    endforeach()
294
295    # Check that the CMake target value matches the Kconfig target value.
296    __target_check()
297
298    idf_build_get_property(build_component_targets __BUILD_COMPONENT_TARGETS)
299
300    # Include each component's project_include.cmake
301    foreach(component_target ${build_component_targets})
302        __component_get_property(dir ${component_target} COMPONENT_DIR)
303        __component_get_property(_name ${component_target} COMPONENT_NAME)
304        set(COMPONENT_NAME ${_name})
305        set(COMPONENT_DIR ${dir})
306        set(COMPONENT_PATH ${dir})  # this is deprecated, users are encouraged to use COMPONENT_DIR;
307                                    # retained for compatibility
308        if(EXISTS ${COMPONENT_DIR}/project_include.cmake)
309            include(${COMPONENT_DIR}/project_include.cmake)
310        endif()
311    endforeach()
312endmacro()
313
314#
315# Utility macro for setting default property value if argument is not specified
316# for idf_build_process().
317#
318macro(__build_set_default var default)
319    set(_var __${var})
320    if(NOT "${${_var}}" STREQUAL "")
321        idf_build_set_property(${var} "${${_var}}")
322    else()
323        idf_build_set_property(${var} "${default}")
324    endif()
325    unset(_var)
326endmacro()
327
328#
329# Import configs as build instance properties so that they are accessible
330# using idf_build_get_config(). Config has to have been generated before calling
331# this command.
332#
333function(__build_import_configs)
334    # Include the sdkconfig cmake file, since the following operations require
335    # knowledge of config values.
336    idf_build_get_property(sdkconfig_cmake SDKCONFIG_CMAKE)
337    include(${sdkconfig_cmake})
338
339    idf_build_set_property(__CONFIG_VARIABLES "${CONFIGS_LIST}")
340    foreach(config ${CONFIGS_LIST})
341        set_property(TARGET __idf_build_target PROPERTY ${config} "${${config}}")
342    endforeach()
343endfunction()
344
345# idf_build_process
346#
347# @brief Main processing step for ESP-IDF build: config generation, adding components to the build,
348#        dependency resolution, etc.
349#
350# @param[in] target ESP-IDF target
351#
352# @param[in, optional] PROJECT_DIR (single value) directory of the main project the buildsystem
353#                      is processed for; defaults to CMAKE_SOURCE_DIR
354# @param[in, optional] PROJECT_VER (single value) version string of the main project; defaults
355#                      to 1
356# @param[in, optional] PROJECT_NAME (single value) main project name, defaults to CMAKE_PROJECT_NAME
357# @param[in, optional] SDKCONFIG (single value) sdkconfig output path, defaults to PROJECT_DIR/sdkconfig
358#                       if PROJECT_DIR is set and CMAKE_SOURCE_DIR/sdkconfig if not
359# @param[in, optional] SDKCONFIG_DEFAULTS (single value) config defaults file to use for the build; defaults
360#                       to none (Kconfig defaults or previously generated config are used)
361# @param[in, optional] BUILD_DIR (single value) directory for build artifacts; defautls to CMAKE_BINARY_DIR
362# @param[in, optional] COMPONENTS (multivalue) select components to process among the components
363#                       known by the build system
364#                       (added via `idf_build_component`). This argument is used to trim the build.
365#                       Other components are automatically added if they are required
366#                       in the dependency chain, i.e.
367#                       the public and private requirements of the components in this list
368#                       are automatically added, and in
369#                       turn the public and private requirements of those requirements,
370#                       so on and so forth. If not specified, all components known to the build system
371#                       are processed.
372macro(idf_build_process target)
373    set(options)
374    set(single_value PROJECT_DIR PROJECT_VER PROJECT_NAME BUILD_DIR SDKCONFIG)
375    set(multi_value COMPONENTS SDKCONFIG_DEFAULTS)
376    cmake_parse_arguments(_ "${options}" "${single_value}" "${multi_value}" ${ARGN})
377
378    idf_build_set_property(BOOTLOADER_BUILD "${BOOTLOADER_BUILD}")
379
380    # Check build target is specified. Since this target corresponds to a component
381    # name, the target component is automatically added to the list of common component
382    # requirements.
383    if(target STREQUAL "")
384        message(FATAL_ERROR "Build target not specified.")
385    endif()
386
387    idf_build_set_property(IDF_TARGET ${target})
388
389    __build_set_default(PROJECT_DIR ${CMAKE_SOURCE_DIR})
390    __build_set_default(PROJECT_NAME ${CMAKE_PROJECT_NAME})
391    __build_set_default(PROJECT_VER 1)
392    __build_set_default(BUILD_DIR ${CMAKE_BINARY_DIR})
393
394    idf_build_get_property(project_dir PROJECT_DIR)
395    __build_set_default(SDKCONFIG "${project_dir}/sdkconfig")
396
397    __build_set_default(SDKCONFIG_DEFAULTS "")
398
399    # Check for required Python modules
400    __build_check_python()
401
402    idf_build_get_property(target IDF_TARGET)
403
404    if(NOT target STREQUAL "linux")
405        idf_build_set_property(__COMPONENT_REQUIRES_COMMON ${target} APPEND)
406    else()
407        idf_build_set_property(__COMPONENT_REQUIRES_COMMON "")
408    endif()
409
410    # Perform early expansion of component CMakeLists.txt in CMake scripting mode.
411    # It is here we retrieve the public and private requirements of each component.
412    # It is also here we add the common component requirements to each component's
413    # own requirements.
414    __component_get_requirements()
415
416    idf_build_get_property(component_targets __COMPONENT_TARGETS)
417
418    # Finally, do component expansion. In this case it simply means getting a final list
419    # of build component targets given the requirements set by each component.
420
421    # Check if we need to trim the components first, and build initial components list
422    # from that.
423    if(__COMPONENTS)
424        unset(component_targets)
425        foreach(component ${__COMPONENTS})
426            __component_get_target(component_target ${component})
427            if(NOT component_target)
428                message(FATAL_ERROR "Failed to resolve component '${component}'.")
429            endif()
430            list(APPEND component_targets ${component_target})
431        endforeach()
432    endif()
433
434    foreach(component_target ${component_targets})
435        __build_expand_requirements(${component_target})
436    endforeach()
437    idf_build_unset_property(__COMPONENT_TARGETS_SEEN)
438
439    # Get a list of common component requirements in component targets form (previously
440    # we just have a list of component names)
441    idf_build_get_property(common_reqs __COMPONENT_REQUIRES_COMMON)
442    foreach(common_req ${common_reqs})
443        __component_get_target(component_target ${common_req})
444        __component_get_property(lib ${component_target} COMPONENT_LIB)
445        idf_build_set_property(___COMPONENT_REQUIRES_COMMON ${lib} APPEND)
446    endforeach()
447
448    # Generate config values in different formats
449    idf_build_get_property(sdkconfig SDKCONFIG)
450    idf_build_get_property(sdkconfig_defaults SDKCONFIG_DEFAULTS)
451    __kconfig_generate_config("${sdkconfig}" "${sdkconfig_defaults}")
452    __build_import_configs()
453
454    # All targets built under this scope is with the ESP-IDF build system
455    set(ESP_PLATFORM 1)
456    idf_build_set_property(COMPILE_DEFINITIONS "-DESP_PLATFORM" APPEND)
457
458    # Perform component processing (inclusion of project_include.cmake, adding component
459    # subdirectories, creating library targets, linking libraries, etc.)
460    __build_process_project_includes()
461
462    idf_build_get_property(idf_path IDF_PATH)
463    add_subdirectory(${idf_path} ${build_dir}/esp-idf)
464
465    unset(ESP_PLATFORM)
466endmacro()
467
468# idf_build_executable
469#
470# @brief Specify the executable the build system can attach dependencies to (for generating
471# files used for linking, targets which should execute before creating the specified executable,
472# generating additional binary files, generating files related to flashing, etc.)
473function(idf_build_executable elf)
474    # Set additional link flags for the executable
475    idf_build_get_property(link_options LINK_OPTIONS)
476    # Using LINK_LIBRARIES here instead of LINK_OPTIONS, as the latter is not in CMake 3.5.
477    set_property(TARGET ${elf} APPEND PROPERTY LINK_LIBRARIES "${link_options}")
478
479    # Propagate link dependencies from component library targets to the executable
480    idf_build_get_property(link_depends __LINK_DEPENDS)
481    set_property(TARGET ${elf} APPEND PROPERTY LINK_DEPENDS "${link_depends}")
482
483    # Set the EXECUTABLE_NAME and EXECUTABLE properties since there are generator expression
484    # from components that depend on it
485    get_filename_component(elf_name ${elf} NAME_WE)
486    get_target_property(elf_dir ${elf} BINARY_DIR)
487
488    idf_build_set_property(EXECUTABLE_NAME ${elf_name})
489    idf_build_set_property(EXECUTABLE ${elf})
490    idf_build_set_property(EXECUTABLE_DIR "${elf_dir}")
491
492    # Add dependency of the build target to the executable
493    add_dependencies(${elf} __idf_build_target)
494endfunction()
495
496# idf_build_get_config
497#
498# @brief Get value of specified config variable
499function(idf_build_get_config var config)
500    cmake_parse_arguments(_ "GENERATOR_EXPRESSION" "" "" ${ARGN})
501    if(__GENERATOR_EXPRESSION)
502        set(val "$<TARGET_PROPERTY:__idf_build_target,${config}>")
503    else()
504        get_property(val TARGET __idf_build_target PROPERTY ${config})
505    endif()
506    set(${var} ${val} PARENT_SCOPE)
507endfunction()
508