1# Designed to be included from an IDF app's CMakeLists.txt file
2cmake_minimum_required(VERSION 3.5)
3
4include(${CMAKE_CURRENT_LIST_DIR}/targets.cmake)
5# Initialize build target for this build using the environment variable or
6# value passed externally.
7__target_init()
8
9# The mere inclusion of this CMake file sets up some interal build properties.
10# These properties can be modified in between this inclusion the the idf_build_process
11# call.
12include(${CMAKE_CURRENT_LIST_DIR}/idf.cmake)
13
14# setting PYTHON variable here for compatibility only, new code should use
15# idf_build_get_property(variable PYTHON)
16idf_build_get_property(PYTHON PYTHON)
17if(NOT PYTHON)
18    message(FATAL_ERROR "Internal error, PYTHON build property not set correctly.")
19endif()
20
21# legacy variable for compatibility
22set(IDFTOOL ${PYTHON} "${IDF_PATH}/tools/idf.py")
23
24# On processing, checking Python required modules can be turned off if it was
25# already checked externally.
26if(PYTHON_DEPS_CHECKED)
27    idf_build_set_property(__CHECK_PYTHON 0)
28endif()
29
30# Store CMake arguments that need to be passed into all CMake sub-projects as well
31# (bootloader, ULP, etc)
32#
33# It's not possible to tell if CMake was called with --warn-uninitialized, so to also
34# have these warnings in sub-projects we set a cache variable as well and then check that.
35if(WARN_UNINITIALIZED)
36    idf_build_set_property(EXTRA_CMAKE_ARGS --warn-uninitialized)
37else()
38    idf_build_set_property(EXTRA_CMAKE_ARGS "")
39endif()
40
41#
42# Get the project version from either a version file or the Git revision. This is passed
43# to the idf_build_process call. Dependencies are also set here for when the version file
44# changes (if it is used).
45#
46function(__project_get_revision var)
47    set(_project_path "${CMAKE_CURRENT_LIST_DIR}")
48    if(NOT DEFINED PROJECT_VER)
49        if(EXISTS "${_project_path}/version.txt")
50            file(STRINGS "${_project_path}/version.txt" PROJECT_VER)
51            set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${_project_path}/version.txt")
52        else()
53            git_describe(PROJECT_VER_GIT "${_project_path}")
54            if(PROJECT_VER_GIT)
55                set(PROJECT_VER ${PROJECT_VER_GIT})
56            else()
57                message(STATUS "Project is not inside a git repository, or git repository has no commits;"
58                        " will not use 'git describe' to determine PROJECT_VER.")
59                set(PROJECT_VER 1)
60            endif()
61        endif()
62    endif()
63    set(${var} "${PROJECT_VER}" PARENT_SCOPE)
64endfunction()
65
66#
67# Output the built components to the user. Generates files for invoking idf_monitor.py
68# that doubles as an overview of some of the more important build properties.
69#
70function(__project_info test_components)
71    idf_build_get_property(prefix __PREFIX)
72    idf_build_get_property(_build_components BUILD_COMPONENTS)
73    idf_build_get_property(build_dir BUILD_DIR)
74    idf_build_get_property(idf_path IDF_PATH)
75
76    list(SORT _build_components)
77
78    unset(build_components)
79    unset(build_component_paths)
80
81    foreach(build_component ${_build_components})
82        __component_get_target(component_target "${build_component}")
83        __component_get_property(_name ${component_target} COMPONENT_NAME)
84        __component_get_property(_prefix ${component_target} __PREFIX)
85        __component_get_property(_alias ${component_target} COMPONENT_ALIAS)
86        __component_get_property(_dir ${component_target} COMPONENT_DIR)
87
88        if(_alias IN_LIST test_components)
89            list(APPEND test_component_paths ${_dir})
90        else()
91            if(_prefix STREQUAL prefix)
92                set(component ${_name})
93            else()
94                set(component ${_alias})
95            endif()
96            list(APPEND build_components ${component})
97            list(APPEND build_component_paths ${_dir})
98        endif()
99    endforeach()
100
101    set(PROJECT_NAME ${CMAKE_PROJECT_NAME})
102    idf_build_get_property(PROJECT_PATH PROJECT_DIR)
103    idf_build_get_property(BUILD_DIR BUILD_DIR)
104    idf_build_get_property(SDKCONFIG SDKCONFIG)
105    idf_build_get_property(SDKCONFIG_DEFAULTS SDKCONFIG_DEFAULTS)
106    idf_build_get_property(PROJECT_EXECUTABLE EXECUTABLE)
107    set(PROJECT_BIN ${CMAKE_PROJECT_NAME}.bin)
108    idf_build_get_property(IDF_VER IDF_VER)
109
110    idf_build_get_property(sdkconfig_cmake SDKCONFIG_CMAKE)
111    include(${sdkconfig_cmake})
112    idf_build_get_property(COMPONENT_KCONFIGS KCONFIGS)
113    idf_build_get_property(COMPONENT_KCONFIGS_PROJBUILD KCONFIG_PROJBUILDS)
114
115    # Write project description JSON file
116    idf_build_get_property(build_dir BUILD_DIR)
117    make_json_list("${build_components};${test_components}" build_components_json)
118    make_json_list("${build_component_paths};${test_component_paths}" build_component_paths_json)
119    configure_file("${idf_path}/tools/cmake/project_description.json.in"
120        "${build_dir}/project_description.json")
121
122    # We now have the following component-related variables:
123    #
124    # build_components is the list of components to include in the build.
125    # build_component_paths is the paths to all of these components, obtained from the component dependencies file.
126    #
127    # Print the list of found components and test components
128    string(REPLACE ";" " " build_components "${build_components}")
129    string(REPLACE ";" " " build_component_paths "${build_component_paths}")
130    message(STATUS "Components: ${build_components}")
131    message(STATUS "Component paths: ${build_component_paths}")
132
133    if(test_components)
134        string(REPLACE ";" " " test_components "${test_components}")
135        string(REPLACE ";" " " test_component_paths "${test_component_paths}")
136        message(STATUS "Test components: ${test_components}")
137        message(STATUS "Test component paths: ${test_component_paths}")
138    endif()
139endfunction()
140
141function(__project_init components_var test_components_var)
142    # Use EXTRA_CFLAGS, EXTRA_CXXFLAGS and EXTRA_CPPFLAGS to add more priority options to the compiler
143    # EXTRA_CPPFLAGS is used for both C and C++
144    # Unlike environments' CFLAGS/CXXFLAGS/CPPFLAGS which work for both host and target build,
145    # these works only for target build
146    set(extra_cflags "$ENV{EXTRA_CFLAGS}")
147    set(extra_cxxflags "$ENV{EXTRA_CXXFLAGS}")
148    set(extra_cppflags "$ENV{EXTRA_CPPFLAGS}")
149
150    spaces2list(extra_cflags)
151    spaces2list(extra_cxxflags)
152    spaces2list(extra_cppflags)
153
154    idf_build_set_property(C_COMPILE_OPTIONS "${extra_cflags}" APPEND)
155    idf_build_set_property(CXX_COMPILE_OPTIONS "${extra_cxxflags}" APPEND)
156    idf_build_set_property(COMPILE_OPTIONS "${extra_cppflags}" APPEND)
157
158    function(__project_component_dir component_dir)
159        get_filename_component(component_dir "${component_dir}" ABSOLUTE)
160        # The directory itself is a valid idf component
161        if(EXISTS ${component_dir}/CMakeLists.txt)
162            idf_build_component(${component_dir})
163        else()
164            # otherwise, check whether the subfolders are potential idf components
165            file(GLOB component_dirs ${component_dir}/*)
166            foreach(component_dir ${component_dirs})
167                if(IS_DIRECTORY ${component_dir})
168                    __component_dir_quick_check(is_component ${component_dir})
169                    if(is_component)
170                        idf_build_component(${component_dir})
171                    endif()
172                endif()
173            endforeach()
174        endif()
175    endfunction()
176
177    # Add component directories to the build, given the component filters, exclusions
178    # extra directories, etc. passed from the root CMakeLists.txt.
179    if(COMPONENT_DIRS)
180        # User wants to fully override where components are pulled from.
181        spaces2list(COMPONENT_DIRS)
182        idf_build_set_property(__COMPONENT_TARGETS "")
183        foreach(component_dir ${COMPONENT_DIRS})
184            __project_component_dir(${component_dir})
185        endforeach()
186    else()
187        if(EXISTS "${CMAKE_CURRENT_LIST_DIR}/main")
188            __project_component_dir("${CMAKE_CURRENT_LIST_DIR}/main")
189        endif()
190
191        spaces2list(EXTRA_COMPONENT_DIRS)
192        foreach(component_dir ${EXTRA_COMPONENT_DIRS})
193            __project_component_dir("${component_dir}")
194        endforeach()
195
196        # Look for components in the usual places: CMAKE_CURRENT_LIST_DIR/main,
197        # extra component dirs, and CMAKE_CURRENT_LIST_DIR/components
198        __project_component_dir("${CMAKE_CURRENT_LIST_DIR}/components")
199    endif()
200
201    # For bootloader components, we only need to set-up the Kconfig files.
202    # Indeed, bootloader is currently compiled as a subproject, thus,
203    # its components are not part of the main project.
204    # However, in order to be able to configure these bootloader components
205    # using menuconfig, we need to look for their Kconfig-related files now.
206    file(GLOB bootloader_component_dirs "${CMAKE_CURRENT_LIST_DIR}/bootloader_components/*")
207    list(SORT bootloader_component_dirs)
208    foreach(bootloader_component_dir ${bootloader_component_dirs})
209        if(IS_DIRECTORY ${bootloader_component_dir})
210            __component_dir_quick_check(is_component ${bootloader_component_dir})
211            if(is_component)
212                __kconfig_bootloader_component_add("${bootloader_component_dir}")
213            endif()
214        endif()
215    endforeach()
216
217    spaces2list(COMPONENTS)
218    spaces2list(EXCLUDE_COMPONENTS)
219    idf_build_get_property(component_targets __COMPONENT_TARGETS)
220    foreach(component_target ${component_targets})
221        __component_get_property(component_name ${component_target} COMPONENT_NAME)
222        set(include 1)
223        if(COMPONENTS AND NOT component_name IN_LIST COMPONENTS)
224            set(include 0)
225        endif()
226        if(EXCLUDE_COMPONENTS AND component_name IN_LIST EXCLUDE_COMPONENTS)
227            set(include 0)
228        endif()
229        if(include)
230            list(APPEND components ${component_name})
231        endif()
232    endforeach()
233
234    if(TESTS_ALL OR BUILD_TESTS OR TEST_COMPONENTS OR TEST_EXCLUDE_COMPONENTS)
235        spaces2list(TEST_COMPONENTS)
236        spaces2list(TEST_EXCLUDE_COMPONENTS)
237        idf_build_get_property(component_targets __COMPONENT_TARGETS)
238        foreach(component_target ${component_targets})
239            __component_get_property(component_dir ${component_target} COMPONENT_DIR)
240            __component_get_property(component_name ${component_target} COMPONENT_NAME)
241            if(component_name IN_LIST components)
242                set(include 1)
243                if(TEST_COMPONENTS AND NOT component_name IN_LIST TEST_COMPONENTS)
244                    set(include 0)
245                endif()
246                if(TEST_EXCLUDE_COMPONENTS AND component_name IN_LIST TEST_EXCLUDE_COMPONENTS)
247                    set(include 0)
248                endif()
249                if(include AND EXISTS ${component_dir}/test)
250                    __component_add(${component_dir}/test ${component_name})
251                    list(APPEND test_components ${component_name}::test)
252                endif()
253            endif()
254        endforeach()
255    endif()
256
257    set(${components_var} "${components}" PARENT_SCOPE)
258    set(${test_components_var} "${test_components}" PARENT_SCOPE)
259endfunction()
260
261# Trick to temporarily redefine project(). When functions are overriden in CMake, the originals can still be accessed
262# using an underscore prefixed function of the same name. The following lines make sure that __project  calls
263# the original project(). See https://cmake.org/pipermail/cmake/2015-October/061751.html.
264function(project)
265endfunction()
266
267function(_project)
268endfunction()
269
270macro(project project_name)
271    # Initialize project, preparing COMPONENTS argument for idf_build_process()
272    # call later using external COMPONENT_DIRS, COMPONENTS_DIRS, EXTRA_COMPONENTS_DIR,
273    # EXTRA_COMPONENTS_DIRS, COMPONENTS, EXLUDE_COMPONENTS, TEST_COMPONENTS,
274    # TEST_EXLUDE_COMPONENTS, TESTS_ALL, BUILD_TESTS
275    __project_init(components test_components)
276
277    __target_set_toolchain()
278
279    if(CCACHE_ENABLE)
280        find_program(CCACHE_FOUND ccache)
281        if(CCACHE_FOUND)
282            message(STATUS "ccache will be used for faster recompilation")
283            set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE ccache)
284        else()
285            message(WARNING "enabled ccache in build but ccache program not found")
286        endif()
287    endif()
288
289    # The actual call to project()
290    __project(${project_name} C CXX ASM)
291
292    # Generate compile_commands.json (needs to come after project call).
293    set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
294
295    # Since components can import third-party libraries, the original definition of project() should be restored
296    # before the call to add components to the build.
297    function(project)
298        set(project_ARGV ARGV)
299        __project(${${project_ARGV}})
300
301        # Set the variables that project() normally sets, documented in the
302        # command's docs.
303        #
304        # https://cmake.org/cmake/help/v3.5/command/project.html
305        #
306        # There is some nuance when it comes to setting version variables in terms of whether
307        # CMP0048 is set to OLD or NEW. However, the proper behavior should have bee already handled by the original
308        # project call, and we're just echoing the values those variables were set to.
309        set(PROJECT_NAME "${PROJECT_NAME}" PARENT_SCOPE)
310        set(PROJECT_BINARY_DIR "${PROJECT_BINARY_DIR}" PARENT_SCOPE)
311        set(PROJECT_SOURCE_DIR "${PROJECT_SOURCE_DIR}" PARENT_SCOPE)
312        set(PROJECT_VERSION "${PROJECT_VERSION}" PARENT_SCOPE)
313        set(PROJECT_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}" PARENT_SCOPE)
314        set(PROJECT_VERSION_MINOR "${PROJECT_VERSION_MINOR}" PARENT_SCOPE)
315        set(PROJECT_VERSION_PATCH "${PROJECT_VERSION_PATCH}" PARENT_SCOPE)
316        set(PROJECT_VERSION_TWEAK "${PROJECT_VERSION_TWEAK}" PARENT_SCOPE)
317
318        set(${PROJECT_NAME}_BINARY_DIR "${${PROJECT_NAME}_BINARY_DIR}" PARENT_SCOPE)
319        set(${PROJECT_NAME}_SOURCE_DIR "${${PROJECT_NAME}_SOURCE_DIR}" PARENT_SCOPE)
320        set(${PROJECT_NAME}_VERSION "${${PROJECT_NAME}_VERSION}" PARENT_SCOPE)
321        set(${PROJECT_NAME}_VERSION_MAJOR "${${PROJECT_NAME}_VERSION_MAJOR}" PARENT_SCOPE)
322        set(${PROJECT_NAME}_VERSION_MINOR "${${PROJECT_NAME}_VERSION_MINOR}" PARENT_SCOPE)
323        set(${PROJECT_NAME}_VERSION_PATCH "${${PROJECT_NAME}_VERSION_PATCH}" PARENT_SCOPE)
324        set(${PROJECT_NAME}_VERSION_TWEAK "${${PROJECT_NAME}_VERSION_TWEAK}" PARENT_SCOPE)
325    endfunction()
326
327    # Prepare the following arguments for the idf_build_process() call using external
328    # user values:
329    #
330    # SDKCONFIG_DEFAULTS is from external SDKCONFIG_DEFAULTS
331    # SDKCONFIG is from external SDKCONFIG
332    # BUILD_DIR is set to project binary dir
333    #
334    # PROJECT_NAME is taken from the passed name from project() call
335    # PROJECT_DIR is set to the current directory
336    # PROJECT_VER is from the version text or git revision of the current repo
337    set(_sdkconfig_defaults "$ENV{SDKCONFIG_DEFAULTS}")
338
339    if(NOT _sdkconfig_defaults)
340        if(EXISTS "${CMAKE_SOURCE_DIR}/sdkconfig.defaults")
341            set(_sdkconfig_defaults "${CMAKE_SOURCE_DIR}/sdkconfig.defaults")
342        else()
343            set(_sdkconfig_defaults "")
344        endif()
345    endif()
346
347    if(SDKCONFIG_DEFAULTS)
348        set(_sdkconfig_defaults "${SDKCONFIG_DEFAULTS}")
349    endif()
350
351    foreach(sdkconfig_default ${_sdkconfig_defaults})
352        get_filename_component(sdkconfig_default "${sdkconfig_default}" ABSOLUTE)
353        if(NOT EXISTS "${sdkconfig_default}")
354            message(FATAL_ERROR "SDKCONFIG_DEFAULTS '${sdkconfig_default}' does not exist.")
355        endif()
356        list(APPEND sdkconfig_defaults ${sdkconfig_default})
357    endforeach()
358
359    if(SDKCONFIG)
360        get_filename_component(sdkconfig "${SDKCONFIG}" ABSOLUTE)
361    else()
362        set(sdkconfig "${CMAKE_CURRENT_LIST_DIR}/sdkconfig")
363    endif()
364
365    if(BUILD_DIR)
366        get_filename_component(build_dir "${BUILD_DIR}" ABSOLUTE)
367        if(NOT EXISTS "${build_dir}")
368            message(FATAL_ERROR "BUILD_DIR '${build_dir}' does not exist.")
369        endif()
370    else()
371        set(build_dir ${CMAKE_BINARY_DIR})
372    endif()
373
374    __project_get_revision(project_ver)
375
376    message(STATUS "Building ESP-IDF components for target ${IDF_TARGET}")
377
378    idf_build_process(${IDF_TARGET}
379                    SDKCONFIG_DEFAULTS "${sdkconfig_defaults}"
380                    SDKCONFIG ${sdkconfig}
381                    BUILD_DIR ${build_dir}
382                    PROJECT_NAME ${CMAKE_PROJECT_NAME}
383                    PROJECT_DIR ${CMAKE_CURRENT_LIST_DIR}
384                    PROJECT_VER "${project_ver}"
385                    COMPONENTS "${components};${test_components}")
386
387    # Special treatment for 'main' component for standard projects (not part of core build system).
388    # Have it depend on every other component in the build. This is
389    # a convenience behavior for the standard project; thus is done outside of the core build system
390    # so that it treats components equally.
391    #
392    # This behavior should only be when user did not set REQUIRES/PRIV_REQUIRES manually.
393    idf_build_get_property(build_components BUILD_COMPONENT_ALIASES)
394    if(idf::main IN_LIST build_components)
395        __component_get_target(main_target idf::main)
396        __component_get_property(reqs ${main_target} REQUIRES)
397        __component_get_property(priv_reqs ${main_target} PRIV_REQUIRES)
398        idf_build_get_property(common_reqs __COMPONENT_REQUIRES_COMMON)
399        if(reqs STREQUAL common_reqs AND NOT priv_reqs) #if user has not set any requirements
400            if(test_components)
401                list(REMOVE_ITEM build_components ${test_components})
402            endif()
403            list(REMOVE_ITEM build_components idf::main)
404            __component_get_property(lib ${main_target} COMPONENT_LIB)
405            set_property(TARGET ${lib} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "${build_components}")
406            get_property(type TARGET ${lib} PROPERTY TYPE)
407            if(type STREQUAL STATIC_LIBRARY)
408                set_property(TARGET ${lib} APPEND PROPERTY LINK_LIBRARIES "${build_components}")
409            endif()
410        endif()
411    endif()
412
413    set(project_elf ${CMAKE_PROJECT_NAME}.elf)
414
415    # Create a dummy file to work around CMake requirement of having a source file while adding an
416    # executable. This is also used by idf_size.py to detect the target
417    set(project_elf_src ${CMAKE_BINARY_DIR}/project_elf_src_${IDF_TARGET}.c)
418    add_custom_command(OUTPUT ${project_elf_src}
419        COMMAND ${CMAKE_COMMAND} -E touch ${project_elf_src}
420        VERBATIM)
421    add_custom_target(_project_elf_src DEPENDS "${project_elf_src}")
422    add_executable(${project_elf} "${project_elf_src}")
423    add_dependencies(${project_elf} _project_elf_src)
424
425    if(__PROJECT_GROUP_LINK_COMPONENTS)
426        target_link_libraries(${project_elf} "-Wl,--start-group")
427    endif()
428
429    if(test_components)
430        target_link_libraries(${project_elf} "-Wl,--whole-archive")
431        foreach(test_component ${test_components})
432            if(TARGET ${test_component})
433                target_link_libraries(${project_elf} ${test_component})
434            endif()
435        endforeach()
436        target_link_libraries(${project_elf} "-Wl,--no-whole-archive")
437    endif()
438
439    idf_build_get_property(build_components BUILD_COMPONENT_ALIASES)
440    if(test_components)
441        list(REMOVE_ITEM build_components ${test_components})
442    endif()
443    target_link_libraries(${project_elf} ${build_components})
444
445    if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
446        set(mapfile "${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.map")
447        target_link_libraries(${project_elf} "-Wl,--cref" "-Wl,--Map=\"${mapfile}\"")
448    endif()
449
450    set_property(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" APPEND PROPERTY
451        ADDITIONAL_MAKE_CLEAN_FILES
452        "${mapfile}" "${project_elf_src}")
453
454    idf_build_get_property(idf_path IDF_PATH)
455    idf_build_get_property(python PYTHON)
456
457    set(idf_size ${python} ${idf_path}/tools/idf_size.py)
458    if(DEFINED OUTPUT_JSON AND OUTPUT_JSON)
459        list(APPEND idf_size "--json")
460    endif()
461
462    # Add size targets, depend on map file, run idf_size.py
463    add_custom_target(size
464        DEPENDS ${mapfile}
465        COMMAND ${idf_size} ${mapfile}
466        )
467    add_custom_target(size-files
468        DEPENDS ${mapfile}
469        COMMAND ${idf_size} --files ${mapfile}
470        )
471    add_custom_target(size-components
472        DEPENDS ${mapfile}
473        COMMAND ${idf_size} --archives ${mapfile}
474        )
475
476    unset(idf_size)
477
478    # Add DFU build and flash targets
479    __add_dfu_targets()
480
481    # Add UF2 build targets
482    __add_uf2_targets()
483
484    idf_build_executable(${project_elf})
485
486    __project_info("${test_components}")
487endmacro()
488