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