SettingsRegistry.cmake 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. #
  2. # Copyright (c) Contributors to the Open 3D Engine Project.
  3. # For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. #
  5. # SPDX-License-Identifier: Apache-2.0 OR MIT
  6. #
  7. #
  8. # Responsible for generating a settings registry file containing the moduleload dependencies of any ly_delayed_load_targets
  9. # This is used for example, to allow Open 3D Engine Applications to know which set of gems have built for a particular project/target
  10. # combination
  11. include_guard()
  12. #templates used to generate the dependencies setreg files
  13. set(root_dependencies_setreg_template [[
  14. {
  15. @gem_metadata_objects@
  16. }
  17. ]]
  18. )
  19. set(gems_json_template [[
  20. "O3DE": {
  21. "Gems": {
  22. @gem_setreg_objects@
  23. }
  24. }]]
  25. )
  26. string(APPEND gem_module_template
  27. [=[ "@stripped_gem_target@": {]=] "\n"
  28. [=[$<$<IN_LIST:$<TARGET_PROPERTY:@gem_target@,TYPE>,MODULE_LIBRARY$<SEMICOLON>SHARED_LIBRARY>: "Modules":["$<TARGET_FILE_NAME:@gem_target@>"]]=] "\n>"
  29. [=[ }]=]
  30. )
  31. string(APPEND gem_name_template
  32. [=[ "@gem_name@": {]=] "\n"
  33. [=[ "Targets": {]=] "\n"
  34. [=[@gem_target_objects@]=] "\n"
  35. [=[ }]=] "\n"
  36. [=[ }]=]
  37. )
  38. #!ly_detect_cycle_through_visitation: Detects if there is a cycle based on a list of visited
  39. # items. If the passed item is in the list, then there is a cycle.
  40. # \arg:item - item being checked for the cycle
  41. # \arg:visited_items - list of visited items
  42. # \arg:visited_items_var - list of visited items variable, "item" will be added to the list
  43. # \arg:cycle(variable) - empty string if there is no cycle (an empty string in cmake evaluates
  44. # to false). If there is a cycle a cycle dependency string detailing the sequence of items
  45. # that produce a cycle, e.g. A --> B --> C --> A
  46. #
  47. function(ly_detect_cycle_through_visitation item visited_items visited_items_var cycle)
  48. if(item IN_LIST visited_items)
  49. unset(dependency_cycle_loop)
  50. foreach(visited_item IN LISTS visited_items)
  51. string(APPEND dependency_cycle_loop ${visited_item})
  52. if(visited_item STREQUAL item)
  53. string(APPEND dependency_cycle_loop " (cycle starts)")
  54. endif()
  55. string(APPEND dependency_cycle_loop " --> ")
  56. endforeach()
  57. string(APPEND dependency_cycle_loop "${item} (cycle ends)")
  58. set(${cycle} "${dependency_cycle_loop}" PARENT_SCOPE)
  59. else()
  60. set(cycle "" PARENT_SCOPE) # no cycles
  61. endif()
  62. list(APPEND visited_items ${item})
  63. set(${visited_items_var} "${visited_items}" PARENT_SCOPE)
  64. endfunction()
  65. #!o3de_get_gem_load_dependencies: Retrieves the list of "load" dependencies for a target
  66. # Visits through MANUALLY_ADDED_DEPENDENCIES of targets with a GEM_MODULE property
  67. # to determine which gems a target needs to load
  68. #
  69. # NOTE: ly_get_runtime_dependencies cannot be used as it will recurse through non-manually added dependencies
  70. # to add manually added added which results in false load dependencies.
  71. # \arg:GEM_LOAD_DEPENDENCIES(name) - Output variable to be populated gem load dependencies
  72. # \arg:TARGET(TARGET) - CMake target to examine for dependencies
  73. # \arg:CYCLE_DETECTION_SET(variable name)[optional] - Used to track cycles in load dependencies
  74. function(o3de_get_gem_load_dependencies)
  75. set(options)
  76. set(oneValueArgs TARGET GEM_LOAD_DEPENDENCIES_VAR)
  77. set(multiValueArgs CYCLE_DETECTION_SET)
  78. cmake_parse_arguments(o3de_get_gem_load_dependencies "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  79. if(NOT TARGET ${o3de_get_gem_load_dependencies_TARGET})
  80. return() # Nothing to do
  81. endif()
  82. # Define the indent level variable to 0 in the first iteration
  83. if(NOT DEFINED indent_level)
  84. set(indent_level "0")
  85. endif()
  86. # Set indent string for current indent level
  87. string(REPEAT " " "${indent_level}" indent)
  88. set(target ${o3de_get_gem_load_dependencies_TARGET})
  89. set(gem_load_dependencies_var ${o3de_get_gem_load_dependencies_GEM_LOAD_DEPENDENCIES_VAR})
  90. set(cycle_detection_targets ${o3de_get_gem_load_dependencies_CYCLE_DETECTION_SET})
  91. # Optimize the search by caching gem load dependencies
  92. get_property(are_dependencies_cached GLOBAL PROPERTY "LY_GEM_LOAD_DEPENDENCIES,${target}" SET)
  93. if(are_dependencies_cached)
  94. # We already walked through this target
  95. get_property(cached_dependencies GLOBAL PROPERTY "LY_GEM_LOAD_DEPENDENCIES,${target}")
  96. set(${gem_load_dependencies_var} ${cached_dependencies} PARENT_SCOPE)
  97. message(DEBUG "${indent}Found cache load dependencies for Gem Target \"${target}\": ${cached_dependencies}")
  98. return()
  99. endif()
  100. message(DEBUG "${indent}Visiting target \"${target}\" when looking for gem module load dependencies")
  101. # detect cycles
  102. unset(cycle_detected)
  103. ly_detect_cycle_through_visitation(${target} "${cycle_detection_targets}" cycle_detection_targets cycle_detected)
  104. if(cycle_detected)
  105. message(FATAL_ERROR "Runtime dependency detected: ${cycle_detected}")
  106. endif()
  107. # For load dependencies, we want to copy over the dependency and traverse them
  108. # Only if the dependency has a GEM_MODULE property
  109. get_property(manually_added_dependencies TARGET ${target} PROPERTY MANUALLY_ADDED_DEPENDENCIES)
  110. unset(load_dependencies)
  111. if(manually_added_dependencies)
  112. list(APPEND load_dependencies ${manually_added_dependencies})
  113. message(VERBOSE "${indent}Gem Target \"${target}\" has direct manually added dependencies of: ${manually_added_dependencies}")
  114. endif()
  115. # unset all_gem_load_dependencies to prevent using variable values from the PARENT_SCOPE
  116. unset(all_gem_load_dependencies)
  117. # Remove duplicate load dependencies
  118. list(REMOVE_DUPLICATES load_dependencies)
  119. foreach(load_dependency IN LISTS load_dependencies)
  120. # Skip wrapping produced when targets are not created in the same directory
  121. ly_de_alias_target(${load_dependency} dealias_load_dependency)
  122. get_property(is_gem_target TARGET ${dealias_load_dependency} PROPERTY GEM_MODULE SET)
  123. # If the dependency is a "gem module" then add it as a load dependencies
  124. # and recurse into its manually added dependencies
  125. if (is_gem_target)
  126. # shift identation level for recursive calls to ${CMAKE_CURRENT_FUNCTION}
  127. set(current_indent_level "${indent_level}")
  128. math(EXPR indent_level "${current_indent_level} + 2")
  129. unset(dependencies)
  130. o3de_get_gem_load_dependencies(
  131. GEM_LOAD_DEPENDENCIES_VAR dependencies
  132. TARGET ${dealias_load_dependency}
  133. CYCLE_DETECTION_SET "${cycle_detection_set}"
  134. )
  135. # restore indentation level
  136. set(indent_level "${current_indent_level}")
  137. list(APPEND all_gem_load_dependencies ${dependencies})
  138. list(APPEND all_gem_load_dependencies ${dealias_load_dependency})
  139. endif()
  140. endforeach()
  141. list(REMOVE_DUPLICATES all_gem_load_dependencies)
  142. set_property(GLOBAL PROPERTY "LY_GEM_LOAD_DEPENDENCIES,${target}" "${all_gem_load_dependencies}")
  143. set(${gem_load_dependencies_var} ${all_gem_load_dependencies} PARENT_SCOPE)
  144. message(VERBOSE "${indent}Gem Target \"${target}\" has load dependencies of: ${all_gem_load_dependencies}")
  145. endfunction()
  146. #!o3de_get_gem_root_from_target: Uses the supplied gem_target to lookup the nearest
  147. # gem.json file at an ancestor of targets SOURCE_DIR property
  148. #
  149. # \arg:gem_target(TARGET) - Target to look upwards from using its SOURCE_DIR property
  150. function(o3de_get_gem_root_from_target output_gem_root output_gem_name gem_target)
  151. unset(${output_gem_root} PARENT_SCOPE)
  152. get_property(gem_source_dir TARGET ${gem_target} PROPERTY SOURCE_DIR)
  153. # the o3de_find_ancestor_gem_root looks up the nearest gem root path
  154. # at or above the current directory visited by cmake
  155. o3de_find_ancestor_gem_root(gem_source_dir gem_name "${gem_source_dir}")
  156. # Set the gem module root output directory to the location with the gem.json file within it or
  157. # the supplied gem_target SOURCE_DIR location if no gem.json file was found
  158. set(${output_gem_root} ${gem_source_dir} PARENT_SCOPE)
  159. # Set the gem name output value to the name of the gem as in the gem.json file
  160. if(gem_name)
  161. set(${output_gem_name} ${gem_name} PARENT_SCOPE)
  162. endif()
  163. endfunction()
  164. #!ly_populate_gem_objects: Creates a mapping of gem name -> structure of gem module targets and source paths
  165. #
  166. # \arg:output_gem_setreg_object- Variable to populate with configured gem_name_template for each gem dependency
  167. function(ly_populate_gem_objects output_gem_setreg_objects all_gem_dependencies)
  168. unset(gem_name_list)
  169. foreach(gem_target ${all_gem_dependencies})
  170. unset(gem_relative_source_dir)
  171. # Create path from the LY_ROOT_FOLDER to the the Gem directory
  172. if (NOT TARGET ${gem_target})
  173. message(FATAL_ERROR "Dependency ${gem_target} from ${target} does not exist")
  174. endif()
  175. o3de_get_gem_root_from_target(gem_module_root gem_name_value ${gem_target})
  176. if (NOT gem_module_root)
  177. # If the target doesn't have a gem.json, skip it
  178. continue()
  179. endif()
  180. if (NOT gem_name_value IN_LIST gem_name_list)
  181. list(APPEND gem_name_list ${gem_name_value})
  182. endif()
  183. # De-alias namespace from gem targets before configuring them into the json template
  184. ly_de_alias_target(${gem_target} stripped_gem_target)
  185. string(CONFIGURE "${gem_module_template}" gem_module_json @ONLY)
  186. # Create a "mapping" of gem_name to gem module configured object
  187. list(APPEND gem_root_target_objects_${gem_name_value} ${gem_module_json})
  188. endforeach()
  189. unset(gem_setreg_objects)
  190. foreach(gem_name IN LISTS gem_name_list)
  191. # Set the values for the gem_name_template
  192. set(gem_target_objects ${gem_root_target_objects_${gem_name}})
  193. list(JOIN gem_target_objects ",\n" gem_target_objects)
  194. string(CONFIGURE "${gem_name_template}" gem_setreg_object @ONLY)
  195. list(APPEND gem_setreg_objects ${gem_setreg_object})
  196. endforeach()
  197. # Sets the configured values of the gem_name_template into the output argument
  198. list(JOIN gem_setreg_objects ",\n" gem_setreg_objects)
  199. string(CONFIGURE "${gems_json_template}" gem_metadata_objects @ONLY)
  200. # Configure the root template for the cmake_dependencies setreg file
  201. string(CONFIGURE "${root_dependencies_setreg_template}" root_setreg @ONLY)
  202. set(${output_gem_setreg_objects} ${root_setreg} PARENT_SCOPE)
  203. endfunction()
  204. #! ly_delayed_generate_settings_registry: Generates a .setreg file for each target with dependencies
  205. # added to it via ly_add_target_dependencies
  206. # The generated file contains the file to the each dependent targets
  207. # This can be used for example to determine which list of gems to load with an application
  208. function(ly_delayed_generate_settings_registry)
  209. if(LY_MONOLITHIC_GAME) # No need to generate setregs for monolithic builds
  210. set_property(GLOBAL PROPERTY LY_DELAYED_LOAD_DEPENDENCIES) # Clear out the load targets from the global load dependencies list
  211. return()
  212. endif()
  213. get_property(ly_delayed_load_targets GLOBAL PROPERTY LY_DELAYED_LOAD_DEPENDENCIES)
  214. foreach(prefix_target_variant ${ly_delayed_load_targets})
  215. string(REPLACE "," ";" prefix_target_variant_list "${prefix_target_variant}")
  216. list(LENGTH prefix_target_variant_list prefix_target_variant_length)
  217. if(prefix_target_variant_length EQUAL 0)
  218. continue()
  219. endif()
  220. # Retrieves the prefix if available from the front of the list
  221. list(POP_FRONT prefix_target_variant_list prefix)
  222. # Retrieve the target name from the next element of the list
  223. list(POP_FRONT prefix_target_variant_list target)
  224. # Retreive the variant name from the last element of the list
  225. list(POP_BACK prefix_target_variant_list variant)
  226. # Get the gem dependencies for the given project and target combination
  227. get_property(target_load_dependencies GLOBAL PROPERTY LY_DELAYED_LOAD_"${prefix_target_variant}")
  228. message(VERBOSE "prefix=${prefix},target=${target},variant=${variant} has direct load dependencies of ${target_load_dependencies}")
  229. list(REMOVE_DUPLICATES target_load_dependencies) # Strip out any duplicate load dependency CMake targets
  230. unset(all_gem_dependencies)
  231. foreach(gem_target IN LISTS target_load_dependencies)
  232. o3de_get_gem_load_dependencies(
  233. GEM_LOAD_DEPENDENCIES_VAR gem_load_gem_dependencies
  234. TARGET "${gem_target}"
  235. )
  236. list(APPEND all_gem_dependencies ${gem_load_gem_dependencies} ${gem_target})
  237. endforeach()
  238. list(REMOVE_DUPLICATES all_gem_dependencies)
  239. # de-namespace them
  240. unset(new_gem_dependencies)
  241. foreach(gem_target ${all_gem_dependencies})
  242. ly_de_alias_target(${gem_target} stripped_gem_target)
  243. list(APPEND new_gem_dependencies ${stripped_gem_target})
  244. endforeach()
  245. set(all_gem_dependencies ${new_gem_dependencies})
  246. list(REMOVE_DUPLICATES all_gem_dependencies)
  247. # Fill out the gem_setreg_objects variable with the json fields for each gem
  248. unset(gem_setreg_objects)
  249. ly_populate_gem_objects(gem_load_dependencies_json "${all_gem_dependencies}")
  250. string(REPLACE "." "_" escaped_target ${target})
  251. string(JOIN "." specialization_name ${prefix} ${escaped_target})
  252. # Lowercase the specialization name
  253. string(TOLOWER ${specialization_name} specialization_name)
  254. get_target_property(is_imported ${target} IMPORTED)
  255. get_target_property(target_type ${target} TYPE)
  256. set(non_loadable_types "UTILITY" "INTERFACE_LIBRARY" "STATIC_LIBRARY")
  257. if(is_imported OR (target_type IN_LIST non_loadable_types))
  258. unset(target_dir)
  259. foreach(conf IN LISTS CMAKE_CONFIGURATION_TYPES)
  260. string(TOUPPER ${conf} UCONF)
  261. string(APPEND target_dir $<$<CONFIG:${conf}>:${CMAKE_RUNTIME_OUTPUT_DIRECTORY_${UCONF}}>)
  262. endforeach()
  263. else()
  264. set(target_dir $<TARGET_FILE_DIR:${target}>)
  265. endif()
  266. set(dependencies_setreg ${target_dir}/Registry/cmake_dependencies.${specialization_name}.setreg)
  267. file(GENERATE OUTPUT ${dependencies_setreg} CONTENT "${gem_load_dependencies_json}")
  268. set_property(TARGET ${target} APPEND PROPERTY INTERFACE_LY_TARGET_FILES "${dependencies_setreg}\nRegistry")
  269. # Clear out load dependencies for the prefix,target,variant combination
  270. set_property(GLOBAL PROPERTY LY_DELAYED_LOAD_"${prefix_target_variant}")
  271. endforeach()
  272. # Clear out the load targets from the global load dependencies list
  273. set_property(GLOBAL PROPERTY LY_DELAYED_LOAD_DEPENDENCIES)
  274. endfunction()