expand_requirements.cmake 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. # expand_requirements.cmake is a utility cmake script to expand component requirements early in the build,
  2. # before the components are ready to be included.
  3. #
  4. # Parameters:
  5. # - COMPONENTS = Space-separated list of initial components to include in the build.
  6. # Can be empty, in which case all components are in the build.
  7. # - COMPONENT_REQUIRES_COMMON = Components to always include in the build, and treated as dependencies
  8. # of all other components.
  9. # - DEPENDENCIES_FILE = Path of generated cmake file which will contain the expanded dependencies for these
  10. # components.
  11. # - COMPONENT_DIRS = List of paths to search for all components.
  12. # - DEBUG = Set -DDEBUG=1 to debug component lists in the build.
  13. #
  14. # If successful, DEPENDENCIES_FILE can be expanded to set BUILD_COMPONENTS & BUILD_COMPONENT_PATHS with all
  15. # components required for the build, and the get_component_requirements() function to return each component's
  16. # recursively expanded requirements.
  17. #
  18. # BUILD_COMPONENTS & BUILD_COMPONENT_PATHS will be ordered in a best-effort way so that dependencies are listed first.
  19. # (Note that IDF supports cyclic dependencies, and dependencies in a cycle have ordering guarantees.)
  20. #
  21. # Determinism:
  22. #
  23. # Given the the same list of names in COMPONENTS (regardless of order), and an identical value of
  24. # COMPONENT_REQUIRES_COMMON, and all the same COMPONENT_REQUIRES & COMPONENT_PRIV_REQUIRES values in
  25. # each component, then the output of BUILD_COMPONENTS should always be in the same
  26. # order.
  27. #
  28. # BUILD_COMPONENT_PATHS will be in the same component order as BUILD_COMPONENTS, even if the
  29. # actual component paths are different due to different paths.
  30. #
  31. # TODO: Error out if a component requirement is missing
  32. cmake_minimum_required(VERSION 3.5)
  33. include("${IDF_PATH}/tools/cmake/utilities.cmake")
  34. include("${IDF_PATH}/tools/cmake/component_utils.cmake")
  35. include("${IDF_PATH}/tools/cmake/version.cmake")
  36. set(ESP_PLATFORM 1)
  37. if(NOT DEPENDENCIES_FILE)
  38. message(FATAL_ERROR "DEPENDENCIES_FILE must be set.")
  39. endif()
  40. if(NOT COMPONENT_DIRS)
  41. message(FATAL_ERROR "COMPONENT_DIRS variable must be set")
  42. endif()
  43. spaces2list(COMPONENT_DIRS)
  44. spaces2list(COMPONENT_REQUIRES_COMMON)
  45. # Dummy register_component used to save requirements variables as global properties, for later expansion
  46. #
  47. # (expand_component_requirements() includes the component CMakeLists.txt, which then sets its component variables,
  48. # calls this dummy macro, and immediately exits again.)
  49. macro(register_component)
  50. if(COMPONENT STREQUAL main AND NOT COMPONENT_REQUIRES)
  51. set(main_component_requires ${COMPONENTS})
  52. list(REMOVE_ITEM main_component_requires "main")
  53. set_property(GLOBAL PROPERTY "${COMPONENT}_REQUIRES" "${main_component_requires}")
  54. else()
  55. spaces2list(COMPONENT_REQUIRES)
  56. set_property(GLOBAL PROPERTY "${COMPONENT}_REQUIRES" "${COMPONENT_REQUIRES}")
  57. endif()
  58. spaces2list(COMPONENT_PRIV_REQUIRES)
  59. set_property(GLOBAL PROPERTY "${COMPONENT}_PRIV_REQUIRES" "${COMPONENT_PRIV_REQUIRES}")
  60. # This is tricky: we override register_component() so it returns out of the component CMakeLists.txt
  61. # (as we're declaring it as a macro not a function, so it doesn't have its own scope.)
  62. #
  63. # This means no targets are defined, and the component expansion ends early.
  64. return()
  65. endmacro()
  66. macro(register_config_only_component)
  67. register_component()
  68. endmacro()
  69. function(require_idf_targets)
  70. if(NOT ${IDF_TARGET} IN_LIST ARGN)
  71. message(FATAL_ERROR "Component ${COMPONENT_NAME} only supports targets: ${ARGN}")
  72. endif()
  73. endfunction()
  74. # expand_component_requirements: Recursively expand a component's requirements,
  75. # setting global properties BUILD_COMPONENTS & BUILD_COMPONENT_PATHS and
  76. # also invoking the components to call register_component() above,
  77. # which will add per-component global properties with dependencies, etc.
  78. function(expand_component_requirements component)
  79. get_property(seen_components GLOBAL PROPERTY SEEN_COMPONENTS)
  80. if(component IN_LIST seen_components)
  81. return() # already added, or in process of adding, this component
  82. endif()
  83. set_property(GLOBAL APPEND PROPERTY SEEN_COMPONENTS ${component})
  84. find_component_path("${component}" "${ALL_COMPONENTS}" "${ALL_COMPONENT_PATHS}" COMPONENT_PATH)
  85. debug("Expanding dependencies of ${component} @ ${COMPONENT_PATH}")
  86. if(NOT COMPONENT_PATH)
  87. set_property(GLOBAL APPEND PROPERTY COMPONENTS_NOT_FOUND ${component})
  88. return()
  89. endif()
  90. # include the component CMakeLists.txt to expand its properties
  91. # into the global cache (via register_component(), above)
  92. unset(COMPONENT_REQUIRES)
  93. unset(COMPONENT_PRIV_REQUIRES)
  94. set(COMPONENT ${component})
  95. include(${COMPONENT_PATH}/CMakeLists.txt)
  96. get_property(requires GLOBAL PROPERTY "${component}_REQUIRES")
  97. get_property(requires_priv GLOBAL PROPERTY "${component}_PRIV_REQUIRES")
  98. # Recurse dependencies first, so that they appear first in the list (when possible)
  99. foreach(req ${COMPONENT_REQUIRES_COMMON} ${requires} ${requires_priv})
  100. expand_component_requirements(${req})
  101. endforeach()
  102. list(FIND TEST_COMPONENTS ${component} idx)
  103. if(NOT idx EQUAL -1)
  104. list(GET TEST_COMPONENTS ${idx} test_component)
  105. list(GET TEST_COMPONENT_PATHS ${idx} test_component_path)
  106. set_property(GLOBAL APPEND PROPERTY BUILD_TEST_COMPONENTS ${test_component})
  107. set_property(GLOBAL APPEND PROPERTY BUILD_TEST_COMPONENT_PATHS ${test_component_path})
  108. endif()
  109. # Now append this component to the full list (after its dependencies)
  110. set_property(GLOBAL APPEND PROPERTY BUILD_COMPONENT_PATHS ${COMPONENT_PATH})
  111. set_property(GLOBAL APPEND PROPERTY BUILD_COMPONENTS ${component})
  112. endfunction()
  113. # filter_components_list: Filter the components included in the build
  114. # as specified by the user. Or, in the case of unit testing, filter out
  115. # the test components to be built.
  116. macro(filter_components_list)
  117. spaces2list(COMPONENTS)
  118. spaces2list(EXCLUDE_COMPONENTS)
  119. spaces2list(TEST_COMPONENTS)
  120. spaces2list(TEST_EXCLUDE_COMPONENTS)
  121. list(LENGTH ALL_COMPONENTS all_components_length)
  122. math(EXPR all_components_length "${all_components_length} - 1")
  123. foreach(component_idx RANGE 0 ${all_components_length})
  124. list(GET ALL_COMPONENTS ${component_idx} component)
  125. list(GET ALL_COMPONENT_PATHS ${component_idx} component_path)
  126. if(COMPONENTS)
  127. if(${component} IN_LIST COMPONENTS)
  128. set(add_component 1)
  129. else()
  130. set(add_component 0)
  131. endif()
  132. else()
  133. set(add_component 1)
  134. endif()
  135. if(NOT ${component} IN_LIST EXCLUDE_COMPONENTS AND add_component EQUAL 1)
  136. list(APPEND components ${component})
  137. list(APPEND component_paths ${component_path})
  138. if(BUILD_TESTS EQUAL 1)
  139. if(TEST_COMPONENTS)
  140. if(${component} IN_LIST TEST_COMPONENTS)
  141. set(add_test_component 1)
  142. else()
  143. set(add_test_component 0)
  144. endif()
  145. else()
  146. set(add_test_component 1)
  147. endif()
  148. if(${component} IN_LIST ALL_TEST_COMPONENTS)
  149. if(NOT ${component} IN_LIST TEST_EXCLUDE_COMPONENTS AND add_test_component EQUAL 1)
  150. list(APPEND test_components ${component}_test)
  151. list(APPEND test_component_paths ${component_path}/test)
  152. list(APPEND components ${component}_test)
  153. list(APPEND component_paths ${component_path}/test)
  154. endif()
  155. endif()
  156. endif()
  157. endif()
  158. endforeach()
  159. set(COMPONENTS ${components})
  160. set(TEST_COMPONENTS ${test_components})
  161. set(TEST_COMPONENT_PATHS ${test_component_paths})
  162. list(APPEND ALL_COMPONENTS "${TEST_COMPONENTS}")
  163. list(APPEND ALL_COMPONENT_PATHS "${TEST_COMPONENT_PATHS}")
  164. endmacro()
  165. # Main functionality goes here
  166. # Find every available component in COMPONENT_DIRS, save as ALL_COMPONENT_PATHS and ALL_COMPONENTS
  167. components_find_all("${COMPONENT_DIRS}" ALL_COMPONENT_PATHS ALL_COMPONENTS ALL_TEST_COMPONENTS)
  168. filter_components_list()
  169. debug("ALL_COMPONENT_PATHS ${ALL_COMPONENT_PATHS}")
  170. debug("ALL_COMPONENTS ${ALL_COMPONENTS}")
  171. debug("ALL_TEST_COMPONENTS ${ALL_TEST_COMPONENTS}")
  172. set_property(GLOBAL PROPERTY SEEN_COMPONENTS "") # anti-infinite-recursion
  173. set_property(GLOBAL PROPERTY BUILD_COMPONENTS "")
  174. set_property(GLOBAL PROPERTY BUILD_COMPONENT_PATHS "")
  175. set_property(GLOBAL PROPERTY BUILD_TEST_COMPONENTS "")
  176. set_property(GLOBAL PROPERTY BUILD_TEST_COMPONENT_PATHS "")
  177. set_property(GLOBAL PROPERTY COMPONENTS_NOT_FOUND "")
  178. # Indicate that the component CMakeLists.txt is being included in the early expansion phase of the build,
  179. # and might not want to execute particular operations.
  180. set(CMAKE_BUILD_EARLY_EXPANSION 1)
  181. foreach(component ${COMPONENTS})
  182. debug("Expanding initial component ${component}")
  183. expand_component_requirements(${component})
  184. endforeach()
  185. unset(CMAKE_BUILD_EARLY_EXPANSION)
  186. get_property(build_components GLOBAL PROPERTY BUILD_COMPONENTS)
  187. get_property(build_component_paths GLOBAL PROPERTY BUILD_COMPONENT_PATHS)
  188. get_property(build_test_components GLOBAL PROPERTY BUILD_TEST_COMPONENTS)
  189. get_property(build_test_component_paths GLOBAL PROPERTY BUILD_TEST_COMPONENT_PATHS)
  190. get_property(not_found GLOBAL PROPERTY COMPONENTS_NOT_FOUND)
  191. debug("components in build: ${build_components}")
  192. debug("components in build: ${build_component_paths}")
  193. debug("components not found: ${not_found}")
  194. function(line contents)
  195. file(APPEND "${DEPENDENCIES_FILE}.tmp" "${contents}\n")
  196. endfunction()
  197. file(WRITE "${DEPENDENCIES_FILE}.tmp" "# Component requirements generated by expand_requirements.cmake\n\n")
  198. line("set(BUILD_COMPONENTS ${build_components})")
  199. line("set(BUILD_COMPONENT_PATHS ${build_component_paths})")
  200. line("set(BUILD_TEST_COMPONENTS ${build_test_components})")
  201. line("set(BUILD_TEST_COMPONENT_PATHS ${build_test_component_paths})")
  202. line("")
  203. line("# get_component_requirements: Generated function to read the dependencies of a given component.")
  204. line("#")
  205. line("# Parameters:")
  206. line("# - component: Name of component")
  207. line("# - var_requires: output variable name. Set to recursively expanded COMPONENT_REQUIRES ")
  208. line("# for this component.")
  209. line("# - var_private_requires: output variable name. Set to recursively expanded COMPONENT_PRIV_REQUIRES ")
  210. line("# for this component.")
  211. line("#")
  212. line("# Throws a fatal error if 'componeont' is not found (indicates a build system problem).")
  213. line("#")
  214. line("function(get_component_requirements component var_requires var_private_requires)")
  215. foreach(build_component ${build_components})
  216. get_property(reqs GLOBAL PROPERTY "${build_component}_REQUIRES")
  217. get_property(private_reqs GLOBAL PROPERTY "${build_component}_PRIV_REQUIRES")
  218. line(" if(\"\$\{component}\" STREQUAL \"${build_component}\")")
  219. line(" set(\${var_requires} \"${reqs}\" PARENT_SCOPE)")
  220. line(" set(\${var_private_requires} \"${private_reqs}\" PARENT_SCOPE)")
  221. line(" return()")
  222. line(" endif()")
  223. endforeach()
  224. line(" message(FATAL_ERROR \"Component not found: \${component}\")")
  225. line("endfunction()")
  226. # only replace DEPENDENCIES_FILE if it has changed (prevents ninja/make build loops.)
  227. execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different "${DEPENDENCIES_FILE}.tmp" "${DEPENDENCIES_FILE}")
  228. execute_process(COMMAND ${CMAKE_COMMAND} -E remove "${DEPENDENCIES_FILE}.tmp")