Subdirectories.cmake 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  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. include_guard()
  9. ################################################################################
  10. # Subdirectory processing
  11. ################################################################################
  12. # Add a GLOBAL property which can be used to quickly determine if a directory is an external subdirectory
  13. get_property(cache_external_subdirs CACHE O3DE_EXTERNAL_SUBDIRS PROPERTY VALUE)
  14. foreach(cache_external_subdir IN LISTS cache_external_subdirs)
  15. file(REAL_PATH ${cache_external_subdir} real_external_subdir)
  16. set_property(GLOBAL PROPERTY "O3DE_SUBDIRECTORY_${real_external_subdir}" TRUE)
  17. endforeach()
  18. # The visited_gem_name_set is used to append the external_subdirectories found
  19. # within descendant gems to the parants to the globl O3DE_EXTERNAL_SUBDIRS_GEM_<gem_name> property
  20. # i.e If `GemA` "external_subdirectories" points to `GemB` and `GemB` external_subdirectories
  21. # points to `GemC`.
  22. # Then O3DE_EXTERNAL_SUBDIRS_GEM_GemA = [<AbsPath GemB>, <AbsPath GemC>]
  23. # And O3DE_EXTERNAL_SUBDIRS_GEM_GemB = [<AbsPath GemC>]
  24. #! add_o3de_object_gem_json_external_subdirectories : Recurses through external subdirectories
  25. #! originally found in the add_*_json_external_subdirectories command
  26. function(add_o3de_object_gem_json_external_subdirectories object_type object_name gem_path visited_gem_name_set_ref)
  27. set(gem_json_path ${gem_path}/gem.json)
  28. if(EXISTS ${gem_json_path})
  29. o3de_read_json_external_subdirs(gem_external_subdirs ${gem_path}/gem.json)
  30. # Read the gem_name from the gem.json and map it to the gem path
  31. o3de_read_json_key(gem_name "${gem_path}/gem.json" "gem_name")
  32. if (gem_name)
  33. set_property(GLOBAL PROPERTY "@GEMROOT:${gem_name}@" "${gem_path}")
  34. endif()
  35. # Push the gem name onto the visited set
  36. list(APPEND ${visited_gem_name_set_ref} ${gem_name})
  37. foreach(gem_external_subdir IN LISTS gem_external_subdirs)
  38. file(REAL_PATH ${gem_external_subdir} real_external_subdir BASE_DIRECTORY ${gem_path})
  39. if(NOT object_name STREQUAL "")
  40. # Append external subdirectory to the O3DE_EXTERNAL_SUBDIRS_${object_type}_${object_name} PROPERTY
  41. set(object_external_subdir_property_name O3DE_EXTERNAL_SUBDIRS_${object_type}_${object_name})
  42. else()
  43. # Append external subdirectory to the O3DE_EXTERNAL_SUBDIRS_${object_type} PROPERTY
  44. set(object_external_subdir_property_name O3DE_EXTERNAL_SUBDIRS_${object_type})
  45. endif()
  46. get_property(current_external_subdirs GLOBAL PROPERTY ${object_external_subdir_property_name})
  47. if(NOT real_external_subdir IN_LIST current_external_subdirs)
  48. set_property(GLOBAL APPEND PROPERTY ${object_external_subdir_property_name} "${real_external_subdir}")
  49. set_property(GLOBAL PROPERTY "O3DE_SUBDIRECTORY_${real_external_subdir}" TRUE)
  50. foreach(visited_gem_name IN LISTS ${visited_gem_name_set_ref})
  51. # Append the external subdirectories that come with the gem to
  52. # the visited_gem_set O3DE_EXTERNAL_SUBDIRS_GEM_<gem-name> properties as well
  53. set_property(GLOBAL APPEND PROPERTY O3DE_EXTERNAL_SUBDIRS_GEM_${visited_gem_name} ${real_external_subdir})
  54. endforeach()
  55. add_o3de_object_gem_json_external_subdirectories("${object_type}" "${object_name}" "${real_external_subdir}" "${visited_gem_name_set_ref}")
  56. endif()
  57. endforeach()
  58. # Pop the gem name from the visited set
  59. list(POP_BACK ${visited_gem_name_set_ref})
  60. endif()
  61. endfunction()
  62. #! add_o3de_object_json_external_subdirectories:
  63. #! Returns the external_subdirectories referenced by the supplied <o3de_object>.json
  64. #! This will recurse through to check for gem.json external_subdirectories
  65. #! via calling the *_gem_json_extenal_subdirectories variant of this function
  66. function(add_o3de_object_json_external_subdirectories object_type object_name object_path object_json_filename)
  67. set(object_json_path ${object_path}/${object_json_filename})
  68. if(EXISTS ${object_json_path})
  69. o3de_read_json_external_subdirs(object_external_subdirs ${object_json_path})
  70. foreach(object_external_subdir IN LISTS object_external_subdirs)
  71. file(REAL_PATH ${object_external_subdir} real_external_subdir BASE_DIRECTORY ${object_path})
  72. # Append external subdirectory ONLY to O3DE_EXTERNAL_SUBDIRS_PROJECT_${project_name} PROPERTY
  73. if(NOT object_name STREQUAL "")
  74. # Append external subdirectory to the O3DE_EXTERNAL_SUBDIRS_${object_type}_${object_name} PROPERTY
  75. set(object_external_subdir_property_name O3DE_EXTERNAL_SUBDIRS_${object_type}_${object_name})
  76. else()
  77. # Append external subdirectory to the O3DE_EXTERNAL_SUBDIRS_${object_type} PROPERTY
  78. set(object_external_subdir_property_name O3DE_EXTERNAL_SUBDIRS_${object_type})
  79. endif()
  80. get_property(current_external_subdirs GLOBAL PROPERTY ${object_external_subdir_property_name})
  81. if(NOT real_external_subdir IN_LIST current_external_subdirs)
  82. set_property(GLOBAL APPEND PROPERTY ${object_external_subdir_property_name} "${real_external_subdir}")
  83. set_property(GLOBAL PROPERTY "O3DE_SUBDIRECTORY_${real_external_subdir}" TRUE)
  84. set(visited_gem_name_set)
  85. add_o3de_object_gem_json_external_subdirectories("${object_type}" "${object_name}" "${real_external_subdir}" visited_gem_name_set)
  86. endif()
  87. endforeach()
  88. endif()
  89. endfunction()
  90. # The following functions is for gathering the list of external subdirectories
  91. # provided by the engine.json
  92. function(add_engine_json_external_subdirectories)
  93. add_o3de_object_json_external_subdirectories("ENGINE" "" "${LY_ROOT_FOLDER}" "engine.json")
  94. endfunction()
  95. # The following functions is for gathering the list of external subdirectories
  96. # provided by the project.json
  97. function(add_project_json_external_subdirectories project_path project_name)
  98. add_o3de_object_json_external_subdirectories("PROJECT" "${project_name}" "${project_path}" "project.json")
  99. endfunction()
  100. #! add_o3de_manifest_json_external_subdirectories : Adds the list of external_subdirectories
  101. #! in the user o3de_manifest.json to the O3DE_EXTERNAL_SUBDIRS_O3DE_MANIFEST property
  102. function(add_o3de_manifest_json_external_subdirectories)
  103. # Retrieves the path to the o3de_manifest.json(includes the name)
  104. o3de_get_manifest_path(manifest_path)
  105. # Separate the o3de_manifest.json from the path to it
  106. cmake_path(GET manifest_path FILENAME manifest_json_name)
  107. cmake_path(GET manifest_path PARENT_PATH manifest_path)
  108. add_o3de_object_json_external_subdirectories("O3DE_MANIFEST" "" "${manifest_path}" "${manifest_json_name}")
  109. endfunction()
  110. #! Gather unique_list of all external subdirectories that is union
  111. #! of the engine.json, project.json, o3de_manifest.json and any gem.json files found visiting
  112. function(get_all_external_subdirectories output_subdirs)
  113. # Gather user supplied external subdirectories via the Cache Variable
  114. get_property(all_external_subdirs GLOBAL PROPERTY O3DE_EXTERNAL_SUBDIRS)
  115. get_property(manifest_external_subdirs GLOBAL PROPERTY O3DE_EXTERNAL_SUBDIRS_O3DE_MANIFEST)
  116. list(APPEND all_external_subdirs ${manifest_external_subdirs})
  117. get_property(engine_external_subdirs GLOBAL PROPERTY O3DE_EXTERNAL_SUBDIRS_ENGINE)
  118. list(APPEND all_external_subdirs ${engine_external_subdirs})
  119. # Gather the list of every configured project external subdirectory
  120. # and and append them to the list of external subdirectories
  121. get_property(project_names GLOBAL PROPERTY O3DE_PROJECTS_NAME)
  122. foreach(project_name IN LISTS project_names)
  123. get_property(project_external_subdirs GLOBAL PROPERTY O3DE_EXTERNAL_SUBDIRS_PROJECT_${project_name})
  124. list(APPEND all_external_subdirs ${project_external_subdirs})
  125. endforeach()
  126. list(REMOVE_DUPLICATES all_external_subdirs)
  127. set(${output_subdirs} ${all_external_subdirs} PARENT_SCOPE)
  128. endfunction()
  129. #! Accepts a list of gem names (which can be read from the project.json, gem.json or engine.json)
  130. #! and a list of ALL registered external subdirectories across all manifest
  131. #! and cross checks them against union of all external subdirectories to determine the gem path.
  132. #! If that gem path exist it is appended to the output parameter output gem directories parameter
  133. #! A fatal error is logged indicating that is not gem could not be found in the list of external subdirectories
  134. function(query_gem_paths_from_external_subdirs output_gem_dirs gem_names registered_external_subdirs)
  135. if (gem_names)
  136. foreach(gem_name IN LISTS gem_names)
  137. unset(gem_path)
  138. o3de_find_gem_with_registered_external_subdirs(${gem_name} gem_path "${registered_external_subdirs}")
  139. if (gem_path)
  140. list(APPEND gem_dirs ${gem_path})
  141. else()
  142. list(JOIN registered_external_subdirs "\n" external_subdirs_formatted)
  143. message(SEND_ERROR "The gem \"${gem_name}\""
  144. " could not be found in any gem.json from the following list of registered external subdirectories:\n"
  145. "${external_subdirs_formatted}")
  146. break()
  147. endif()
  148. endforeach()
  149. endif()
  150. set(${output_gem_dirs} ${gem_dirs} PARENT_SCOPE)
  151. endfunction()
  152. #! Queries the list of gem names against the list of ALL registered external subdirectories
  153. #! in order to determine the paths corresponding to the gem names
  154. function(add_registered_gems_to_external_subdirs output_gem_dirs gem_names)
  155. get_all_external_subdirectories(registered_external_subdirs)
  156. query_gem_paths_from_external_subdirs(gem_dirs "${gem_names}" "${registered_external_subdirs}")
  157. set(${output_gem_dirs} ${gem_dirs} PARENT_SCOPE)
  158. endfunction()
  159. #! Recurses "dependencies" array if the external subdirectory is a gem(contains a gem.json)
  160. #! for each subdirectory in use.
  161. #! This function looks up the each dependent gem path from the registered external subdirectory set
  162. #! That list of resolved gem paths then have this function invoked on it to perform the same behavior
  163. #! When every descendent gem referenced from the "dependencies" field of the current subdirectory is visited
  164. #! it is then appended to a list of output external subdirectories
  165. #! NOTE: This must be invoked after all the add_*_json_external_subdirectories function
  166. function(reorder_dependent_gems_with_cycle_detection _output_external_dirs subdirs_in_use registered_external_subdirs cycle_detection_set)
  167. # output_external_dirs is a variable whose value is the name of a variable to set in the parent scope
  168. # So double resolve the variable to retrieve its value
  169. set(current_external_dirs "${${_output_external_dirs}}")
  170. foreach(external_subdir IN LISTS subdirs_in_use)
  171. # If a cycle is detected, fatal error and output the list of subdirectories that led to the outcome
  172. if (external_subdir IN_LIST cycle_detection_set)
  173. message(FATAL_ERROR "While visiting \"${external_subdir}\", a cycle was detected in the \"dependencies\""
  174. " array of the following gem.json files in the directories: ${cycle_detection_set}")
  175. endif()
  176. # This subdirectory has already been processed so skip to the next one
  177. if(external_subdir IN_LIST current_external_dirs)
  178. continue()
  179. endif()
  180. get_property(ordered_dependent_subdirs GLOBAL PROPERTY "Dependent:${external_subdir}")
  181. if(ordered_dependent_subdirs)
  182. # Re-use the cached list of dependent subdirs if available
  183. list(APPEND current_external_dirs "${ordered_dependent_subdirs}")
  184. else()
  185. cmake_path(SET gem_manifest_path "${external_subdir}/gem.json")
  186. if(EXISTS ${gem_manifest_path})
  187. # Read the "dependencies" array from gem.json
  188. o3de_read_json_array(dependencies_array "${gem_manifest_path}" "dependencies")
  189. # Lookup the paths using the dependent gem names
  190. unset(reference_external_dirs)
  191. query_gem_paths_from_external_subdirs(reference_external_dirs "${dependencies_array}" "${registered_external_subdirs}")
  192. # Append the external subdirectory into the children cycle_detection_set
  193. set(child_cycle_detection_set ${cycle_detection_set} ${external_subdir})
  194. # Recursively visit the list of gem dependencies for the current external subdir
  195. reorder_dependent_gems_with_cycle_detection(current_external_dirs "${reference_external_dirs}"
  196. "${registered_external_subdirs}" "${child_cycle_detection_set}")
  197. # Append the referenced gem directories before the current external subdir so that they are visited first
  198. list(APPEND current_external_dirs "${reference_external_dirs}")
  199. # Cache the list of external subdirectories so that it can be reused in subsequent calls
  200. set_property(GLOBAL PROPERTY "Dependent:${external_subdir}" "${reference_external_dirs}")
  201. endif()
  202. endif()
  203. # Now append the external subdir
  204. list(APPEND current_external_dirs ${external_subdir})
  205. endforeach()
  206. set(${_output_external_dirs} ${current_external_dirs} PARENT_SCOPE)
  207. endfunction()
  208. function(reorder_dependent_gems_before_external_subdirs output_gem_subdirs subdirs_in_use)
  209. # Lookup the registered external subdirectories once and re-use it for each call
  210. get_all_external_subdirectories(registered_external_subdirs)
  211. # Supply an empty visited set and cycle_detection_set argument
  212. reorder_dependent_gems_with_cycle_detection(output_external_dirs "${subdirs_in_use}" "${registered_external_subdirs}" "")
  213. set(${output_gem_subdirs} ${output_external_dirs} PARENT_SCOPE)
  214. endfunction()
  215. #! Gather unique_list of all external subdirectories that the o3de object provides or uses
  216. #! The list is made up of the following
  217. #! - The paths of gems referenced in the <o3de_object>.json "gem_names" key. Those paths are queried
  218. #! from the "external_subdirectories" in o3de_manifest.json
  219. #! - The <o3de_object> path
  220. #! - The list of external_subdirectories found by recursively visting the <o3de_object>.json "external_subdirectories"
  221. function(get_all_external_subdirectories_for_o3de_object output_subdirs object_type object_name object_path object_json_filename)
  222. # Append the gems referenced by name from "gem_names" field in the <object>.json
  223. # These gems are registered in the users o3de_manifest.json
  224. o3de_read_json_array(gem_names ${object_path}/${object_json_filename} "gem_names")
  225. add_registered_gems_to_external_subdirs(object_gem_reference_dirs "${gem_names}")
  226. list(APPEND subdirs_for_object ${object_gem_reference_dirs})
  227. # Also append the array the "external_subdirectories" from each gem referenced through the "gem_names"
  228. # field
  229. foreach(gem_name IN LISTS gem_names)
  230. get_property(gem_real_external_subdirs GLOBAL PROPERTY O3DE_EXTERNAL_SUBDIRS_GEM_${gem_name})
  231. list(APPEND subdirs_for_object ${gem_real_external_subdirs})
  232. endforeach()
  233. # Append the list of external_subdirectories that come with the object
  234. if(NOT object_name STREQUAL "")
  235. # query the O3DE_EXTERNAL_SUBDIRS_${object_type}_${object_name} PROPERTY
  236. set(object_external_subdir_property_name O3DE_EXTERNAL_SUBDIRS_${object_type}_${object_name})
  237. else()
  238. # query the O3DE_EXTERNAL_SUBDIRS_${object_type} PROPERTY
  239. set(object_external_subdir_property_name O3DE_EXTERNAL_SUBDIRS_${object_type})
  240. endif()
  241. get_property(object_external_subdirs GLOBAL PROPERTY ${object_external_subdir_property_name})
  242. list(APPEND subdirs_for_object ${object_external_subdirs})
  243. list(REMOVE_DUPLICATES subdirs_for_object)
  244. set(${output_subdirs} ${subdirs_for_object} PARENT_SCOPE)
  245. endfunction()
  246. #! Gather the unqiue list of all external subdirectories that the engine provides("external_subdirectories")
  247. #! plus all external subdirectories that every active project provides("external_subdirectories")
  248. #! or references("gem_names")
  249. function(get_external_subdirectories_in_use output_subdirs)
  250. # Gather the list of external subdirectories set through the O3DE_EXTERNAL_SUBDIRS Cache Variable
  251. get_property(all_external_subdirs CACHE O3DE_EXTERNAL_SUBDIRS PROPERTY VALUE)
  252. # Append the list of external subdirectories from the engine.json
  253. get_all_external_subdirectories_for_o3de_object(engine_external_subdirs "ENGINE" "" ${LY_ROOT_FOLDER} "engine.json")
  254. list(APPEND all_external_subdirs ${engine_external_subdirs})
  255. # Visit each LY_PROJECTS entry and append the external subdirectories
  256. # the project provides and references
  257. get_property(O3DE_PROJECTS_NAME GLOBAL PROPERTY O3DE_PROJECTS_NAME)
  258. foreach(project_name project_path IN ZIP_LISTS O3DE_PROJECTS_NAME LY_PROJECTS)
  259. file(REAL_PATH ${project_path} full_directory_path BASE_DIRECTORY ${CMAKE_SOURCE_DIR})
  260. # Append the project root path to the list of external subdirectories so that it is visited
  261. list(APPEND all_external_subdirs ${project_path})
  262. get_all_external_subdirectories_for_o3de_object(external_subdirs "PROJECT" "${project_name}" ${full_directory_path} "project.json")
  263. list(APPEND all_external_subdirs ${external_subdirs})
  264. endforeach()
  265. # Make sure any gems in the "dependencies" field of a gem.json
  266. # are ordered before that gem, so they are parsed first.
  267. reorder_dependent_gems_before_external_subdirs(all_external_subdirs "${all_external_subdirs}")
  268. list(REMOVE_DUPLICATES all_external_subdirs)
  269. set(${output_subdirs} ${all_external_subdirs} PARENT_SCOPE)
  270. endfunction()
  271. #! Visit all external subdirectories that is in use by the engine and each project
  272. #! This visits "external_subdirectories" listed in the engine.json,
  273. #! the "external_subdirectories" listed in the each LY_PROJECTS project.json,
  274. #! and the "external_subdirectories" listed o3de_manifest.json in which the engine.json/project.json
  275. #! references in their "gem_names" key.
  276. function(add_subdirectory_on_external_subdirs)
  277. # Query the list of external subdirectories in use by the engine and any projects
  278. get_external_subdirectories_in_use(all_external_subdirs)
  279. # Log the external subdirectory visit order
  280. message(VERBOSE "add_subdirectory will be called on the following external subdirectories in order:")
  281. foreach(external_directory IN LISTS all_external_subdirs)
  282. message(VERBOSE "${external_directory}")
  283. endforeach()
  284. # Loop over the additional external subdirectories and invoke add_subdirectory on them
  285. foreach(external_directory IN LISTS all_external_subdirs)
  286. # Hash the external_directory name and append it to the Binary Directory section of add_subdirectory
  287. # This is to deal with potential situations where multiple external directories has the same last directory name
  288. # For example if D:/Company1/RayTracingGem and F:/Company2/Path/RayTracingGem were both added as a subdirectory
  289. file(REAL_PATH ${external_directory} full_directory_path)
  290. string(SHA256 full_directory_hash ${full_directory_path})
  291. # Truncate the full_directory_hash down to 8 characters to avoid hitting the Windows 260 character path limit
  292. # when the external subdirectory contains relative paths of significant length
  293. string(SUBSTRING ${full_directory_hash} 0 8 full_directory_hash)
  294. # Use the last directory as the suffix path to use for the Binary Directory
  295. cmake_path(GET external_directory FILENAME directory_name)
  296. add_subdirectory(${external_directory} ${CMAKE_BINARY_DIR}/External/${directory_name}-${full_directory_hash})
  297. endforeach()
  298. endfunction()