Subdirectories.cmake 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  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. set(O3DE_DISABLE_GEM_DEPENDENCY_RESOLUTION FALSE CACHE BOOL "Option to forcibly disable the resolution of gem dependencies")
  10. ################################################################################
  11. # Subdirectory processing
  12. ################################################################################
  13. # Add a GLOBAL property which can be used to quickly determine if a directory is an external subdirectory
  14. get_property(cache_external_subdirs CACHE O3DE_EXTERNAL_SUBDIRS PROPERTY VALUE)
  15. foreach(cache_external_subdir IN LISTS cache_external_subdirs)
  16. file(REAL_PATH ${cache_external_subdir} real_external_subdir)
  17. set_property(GLOBAL PROPERTY "O3DE_SUBDIRECTORY_${real_external_subdir}" TRUE)
  18. endforeach()
  19. # The visited_gem_name_set is used to append the external_subdirectories found
  20. # within descendant gems to the parants to the globl O3DE_EXTERNAL_SUBDIRS_GEM_<gem_name> property
  21. # i.e If `GemA` "external_subdirectories" points to `GemB` and `GemB` external_subdirectories
  22. # points to `GemC`.
  23. # Then O3DE_EXTERNAL_SUBDIRS_GEM_GemA = [<AbsPath GemB>, <AbsPath GemC>]
  24. # And O3DE_EXTERNAL_SUBDIRS_GEM_GemB = [<AbsPath GemC>]
  25. #! add_o3de_object_gem_json_external_subdirectories : Recurses through external subdirectories
  26. #! originally found in the add_*_json_external_subdirectories command
  27. function(add_o3de_object_gem_json_external_subdirectories object_type object_name gem_path visited_gem_name_set_ref)
  28. set(gem_json_path ${gem_path}/gem.json)
  29. if(EXISTS ${gem_json_path})
  30. o3de_read_json_external_subdirs(gem_external_subdirs ${gem_path}/gem.json)
  31. # Read the gem_name from the gem.json and map it to the gem path
  32. o3de_read_json_key(gem_name_with_version_specifier "${gem_path}/gem.json" "gem_name")
  33. if(NOT gem_name_with_version_specifier)
  34. MESSAGE(FATAL_ERROR "Failed to read the gem name from '${gem_path/gem.json}' or the gem name is empty.")
  35. return()
  36. endif()
  37. # Remove any version specifier
  38. o3de_get_name_and_version_specifier(${gem_name_with_version_specifier} gem_name spec_op spec_version)
  39. set_property(GLOBAL PROPERTY "@GEMROOT:${gem_name}@" "${gem_path}")
  40. # Push the gem name onto the visited set
  41. list(APPEND ${visited_gem_name_set_ref} ${gem_name})
  42. foreach(gem_external_subdir IN LISTS gem_external_subdirs)
  43. file(REAL_PATH ${gem_external_subdir} real_external_subdir BASE_DIRECTORY ${gem_path})
  44. if(NOT object_name STREQUAL "")
  45. # Append external subdirectory to the O3DE_EXTERNAL_SUBDIRS_${object_type}_${object_name} PROPERTY
  46. set(object_external_subdir_property_name O3DE_EXTERNAL_SUBDIRS_${object_type}_${object_name})
  47. else()
  48. # Append external subdirectory to the O3DE_EXTERNAL_SUBDIRS_${object_type} PROPERTY
  49. set(object_external_subdir_property_name O3DE_EXTERNAL_SUBDIRS_${object_type})
  50. endif()
  51. get_property(current_external_subdirs GLOBAL PROPERTY ${object_external_subdir_property_name})
  52. if(NOT real_external_subdir IN_LIST current_external_subdirs)
  53. set_property(GLOBAL APPEND PROPERTY ${object_external_subdir_property_name} "${real_external_subdir}")
  54. set_property(GLOBAL PROPERTY "O3DE_SUBDIRECTORY_${real_external_subdir}" TRUE)
  55. foreach(visited_gem_name IN LISTS ${visited_gem_name_set_ref})
  56. # Append the external subdirectories that come with the gem to
  57. # the visited_gem_set O3DE_EXTERNAL_SUBDIRS_GEM_<gem-name> properties as well
  58. set_property(GLOBAL APPEND PROPERTY O3DE_EXTERNAL_SUBDIRS_GEM_${visited_gem_name} ${real_external_subdir})
  59. endforeach()
  60. add_o3de_object_gem_json_external_subdirectories("${object_type}" "${object_name}" "${real_external_subdir}" "${visited_gem_name_set_ref}")
  61. endif()
  62. endforeach()
  63. # Pop the gem name from the visited set
  64. list(POP_BACK ${visited_gem_name_set_ref})
  65. endif()
  66. endfunction()
  67. #! add_o3de_object_json_external_subdirectories:
  68. #! Returns the external_subdirectories referenced by the supplied <o3de_object>.json
  69. #! This will recurse through to check for gem.json external_subdirectories
  70. #! via calling the *_gem_json_extenal_subdirectories variant of this function
  71. function(add_o3de_object_json_external_subdirectories object_type object_name object_path object_json_filename)
  72. set(object_json_path ${object_path}/${object_json_filename})
  73. if(EXISTS ${object_json_path})
  74. o3de_read_json_external_subdirs(object_external_subdirs ${object_json_path})
  75. foreach(object_external_subdir IN LISTS object_external_subdirs)
  76. file(REAL_PATH ${object_external_subdir} real_external_subdir BASE_DIRECTORY ${object_path})
  77. # Append external subdirectory ONLY to O3DE_EXTERNAL_SUBDIRS_PROJECT_${project_name} PROPERTY
  78. if(NOT object_name STREQUAL "")
  79. # Append external subdirectory to the O3DE_EXTERNAL_SUBDIRS_${object_type}_${object_name} PROPERTY
  80. set(object_external_subdir_property_name O3DE_EXTERNAL_SUBDIRS_${object_type}_${object_name})
  81. else()
  82. # Append external subdirectory to the O3DE_EXTERNAL_SUBDIRS_${object_type} PROPERTY
  83. set(object_external_subdir_property_name O3DE_EXTERNAL_SUBDIRS_${object_type})
  84. endif()
  85. get_property(current_external_subdirs GLOBAL PROPERTY ${object_external_subdir_property_name})
  86. if(NOT real_external_subdir IN_LIST current_external_subdirs)
  87. set_property(GLOBAL APPEND PROPERTY ${object_external_subdir_property_name} "${real_external_subdir}")
  88. set_property(GLOBAL PROPERTY "O3DE_SUBDIRECTORY_${real_external_subdir}" TRUE)
  89. set(visited_gem_name_set)
  90. add_o3de_object_gem_json_external_subdirectories("${object_type}" "${object_name}" "${real_external_subdir}" visited_gem_name_set)
  91. endif()
  92. endforeach()
  93. endif()
  94. endfunction()
  95. # The following functions is for gathering the list of external subdirectories
  96. # provided by the engine.json
  97. function(add_engine_json_external_subdirectories)
  98. add_o3de_object_json_external_subdirectories("ENGINE" "" "${LY_ROOT_FOLDER}" "engine.json")
  99. endfunction()
  100. # The following functions is for gathering the list of external subdirectories
  101. # provided by the project.json
  102. function(add_project_json_external_subdirectories project_path project_name)
  103. add_o3de_object_json_external_subdirectories("PROJECT" "${project_name}" "${project_path}" "project.json")
  104. endfunction()
  105. #! add_o3de_manifest_json_external_subdirectories : Adds the list of external_subdirectories
  106. #! in the user o3de_manifest.json to the O3DE_EXTERNAL_SUBDIRS_O3DE_MANIFEST property
  107. function(add_o3de_manifest_json_external_subdirectories)
  108. # Retrieves the path to the o3de_manifest.json(includes the name)
  109. o3de_get_manifest_path(manifest_path)
  110. # Separate the o3de_manifest.json from the path to it
  111. cmake_path(GET manifest_path FILENAME manifest_json_name)
  112. cmake_path(GET manifest_path PARENT_PATH manifest_path)
  113. add_o3de_object_json_external_subdirectories("O3DE_MANIFEST" "" "${manifest_path}" "${manifest_json_name}")
  114. endfunction()
  115. #! Gather unique_list of all external subdirectories that is union
  116. #! of the engine.json, project.json, o3de_manifest.json and any gem.json files found visiting
  117. function(get_all_external_subdirectories output_subdirs)
  118. # Gather user supplied external subdirectories via the Cache Variable
  119. get_property(o3de_external_subdirs CACHE O3DE_EXTERNAL_SUBDIRS PROPERTY VALUE)
  120. list(APPEND all_external_subdirs ${o3de_external_subdirs})
  121. get_property(manifest_external_subdirs GLOBAL PROPERTY O3DE_EXTERNAL_SUBDIRS_O3DE_MANIFEST)
  122. list(APPEND all_external_subdirs ${manifest_external_subdirs})
  123. get_property(engine_external_subdirs GLOBAL PROPERTY O3DE_EXTERNAL_SUBDIRS_ENGINE)
  124. list(APPEND all_external_subdirs ${engine_external_subdirs})
  125. # Gather the list of every configured project external subdirectory
  126. # and and append them to the list of external subdirectories
  127. get_property(project_names GLOBAL PROPERTY O3DE_PROJECTS_NAME)
  128. foreach(project_name IN LISTS project_names)
  129. get_property(project_external_subdirs GLOBAL PROPERTY O3DE_EXTERNAL_SUBDIRS_PROJECT_${project_name})
  130. list(APPEND all_external_subdirs ${project_external_subdirs})
  131. endforeach()
  132. list(REMOVE_DUPLICATES all_external_subdirs)
  133. set(${output_subdirs} ${all_external_subdirs} PARENT_SCOPE)
  134. endfunction()
  135. #! Accepts a list of gem names (which can be read from the project.json, gem.json or engine.json)
  136. #! and a list of ALL registered external subdirectories across all manifest
  137. #! and cross checks them against union of all external subdirectories to determine the gem path.
  138. #! If that gem path exist it is appended to the output parameter output gem directories parameter
  139. #! A fatal error is logged indicating that is not gem could not be found in the list of external subdirectories
  140. function(query_gem_paths_from_external_subdirs output_gem_dirs gem_names registered_external_subdirs)
  141. if (gem_names)
  142. foreach(gem_name_with_version_specifier IN LISTS gem_names)
  143. unset(gem_path)
  144. # Remove the version specifier from the gem name before fetching properties
  145. o3de_get_name_and_version_specifier(${gem_name_with_version_specifier} gem_name spec_op spec_version)
  146. get_property(gem_optional GLOBAL PROPERTY ${gem_name}_OPTIONAL)
  147. get_property(gem_path GLOBAL PROPERTY "@GEMROOT:${gem_name}@")
  148. if (gem_path)
  149. list(APPEND gem_dirs ${gem_path})
  150. elseif(NOT gem_optional)
  151. # Sort the list so it is easier to search visually
  152. list(SORT registered_external_subdirs COMPARE NATURAL CASE INSENSITIVE ORDER ASCENDING)
  153. # Indent the text to be easier to read. If the indent is removed CMake will add an
  154. # additional newline automatically because "non-indented text is formatted in
  155. # line-wrapped paragraphs delimited by newlines"
  156. list(JOIN registered_external_subdirs "\n " external_subdirs_formatted)
  157. message(SEND_ERROR "The gem \"${gem_name}\""
  158. " could not be found in any gem.json from the following list of registered external subdirectories:"
  159. "\n ${external_subdirs_formatted}")
  160. break()
  161. endif()
  162. endforeach()
  163. endif()
  164. set(${output_gem_dirs} ${gem_dirs} PARENT_SCOPE)
  165. endfunction()
  166. #! Use cmake.py to get a resolved list of gem names and paths for this object (engine or project)
  167. #! If dependencies are resolved successfully, save each gem's resolved path in a global property
  168. #! named "@GEMROOT:${gem_name}@"
  169. function(resolve_gem_dependencies object_type object_path)
  170. # Avoid resolving dependencies for the same object type and path multiple times
  171. get_property(resolved_dependencies GLOBAL PROPERTY "O3DE_RESOLVED_GEM_DEPENDENCIES_${object_type}_${object_path}")
  172. if(resolved_dependencies)
  173. return()
  174. endif()
  175. set(ENV{PYTHONNOUSERSITE} 1)
  176. string(TOLOWER ${object_type} object_type_lower)
  177. get_property(user_external_subdirs CACHE O3DE_EXTERNAL_SUBDIRS PROPERTY VALUE)
  178. if(user_external_subdirs)
  179. set(user_external_subdir_option -ed "${user_external_subdirs}")
  180. endif()
  181. execute_process(COMMAND
  182. ${LY_PYTHON_CMD} "${LY_ROOT_FOLDER}/scripts/o3de/o3de/cmake.py" "resolve-gem-dependencies" --${object_type_lower}-path "${object_path}" --engine-path "${LY_ROOT_FOLDER}" ${user_external_subdir_option}
  183. WORKING_DIRECTORY ${LY_ROOT_FOLDER}
  184. RESULT_VARIABLE O3DE_CLI_RESULT
  185. OUTPUT_VARIABLE resolved_gem_dependency_output
  186. ERROR_VARIABLE O3DE_CLI_OUT
  187. )
  188. if(O3DE_CLI_RESULT)
  189. message(WARNING "Dependency resolution failed.\n If needed, set the O3DE_DISABLE_GEM_DEPENDENCY_RESOLUTION variable to bypass dependency resolution.\n Error: ${O3DE_CLI_OUT}")
  190. return()
  191. endif()
  192. # Strip any whitespace which might be included in the first or last elements of the list
  193. string(STRIP "${resolved_gem_dependency_output}" resolved_gem_dependency_output)
  194. unset(gem_name)
  195. foreach(entry IN LISTS resolved_gem_dependency_output)
  196. if(NOT DEFINED gem_name)
  197. # The first entry is the gem name
  198. set(gem_name ${entry})
  199. else()
  200. # The next entry after every gem name is the gem path
  201. cmake_path(SET gem_path "${entry}")
  202. get_property(current_gem_path GLOBAL PROPERTY "@GEMROOT:${gem_name}@")
  203. if(current_gem_path)
  204. cmake_path(SET current_gem_path "${current_gem_path}")
  205. cmake_path(COMPARE "${gem_path}" NOT_EQUAL "${current_gem_path}" paths_are_different)
  206. if (paths_are_different)
  207. if (PAL_HOST_PLATFORM_NAME_LOWERCASE STREQUAL "windows")
  208. # Changing the case can cause problems on Windows where the drive
  209. # letter can be upper or lower case in CMake
  210. string(TOLOWER "${current_gem_path}" current_gem_path_lower)
  211. string(TOLOWER "${gem_path}" gem_path_lower)
  212. if(current_gem_path_lower STREQUAL gem_path_lower)
  213. message(VERBOSE "Not replacing existing path '${current_gem_path}' with different case '${gem_path}'")
  214. unset(gem_name)
  215. continue()
  216. endif()
  217. endif()
  218. message(VERBOSE "Multiple paths were found for the same gem '${gem_name}'.\n Current:'${current_gem_path}'\n New:'${gem_path}'")
  219. endif()
  220. else()
  221. message(VERBOSE "New path found for gem '${gem_name}' ${current_gem_path}")
  222. endif()
  223. set_property(GLOBAL PROPERTY "@GEMROOT:${gem_name}@" "${gem_path}")
  224. unset(gem_name)
  225. endif()
  226. endforeach()
  227. set_property(GLOBAL PROPERTY "O3DE_RESOLVED_GEM_DEPENDENCIES_${object_type}_${object_path}" TRUE)
  228. endfunction()
  229. #! Queries the list of gem names against the list of ALL registered external subdirectories
  230. #! in order to determine the paths corresponding to the gem names
  231. function(add_registered_gems_to_external_subdirs output_gem_dirs gem_names)
  232. get_all_external_subdirectories(registered_external_subdirs)
  233. query_gem_paths_from_external_subdirs(gem_dirs "${gem_names}" "${registered_external_subdirs}")
  234. set(${output_gem_dirs} ${gem_dirs} PARENT_SCOPE)
  235. endfunction()
  236. #! Recurses "dependencies" array if the external subdirectory is a gem(contains a gem.json)
  237. #! for each subdirectory in use.
  238. #! This function looks up the each dependent gem path from the registered external subdirectory set
  239. #! That list of resolved gem paths then have this function invoked on it to perform the same behavior
  240. #! When every descendent gem referenced from the "dependencies" field of the current subdirectory is visited
  241. #! it is then appended to a list of output external subdirectories
  242. #! NOTE: This must be invoked after all the add_*_json_external_subdirectories function
  243. function(reorder_dependent_gems_with_cycle_detection _output_external_dirs subdirs_in_use registered_external_subdirs cycle_detection_set)
  244. # output_external_dirs is a variable whose value is the name of a variable to set in the parent scope
  245. # So double resolve the variable to retrieve its value
  246. set(current_external_dirs "${${_output_external_dirs}}")
  247. foreach(external_subdir IN LISTS subdirs_in_use)
  248. # If a cycle is detected, fatal error and output the list of subdirectories that led to the outcome
  249. if (external_subdir IN_LIST cycle_detection_set)
  250. message(FATAL_ERROR "While visiting \"${external_subdir}\", a cycle was detected in the \"dependencies\""
  251. " array of the following gem.json files in the directories: ${cycle_detection_set}")
  252. endif()
  253. # This subdirectory has already been processed so skip to the next one
  254. if(external_subdir IN_LIST current_external_dirs)
  255. continue()
  256. endif()
  257. get_property(ordered_dependent_subdirs GLOBAL PROPERTY "Dependent:${external_subdir}")
  258. if(ordered_dependent_subdirs)
  259. # Re-use the cached list of dependent subdirs if available
  260. list(APPEND current_external_dirs "${ordered_dependent_subdirs}")
  261. else()
  262. cmake_path(SET gem_manifest_path "${external_subdir}/gem.json")
  263. if(EXISTS ${gem_manifest_path})
  264. # Read the "dependencies" array from gem.json
  265. o3de_read_json_array(dependencies_array "${gem_manifest_path}" "dependencies")
  266. # Lookup the paths using the dependent gem names
  267. unset(reference_external_dirs)
  268. query_gem_paths_from_external_subdirs(reference_external_dirs "${dependencies_array}" "${registered_external_subdirs}")
  269. # Append the external subdirectory into the children cycle_detection_set
  270. set(child_cycle_detection_set ${cycle_detection_set} ${external_subdir})
  271. # Recursively visit the list of gem dependencies for the current external subdir
  272. reorder_dependent_gems_with_cycle_detection(current_external_dirs "${reference_external_dirs}"
  273. "${registered_external_subdirs}" "${child_cycle_detection_set}")
  274. # Append the referenced gem directories before the current external subdir so that they are visited first
  275. list(APPEND current_external_dirs "${reference_external_dirs}")
  276. # Cache the list of external subdirectories so that it can be reused in subsequent calls
  277. set_property(GLOBAL PROPERTY "Dependent:${external_subdir}" "${reference_external_dirs}")
  278. endif()
  279. endif()
  280. # Now append the external subdir
  281. list(APPEND current_external_dirs ${external_subdir})
  282. endforeach()
  283. set(${_output_external_dirs} ${current_external_dirs} PARENT_SCOPE)
  284. endfunction()
  285. function(reorder_dependent_gems_before_external_subdirs output_gem_subdirs subdirs_in_use)
  286. # Lookup the registered external subdirectories once and re-use it for each call
  287. get_all_external_subdirectories(registered_external_subdirs)
  288. # Supply an empty visited set and cycle_detection_set argument
  289. reorder_dependent_gems_with_cycle_detection(output_external_dirs "${subdirs_in_use}" "${registered_external_subdirs}" "")
  290. set(${output_gem_subdirs} ${output_external_dirs} PARENT_SCOPE)
  291. endfunction()
  292. #! Gather unique_list of all external subdirectories that the o3de object provides or uses
  293. #! The list is made up of the following
  294. #! - The paths of gems referenced in the <o3de_object>.json "gem_names" key. Those paths are queried
  295. #! from the "external_subdirectories" in o3de_manifest.json
  296. #! - The <o3de_object> path
  297. #! - The list of external_subdirectories found by recursively visting the <o3de_object>.json "external_subdirectories"
  298. function(get_all_external_subdirectories_for_o3de_object output_subdirs object_type object_name object_path object_json_filename)
  299. # Append the gems referenced by name from "gem_names" field in the <object>.json
  300. # These gems are registered in the users o3de_manifest.json
  301. o3de_read_json_array(initial_gem_names ${object_path}/${object_json_filename} "gem_names")
  302. set(gem_names "")
  303. # Gem dependency resolution can be disabled to speed up configuration
  304. # for projects where it is not needed
  305. if(initial_gem_names AND NOT O3DE_DISABLE_GEM_DEPENDENCY_RESOLUTION)
  306. # Resolve gem dependency names to gem paths before adding them to external subdirectories
  307. resolve_gem_dependencies(${object_type} "${object_path}")
  308. endif()
  309. # Cache the "gem_names" field entries as read from the <o3de_object>.json file
  310. # This will be used in the Install code to generate an "engine.json" with the same
  311. # set of active gems into its "gem_names" field
  312. get_property(explicit_active_gems GLOBAL PROPERTY "O3DE_EXPLICIT_ACTIVE_GEMS_${object_type}")
  313. # Append to any existing active gems mapped using the ${object_type} key
  314. list(APPEND explicit_active_gems ${initial_gem_names})
  315. # Make the list of active gems unique
  316. list(REMOVE_DUPLICATES explicit_active_gems)
  317. # Update the ${object_type} -> active gem GLOBAL property
  318. set_property(GLOBAL PROPERTY "O3DE_EXPLICIT_ACTIVE_GEMS_${object_type}" "${explicit_active_gems}")
  319. foreach(gem_name_with_version_specifier IN LISTS initial_gem_names)
  320. # Use the ERROR_VARIABLE to catch the common case when it's a simple string and not a json type.
  321. string(JSON json_type ERROR_VARIABLE json_error TYPE ${gem_name_with_version_specifier})
  322. set(gem_optional FALSE)
  323. if(${json_type} STREQUAL "OBJECT")
  324. string(JSON gem_optional GET ${gem_name_with_version_specifier} "optional")
  325. string(JSON gem_name_with_version_specifier GET ${gem_name_with_version_specifier} "name")
  326. endif()
  327. # Remove any version specifier from the gem name
  328. o3de_get_name_and_version_specifier(${gem_name_with_version_specifier} gem_name spec_op spec_version)
  329. # Set a global "optional" property on the gem name
  330. set_property(GLOBAL PROPERTY "${gem_name}_OPTIONAL" ${gem_optional})
  331. # Build the gem_names list with extracted names
  332. list(APPEND gem_names ${gem_name_with_version_specifier})
  333. endforeach()
  334. # Ensure all gems from "gem_names" are included in the settings registry
  335. # file used to load runtime gems libraries
  336. if(gem_names)
  337. ly_enable_gems(GEMS ${gem_names} PROJECT_NAME ${object_name})
  338. add_registered_gems_to_external_subdirs(object_gem_reference_dirs "${gem_names}")
  339. list(APPEND subdirs_for_object ${object_gem_reference_dirs})
  340. # Also append the array the "external_subdirectories" from each gem referenced through the "gem_names"
  341. # field
  342. foreach(gem_name_with_version_specifier IN LISTS gem_names)
  343. # Remove any version specifier from the gem name e.g. 'atom>=1.2.3' becomes 'atom'
  344. o3de_get_name_and_version_specifier(${gem_name_with_version_specifier} gem_name spec_op spec_version)
  345. get_property(gem_real_external_subdirs GLOBAL PROPERTY O3DE_EXTERNAL_SUBDIRS_GEM_${gem_name})
  346. list(APPEND subdirs_for_object ${gem_real_external_subdirs})
  347. endforeach()
  348. endif()
  349. # Append the list of external_subdirectories that come with the object
  350. if(NOT object_name STREQUAL "")
  351. # query the O3DE_EXTERNAL_SUBDIRS_${object_type}_${object_name} PROPERTY
  352. set(object_external_subdir_property_name O3DE_EXTERNAL_SUBDIRS_${object_type}_${object_name})
  353. else()
  354. # query the O3DE_EXTERNAL_SUBDIRS_${object_type} PROPERTY
  355. set(object_external_subdir_property_name O3DE_EXTERNAL_SUBDIRS_${object_type})
  356. endif()
  357. get_property(object_external_subdirs GLOBAL PROPERTY ${object_external_subdir_property_name})
  358. list(APPEND subdirs_for_object ${object_external_subdirs})
  359. list(REMOVE_DUPLICATES subdirs_for_object)
  360. set(${output_subdirs} ${subdirs_for_object} PARENT_SCOPE)
  361. endfunction()
  362. #! Gather the unqiue list of all external subdirectories that the engine provides("external_subdirectories")
  363. #! plus all external subdirectories that every active project provides("external_subdirectories")
  364. #! or references("gem_names")
  365. function(get_external_subdirectories_in_use output_subdirs)
  366. get_property(all_external_subdirs GLOBAL PROPERTY O3DE_ALL_EXTERNAL_SUBDIRECTORIES)
  367. if(all_external_subdirs)
  368. # This function has already run, use the calculated list of external subdirs
  369. set(${output_subdirs} ${all_external_subdirs} PARENT_SCOPE)
  370. return()
  371. endif()
  372. # Gather the list of external subdirectories set through the O3DE_EXTERNAL_SUBDIRS Cache Variable
  373. get_property(all_external_subdirs CACHE O3DE_EXTERNAL_SUBDIRS PROPERTY VALUE)
  374. # Append the list of external subdirectories from the engine.json
  375. get_all_external_subdirectories_for_o3de_object(engine_external_subdirs "ENGINE" "" ${LY_ROOT_FOLDER} "engine.json")
  376. list(APPEND all_external_subdirs ${engine_external_subdirs})
  377. # Visit each O3DE_PROJECTS_PATHS entry and append the external subdirectories
  378. # the project provides and references
  379. get_property(O3DE_PROJECTS_NAME GLOBAL PROPERTY O3DE_PROJECTS_NAME)
  380. get_property(O3DE_PROJECTS_PATHS GLOBAL PROPERTY O3DE_PROJECTS_PATHS)
  381. foreach(project_name project_path IN ZIP_LISTS O3DE_PROJECTS_NAME O3DE_PROJECTS_PATHS)
  382. # Append the project root path to the list of external subdirectories so that it is visited
  383. list(APPEND all_external_subdirs ${project_path})
  384. get_all_external_subdirectories_for_o3de_object(external_subdirs "PROJECT" "${project_name}" "${project_path}" "project.json")
  385. list(APPEND all_external_subdirs ${external_subdirs})
  386. endforeach()
  387. # Make sure any gems in the "dependencies" field of a gem.json
  388. # are ordered before that gem, so they are parsed first.
  389. reorder_dependent_gems_before_external_subdirs(all_external_subdirs "${all_external_subdirs}")
  390. list(REMOVE_DUPLICATES all_external_subdirs)
  391. # Store in a global property so we don't re-calculate this list again
  392. set_property(GLOBAL PROPERTY O3DE_ALL_EXTERNAL_SUBDIRECTORIES "${all_external_subdirs}")
  393. set(${output_subdirs} ${all_external_subdirs} PARENT_SCOPE)
  394. endfunction()
  395. #! Visit all external subdirectories that are in use by the engine and each project
  396. #! This visits "external_subdirectories" listed in the engine.json,
  397. #! the "external_subdirectories" listed in the each LY_PROJECTS project.json,
  398. #! and the "external_subdirectories" listed o3de_manifest.json in which the engine.json/project.json
  399. #! references in their "gem_names" key.
  400. function(add_subdirectory_on_external_subdirs)
  401. # Query the list of external subdirectories in use by the engine and any projects
  402. get_external_subdirectories_in_use(all_external_subdirs)
  403. # Log the external subdirectory visit order
  404. message(VERBOSE "add_subdirectory will be called on the following external subdirectories in order:")
  405. foreach(external_directory IN LISTS all_external_subdirs)
  406. message(VERBOSE "${external_directory}")
  407. endforeach()
  408. # Loop over the additional external subdirectories and invoke add_subdirectory on them
  409. foreach(external_directory IN LISTS all_external_subdirs)
  410. # Hash the external_directory name and append it to the Binary Directory section of add_subdirectory
  411. # This is to deal with potential situations where multiple external directories has the same last directory name
  412. # For example if D:/Company1/RayTracingGem and F:/Company2/Path/RayTracingGem were both added as a subdirectory
  413. file(REAL_PATH ${external_directory} full_directory_path)
  414. string(SHA256 full_directory_hash ${full_directory_path})
  415. # Truncate the full_directory_hash down to 8 characters to avoid hitting the Windows 260 character path limit
  416. # when the external subdirectory contains relative paths of significant length
  417. string(SUBSTRING ${full_directory_hash} 0 8 full_directory_hash)
  418. # Use the last directory as the suffix path to use for the Binary Directory
  419. cmake_path(GET external_directory FILENAME directory_name)
  420. add_subdirectory(${external_directory} ${CMAKE_BINARY_DIR}/External/${directory_name}-${full_directory_hash})
  421. endforeach()
  422. endfunction()