Gems.cmake 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627
  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. # This file contains utility wrappers for dealing with the Gems system.
  9. set(GEM_VARIANT_DEFAULT_HeadlessServers Servers)
  10. define_property(TARGET PROPERTY LY_PROJECT_NAME
  11. BRIEF_DOCS "Name of the project, this target can use enabled gems from"
  12. FULL_DOCS "If set, the when iterating over the enabled gems in ly_enabled_gems_delayed
  13. only a project with that name can have it's enabled gem list added as a dependency to this target.
  14. If the __NOPROJECT__ placeholder is associated with a list enabled gems, then it applies to this target regardless of this property value")
  15. # o3de_gem_setup: Runs a CMAKE macro that reads the nearest ancestor gem.json
  16. # of where the calling CMakeLists.txt is located and queries information
  17. # about the Gem such as the Gem name and version
  18. # NOTE: A CMake `macro` is used and not a `function` as it a macro
  19. # executes in the same scope of the caller
  20. # https://cmake.org/cmake/help/latest/command/macro.html#macro-vs-function
  21. #
  22. # \param:default_gem_name - Name to use for the ${gem_name} variable
  23. # if a gem.json file cannot be found by searching ancestor directories
  24. # or if the "gem_json" file does not contain a "gem_name" field
  25. #
  26. # \return:gem_path - Sets the ${gem_path} variable to the directory containing
  27. # the gem.json that is the nearest ancestor directory of the calling CMakeLists.txt.
  28. # The "nearest ancestor" also includes the current CMakeLists.txt for clarification
  29. # \return:gem_name - Sets the ${gem_name} variable to the "gem_name" field
  30. # \return:gem_version - Sets the ${gem_version} variable to the "gem_version" field
  31. # \return:gem_restricted_path - Sets the ${gem_restricted_path} variable
  32. # to store the root directory of the Gem for the current platform if it is "restricted"
  33. # (i.e Console) platform.
  34. # \return:gem_parent_relative_path - Sets the ${gem_parent_relative_path} variable
  35. # to the relative path o the Gem root directory within the restricted
  36. # platform root directory
  37. # Ex.
  38. # /home/user/
  39. # o3de/
  40. # Gems/
  41. # RayTracing/
  42. # gem.json
  43. # restricted_platform_name_which_cannot_be_mentioned/
  44. # Gems/
  45. # RayTracing/
  46. # restricted.json
  47. # \return:pal_dir - Sets the ${pal_dir} variable to the "platform
  48. # abstracted layer" (PAL) version of the current directory for a specific platform
  49. # For example if the directory containing the calling CMakeLists.txt
  50. # is "Gems/Location/",
  51. # then the PAL directory for windows is "Gems/Location/Platform/Windows/".
  52. # For a restricted platfrom, the directory could be located at the path
  53. # "<restricted-platform-root>/Gems/Location/Platform/<platform>/"
  54. macro(o3de_gem_setup default_gem_name)
  55. # Query the gem name from the gem.json file if possible
  56. # otherwise fallback to using the default gem name argument
  57. o3de_find_ancestor_gem_root(gem_path gem_name "${CMAKE_CURRENT_SOURCE_DIR}")
  58. if (NOT gem_name)
  59. set(gem_name "${default_gem_name}")
  60. endif()
  61. # Fallback to using the current source CMakeLists.txt directory as the gem root path
  62. if (NOT gem_path)
  63. set(gem_path ${CMAKE_CURRENT_SOURCE_DIR})
  64. endif()
  65. set(gem_json ${gem_path}/gem.json)
  66. # Read the version field from the gem.json
  67. set(gem_version, "0.0.0")
  68. o3de_read_json_key(gem_version ${gem_json} "version")
  69. o3de_restricted_path(${gem_json} gem_restricted_path gem_parent_relative_path)
  70. o3de_pal_dir(pal_dir ${CMAKE_CURRENT_SOURCE_DIR}/Platform/${PAL_PLATFORM_NAME} "${gem_restricted_path}" "${gem_path}" "${gem_parent_relative_path}")
  71. endmacro()
  72. # get_all_gem_dependencies
  73. #
  74. # Determine all of the gem dependencies (recursively) for a given gem.
  75. #
  76. # \arg:gem_name(STRING) - Gem name whose "dependencies" will be queried from its gem.json
  77. # \arg:output_resolved_gem_names(LIST) - The updated list of resolved gem name dependencies
  78. function(get_all_gem_dependencies gem_name output_resolved_gem_names)
  79. get_property(gem_dependencies GLOBAL PROPERTY GEM_DEPENDENCIES_"${gem_name}")
  80. if (gem_dependencies)
  81. # The gem dependency for ${gem_name} has been calculated, return the cached property
  82. set(${output_resolved_gem_names} ${gem_dependencies} PARENT_SCOPE)
  83. return()
  84. else()
  85. # The gem dependency for ${gem_name} has not been calculated.
  86. # First read in the dependencies from the gem.json if possible
  87. # Strip out any possible version specifier in order to lookup the gem path based on the cached
  88. # global property "@GEMROOT:${gem_name}"
  89. unset(gem_name_only)
  90. o3de_get_name_and_version_specifier(${gem_name} gem_name_only ignore_spec_op ignore_spec_version)
  91. get_property(gem_path GLOBAL PROPERTY "@GEMROOT:${gem_name_only}@")
  92. if (NOT gem_path)
  93. unset(gem_optional)
  94. get_property(gem_optional GLOBAL PROPERTY ${gem_name}_OPTIONAL)
  95. if (gem_optional)
  96. return()
  97. endif()
  98. message(FATAL_ERROR "Unable to locate gem path for Gem \"${gem_name_only}\"."
  99. " Is the gem registered in either the ~/.o3de/o3de_manifest.json, ${LY_ROOT_FOLDER}/engine.json,"
  100. " any project.json or any gem.json which itself is registered?")
  101. endif()
  102. o3de_read_json_array(gem_dependencies "${gem_path}/gem.json" "dependencies")
  103. # Keep track of the visited gems to prevent any dependency cycles that can
  104. # cause a possible infinite recursion when evaluating the gem dependencies' dependency
  105. list(APPEND get_all_gem_dependencies_visited ${gem_name})
  106. # Iterate and recursively collect dependent gem names
  107. unset(all_dependent_gem_names)
  108. foreach(dependent_gem_name IN LISTS gem_dependencies)
  109. unset(dependent_gem_names)
  110. # Recursively check a depend gem only if it has not been visited yet
  111. unset(visited_index)
  112. list(FIND get_all_gem_dependencies_visited ${dependent_gem_name} visited_index)
  113. if (visited_index LESS 0)
  114. get_all_gem_dependencies(${dependent_gem_name} dependent_gem_names)
  115. list(APPEND all_dependent_gem_names ${dependent_gem_names})
  116. endif()
  117. endforeach()
  118. list(APPEND gem_dependencies ${all_dependent_gem_names})
  119. list(REMOVE_DUPLICATES gem_dependencies)
  120. # Update the cached value and set the results
  121. set_property(GLOBAL PROPERTY GEM_DEPENDENCIES_"${gem_name}" ${gem_dependencies})
  122. set(${output_resolved_gem_names} ${gem_dependencies} PARENT_SCOPE)
  123. endif()
  124. endfunction()
  125. # o3de_find_ancestor_gem_root:Searches for the nearest gem root from input source_dir
  126. #
  127. # \arg:source_dir(FILEPATH) - Filepath to walk upwards from to locate a gem.json
  128. # \return:output_gem_module_root - The directory containing the nearest gem.json
  129. # \return:output_gem_name - The name of the gem read from the gem.json
  130. function(o3de_find_ancestor_gem_root output_gem_module_root output_gem_name source_dir)
  131. unset(${output_gem_module_root} PARENT_SCOPE)
  132. if(source_dir)
  133. set(candidate_gem_path ${source_dir})
  134. # Locate the root of the gem by finding the gem.json location
  135. cmake_path(APPEND candidate_gem_path "gem.json" OUTPUT_VARIABLE candidate_gem_json_path)
  136. while(NOT EXISTS "${candidate_gem_json_path}")
  137. cmake_path(GET candidate_gem_path PARENT_PATH parent_path)
  138. # If the parent directory is the same as the candidate path then the root path has been found
  139. cmake_path(COMPARE "${candidate_gem_path}" EQUAL "${parent_path}" reached_root_dir)
  140. if (reached_root_dir)
  141. # The source directory is not under a gem path in this case
  142. return()
  143. endif()
  144. set(candidate_gem_path ${parent_path})
  145. cmake_path(APPEND candidate_gem_path "gem.json" OUTPUT_VARIABLE candidate_gem_json_path)
  146. endwhile()
  147. endif()
  148. if (EXISTS ${candidate_gem_json_path})
  149. # Update source_dir if the gem root path exists
  150. set(source_dir ${candidate_gem_path})
  151. o3de_read_json_key(gem_name ${candidate_gem_json_path} "gem_name")
  152. unset(gem_provided_service)
  153. o3de_read_optional_json_key(gem_provided_service ${candidate_gem_json_path} "provided_unique_service")
  154. endif()
  155. # Set the gem module root output directory to the location with the gem.json file within it or
  156. # the supplied gem_target SOURCE_DIR location if no gem.json file was found
  157. set(${output_gem_module_root} ${source_dir} PARENT_SCOPE)
  158. # Set the gem name output value to the name of the gem as in the gem.json file
  159. if(gem_name)
  160. set(${output_gem_name} ${gem_name} PARENT_SCOPE)
  161. endif()
  162. if (gem_provided_service)
  163. # Track any (optional) provided unique service that this gem provides
  164. set_property(GLOBAL PROPERTY LY_GEM_PROVIDED_SERVICE_"${gem_name}" "${gem_provided_unique_service}")
  165. endif()
  166. endfunction()
  167. # o3de_add_variant_dependencies_for_gem_dependencies
  168. #
  169. # For the specified gem, creates cmake TARGET dependencies using
  170. # the gem's "dependencies" field as a prefix for each gem variant supplied to this function
  171. # \arg:GEM_NAME(STRING) - Gem name whose "dependencies" will be queried from its gem.json
  172. # \arg:VARIANTS(LIST) - List of Gem variants which will be suffixed to the input gem name and
  173. # its gem dependencies
  174. function(o3de_add_variant_dependencies_for_gem_dependencies)
  175. set(options)
  176. set(oneValueArgs GEM_NAME)
  177. set(multiValueArgs VARIANTS)
  178. cmake_parse_arguments(o3de_add_variant_dependencies_for_gem_dependencies "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  179. set(gem_name ${o3de_add_variant_dependencies_for_gem_dependencies_GEM_NAME})
  180. set(gem_variants ${o3de_add_variant_dependencies_for_gem_dependencies_VARIANTS})
  181. if(NOT gem_name OR NOT gem_variants)
  182. return() # Nothing to do
  183. endif()
  184. # Get the gem path using the gem name as a key
  185. get_property(gem_path GLOBAL PROPERTY "@GEMROOT:${gem_name}@")
  186. if(NOT gem_path)
  187. message(FATAL_ERROR "Unable to locate gem path for Gem \"${gem_name}\"."
  188. " Is the gem registered in either the ~/.o3de/o3de_manifest.json, ${LY_ROOT_FOLDER}/engine.json,"
  189. " any project.json or any gem.json which itself is registered?")
  190. endif()
  191. # Open gem.json and read "dependencies" array
  192. unset(gem_dependencies)
  193. get_all_gem_dependencies(${gem_name} gem_dependencies)
  194. foreach(variant IN LISTS gem_variants)
  195. set(gem_variant_target "${gem_name}.${variant}")
  196. # Continue to the next variant if the gem didn't specify
  197. # a CMake target for the current variant
  198. if(NOT TARGET ${gem_variant_target})
  199. continue()
  200. endif()
  201. # Append to the list of target dependencies for the current list
  202. # CMake targets that actually exists
  203. unset(target_dependencies_for_variant)
  204. foreach(gem_dependency IN LISTS gem_dependencies)
  205. set(gem_dependency_variant_target "${gem_dependency}.${variant}")
  206. if(TARGET ${gem_dependency_variant_target})
  207. list(APPEND target_dependencies_for_variant ${gem_dependency_variant_target})
  208. endif()
  209. endforeach()
  210. # Validate that there is at least one dependency being added
  211. if(target_dependencies_for_variant)
  212. ly_add_dependencies(${gem_variant_target} ${target_dependencies_for_variant})
  213. message(VERBOSE "Adding target dependencies for Gem \"${gem_name}\" by appending variant \"${variant}\""
  214. " to gem names found in this gem's \"dependencies\" field\n"
  215. "${gem_variant_target} -> ${gem_dependencies_for_variant}")
  216. endif()
  217. endforeach()
  218. # Store of the arguments used to invoke this function in order to replicate the call
  219. # in the SDK install layout
  220. unset(add_variant_dependencies_for_gem_dependencies_args)
  221. list(APPEND add_variant_dependencies_for_gem_dependencies_args
  222. GEM_NAME ${gem_name}
  223. VARIANTS ${gem_variants})
  224. # Replace the list separator with space to have it be stored as a single property element
  225. list(JOIN add_variant_dependencies_for_gem_dependencies_args " " add_variant_dependencies_for_gem_dependencies_args)
  226. set_property(DIRECTORY APPEND PROPERTY O3DE_ADD_VARIANT_DEPENDENCIES_FOR_GEM_DEPENDENCIES_ARGUMENTS
  227. "${add_variant_dependencies_for_gem_dependencies_args}")
  228. endfunction()
  229. # ly_create_alias
  230. # given an alias to create, and a list of one or more targets,
  231. # this creates an alias that depends on all of the given targets.
  232. function(ly_create_alias)
  233. set(options)
  234. set(oneValueArgs NAME NAMESPACE)
  235. set(multiValueArgs TARGETS)
  236. cmake_parse_arguments(ly_create_alias "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  237. if (NOT ly_create_alias_NAME)
  238. message(FATAL_ERROR "Provide the name of the alias to create using the NAME keyword")
  239. endif()
  240. if (NOT ly_create_alias_NAMESPACE)
  241. message(FATAL_ERROR "Provide the namespace of the alias to create using the NAMESPACE keyword")
  242. endif()
  243. if(TARGET ${ly_create_alias_NAMESPACE}::${ly_create_alias_NAME})
  244. message(FATAL_ERROR "Target already exists, cannot create an alias for it: ${ly_create_alias_NAMESPACE}::${ly_create_alias_NAME}\n"
  245. "Make sure the target wasn't copy and pasted here or elsewhere.")
  246. endif()
  247. # Using an ALIAS target inhibits finding the ALIAS target SOURCE_DIR, which is needed to determine the gem root of the alias target
  248. # complex version - one alias to multiple targets or the alias is being made to a TARGET that doesn't exist yet.
  249. # To actually achieve this we have to create an interface library with those dependencies,
  250. # then we have to create an alias to that target.
  251. # By convention we create one without a namespace then alias the namespaced one.
  252. if(TARGET ${ly_create_alias_NAME})
  253. message(FATAL_ERROR "Internal alias target already exists, cannot create an alias for it: ${ly_create_alias_NAME}\n"
  254. "This could be a copy-paste error, where some part of the ly_create_alias call was changed but the other")
  255. endif()
  256. add_library(${ly_create_alias_NAME} INTERFACE IMPORTED GLOBAL)
  257. set_target_properties(${ly_create_alias_NAME} PROPERTIES GEM_MODULE TRUE)
  258. foreach(target_name ${ly_create_alias_TARGETS})
  259. if(TARGET ${target_name})
  260. ly_de_alias_target(${target_name} de_aliased_target_name)
  261. if(NOT de_aliased_target_name)
  262. message(FATAL_ERROR "Target not found in ly_create_alias call: ${target_name} - check your spelling of the target name")
  263. endif()
  264. else()
  265. set(de_aliased_target_name ${target_name})
  266. endif()
  267. list(APPEND final_targets ${de_aliased_target_name})
  268. endforeach()
  269. # add_dependencies must be called with at least one dependent target
  270. if(final_targets)
  271. ly_parse_third_party_dependencies("${final_targets}")
  272. ly_add_dependencies(${ly_create_alias_NAME} ${final_targets})
  273. # copy over all the dependent target interface properties to the alias
  274. o3de_copy_targets_usage_requirements(TARGET ${ly_create_alias_NAME} SOURCE_TARGETS ${final_targets})
  275. # Register the targets this alias aliases
  276. set_property(GLOBAL APPEND PROPERTY O3DE_ALIASED_TARGETS_${ly_create_alias_NAME} ${final_targets})
  277. endif()
  278. # now add the final alias:
  279. add_library(${ly_create_alias_NAMESPACE}::${ly_create_alias_NAME} ALIAS ${ly_create_alias_NAME})
  280. # Store off the arguments used by ly_create_alias into a DIRECTORY property
  281. # This will be used to re-create the calls in the generated CMakeLists.txt in the INSTALL step
  282. # Replace the CMake list separator with a space to replicate the space separated arguments
  283. # A single create_alias_args variable encodes two values. The alias NAME used to check if the target exists
  284. # and the ly_create_alias arguments to replicate this function call
  285. unset(create_alias_args)
  286. list(APPEND create_alias_args "${ly_create_alias_NAME},"
  287. NAME ${ly_create_alias_NAME}
  288. NAMESPACE ${ly_create_alias_NAMESPACE}
  289. TARGETS ${ly_create_alias_TARGETS})
  290. list(JOIN create_alias_args " " create_alias_args)
  291. set_property(DIRECTORY APPEND PROPERTY LY_CREATE_ALIAS_ARGUMENTS "${create_alias_args}")
  292. # Store the directory path in the GLOBAL property so that it can be accessed
  293. # in the layout install logic. Skip if the directory has already been added
  294. get_property(ly_all_target_directories GLOBAL PROPERTY LY_ALL_TARGET_DIRECTORIES)
  295. if(NOT CMAKE_CURRENT_SOURCE_DIR IN_LIST ly_all_target_directories)
  296. set_property(GLOBAL APPEND PROPERTY LY_ALL_TARGET_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR})
  297. endif()
  298. endfunction()
  299. # ly_set_gem_variant_to_load
  300. # Associates a key, value entry of CMake target -> Gem variant
  301. # \arg:TARGETS - list of Targets to associate with the Gem variant
  302. # \arg:VARIANTS - Gem variant
  303. function(ly_set_gem_variant_to_load)
  304. set(options)
  305. set(oneValueArgs)
  306. set(multiValueArgs TARGETS VARIANTS)
  307. cmake_parse_arguments(ly_set_gem_variant_to_load "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  308. if (NOT ly_set_gem_variant_to_load_TARGETS)
  309. message(FATAL_ERROR "You must provide at least 1 target to ${CMAKE_CURRENT_FUNCTION} using the TARGETS keyword")
  310. endif()
  311. # Store a list of targets
  312. foreach(target_name ${ly_set_gem_variant_to_load_TARGETS})
  313. # Append the target to the list of targets with variants if it has not been added
  314. get_property(ly_targets_with_variants GLOBAL PROPERTY LY_TARGETS_WITH_GEM_VARIANTS)
  315. if(NOT target_name IN_LIST ly_targets_with_variants)
  316. set_property(GLOBAL APPEND PROPERTY LY_TARGETS_WITH_GEM_VARIANTS "${target_name}")
  317. endif()
  318. foreach(variant_name ${ly_set_gem_variant_to_load_VARIANTS})
  319. get_property(target_gem_variants GLOBAL PROPERTY LY_GEM_VARIANTS_"${target_name}")
  320. if(NOT variant_name IN_LIST target_gem_variants)
  321. set_property(GLOBAL APPEND PROPERTY LY_GEM_VARIANTS_"${target_name}" "${variant_name}")
  322. endif()
  323. endforeach()
  324. endforeach()
  325. # Store of the arguments used to invoke this function in order to replicate the call in the generated CMakeLists.txt
  326. # in the install layout
  327. unset(set_gem_variant_args)
  328. list(APPEND set_gem_variant_args
  329. TARGETS ${ly_set_gem_variant_to_load_TARGETS}
  330. VARIANTS ${ly_set_gem_variant_to_load_VARIANTS})
  331. # Replace the list separator with space to have it be stored as a single property element
  332. list(JOIN set_gem_variant_args " " set_gem_variant_args)
  333. set_property(DIRECTORY APPEND PROPERTY LY_SET_GEM_VARIANT_TO_LOAD_ARGUMENTS "${set_gem_variant_args}")
  334. endfunction()
  335. # ly_enable_gems
  336. # this function makes sure that the given gems, or gems listed in the variable ENABLED_GEMS
  337. # in the GEM_FILE name, are set as runtime dependencies (and thus loaded) for the given targets
  338. # in the context of the given project.
  339. # note that it can't do this immediately, so it saves the data for later processing.
  340. # Note: If you don't supply a project name, it will apply it across the board to all projects.
  341. # this is useful in the case of "ly_enable_gems" being called for so called 'mandatory gems' inside the engine.
  342. # if you specify a gem name with a namespace, it will be used, otherwise it will assume Gem::
  343. function(ly_enable_gems)
  344. set(options)
  345. set(oneValueArgs PROJECT_NAME GEM_FILE)
  346. set(multiValueArgs GEMS TARGETS VARIANTS)
  347. cmake_parse_arguments(ly_enable_gems "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  348. if (NOT ly_enable_gems_PROJECT_NAME)
  349. message(VERBOSE "Note: ly_enable_gems called with no PROJECT_NAME name, applying to all projects: \n"
  350. " - GEMS ${ly_enable_gems_GEMS} \n"
  351. " - GEM_FILE ${ly_enable_gems_GEM_FILE}")
  352. set(ly_enable_gems_PROJECT_NAME "__NOPROJECT__") # so that the token is not blank
  353. endif()
  354. # Backwards-Compatibility - Delegate any TARGETS and VARIANTS arguments to the ly_set_gem_variant_to_load
  355. # command. That command is used to associate TARGETS with the list of Gem Variants they desire to use
  356. if (ly_enable_gems_TARGETS AND ly_enable_gems_VARIANTS)
  357. message(DEPRECATION "The TARGETS and VARIANTS arguments to \"${CMAKE_CURRENT_FUNCTION}\" is deprecated.\n"
  358. "Please use the \"ly_set_gem_variant_to_load\" function directly to associate a Target with a Gem Variant.\n"
  359. "This function will forward the TARGETS and VARIANTS arguments to \"ly_set_gem_variant_to_load\" for now,"
  360. " but this functionality will be removed.")
  361. ly_set_gem_variant_to_load(TARGETS ${ly_enable_gems_TARGETS} VARIANTS ${ly_enable_gems_VARIANTS})
  362. endif()
  363. if ((NOT ly_enable_gems_GEMS AND NOT ly_enable_gems_GEM_FILE) OR (ly_enable_gems_GEMS AND ly_enable_gems_GEM_FILE))
  364. message(FATAL_ERROR "Provide exactly one of either GEM_FILE (filename) or GEMS (list of gems) keywords.")
  365. endif()
  366. if (ly_enable_gems_GEM_FILE)
  367. set(store_temp ${ENABLED_GEMS})
  368. include(${ly_enable_gems_GEM_FILE} RESULT_VARIABLE was_able_to_load_the_file)
  369. if(NOT was_able_to_load_the_file)
  370. message(FATAL_ERROR "could not load the GEM_FILE ${ly_enable_gems_GEM_FILE}")
  371. endif()
  372. if(NOT DEFINED ENABLED_GEMS)
  373. message(WARNING "GEM_FILE ${ly_enable_gems_GEM_FILE} did not set the value of ENABLED_GEMS.\n"
  374. "Gem Files should contain set(ENABLED_GEMS ... <list of gem names>)")
  375. endif()
  376. set(ly_enable_gems_GEMS ${ENABLED_GEMS})
  377. set(ENABLED_GEMS ${store_temp}) # restore value of ENABLED_GEMS just in case...
  378. endif()
  379. # Remove any version specifiers before looking for variants
  380. # e.g. "Atom==1.2.3" becomes "Atom"
  381. unset(GEM_NAMES)
  382. unset(additional_dependent_gems)
  383. foreach(gem_name_with_version_specifier IN LISTS ly_enable_gems_GEMS)
  384. o3de_get_name_and_version_specifier(${gem_name_with_version_specifier} gem_name spec_op spec_version)
  385. list(APPEND GEM_NAMES "${gem_name}")
  386. # In addition to the gems enabled, collect each of the enabled gem's dependencies as well
  387. unset(gem_dependencies_for_gem)
  388. get_all_gem_dependencies(${gem_name} gem_dependencies_for_gem)
  389. list(APPEND additional_dependent_gems ${gem_dependencies_for_gem})
  390. endforeach()
  391. # Update the gems that were enabled to include all of the gem's dependent gems as well
  392. list(APPEND GEM_NAMES "${additional_dependent_gems}")
  393. list(REMOVE_DUPLICATES GEM_NAMES)
  394. set(ly_enable_gems_GEMS ${GEM_NAMES})
  395. # all the actual work has to be done later.
  396. set_property(GLOBAL APPEND PROPERTY LY_DELAYED_ENABLE_GEMS "${ly_enable_gems_PROJECT_NAME}")
  397. define_property(GLOBAL PROPERTY LY_DELAYED_ENABLE_GEMS_"${ly_enable_gems_PROJECT_NAME}"
  398. BRIEF_DOCS "List of gem names to evaluate variants against" FULL_DOCS "Names of gems that will be paired with the variant name
  399. to determine if it is valid target that should be added as an application dynamic load dependency")
  400. set_property(GLOBAL APPEND PROPERTY LY_DELAYED_ENABLE_GEMS_"${ly_enable_gems_PROJECT_NAME}" ${ly_enable_gems_GEMS})
  401. # Store off the arguments used by ly_enable_gems into a DIRECTORY property
  402. # This will be used to re-create the ly_enable_gems call in the generated CMakeLists.txt at the INSTALL step
  403. # Replace the CMake list separator with a space to replicate the space separated TARGETS arguments
  404. if(NOT ly_enable_gems_PROJECT_NAME STREQUAL "__NOPROJECT__")
  405. set(replicated_project_name PROJECT_NAME ${ly_enable_gems_PROJECT_NAME})
  406. endif()
  407. # The GEM_FILE file is used to populate the GEMS argument via the ENABLED_GEMS variable in the file.
  408. # Furthermore the GEM_FILE itself is not copied over to the install layout, so make its argument entry blank and use the list of GEMS
  409. # stored in ly_enable_gems_GEMS
  410. unset(enable_gems_args)
  411. list(APPEND enable_gems_args
  412. ${replicated_project_name}
  413. GEMS ${ly_enable_gems_GEMS})
  414. list(JOIN enable_gems_args " " enable_gems_args)
  415. set_property(DIRECTORY APPEND PROPERTY LY_ENABLE_GEMS_ARGUMENTS "${enable_gems_args}")
  416. endfunction()
  417. function(ly_add_gem_dependencies_to_project_variants)
  418. set(options)
  419. set(oneValueArgs PROJECT_NAME TARGET VARIANT)
  420. set(multiValueArgs GEM_DEPENDENCIES)
  421. cmake_parse_arguments(ly_add_gem_dependencies "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  422. if (NOT ly_add_gem_dependencies_PROJECT_NAME)
  423. message(FATAL_ERROR "Missing required PROJECT_NAME argument which is used to determine gem load prefix")
  424. endif()
  425. if (NOT ly_add_gem_dependencies_TARGET)
  426. message(FATAL_ERROR "Missing required TARGET argument ")
  427. endif()
  428. if (NOT ly_add_gem_dependencies_VARIANT)
  429. message(FATAL_ERROR "Missing required gem VARIANT argument needed to determine which gem variants to load for the target")
  430. endif()
  431. if(${ly_add_gem_dependencies_PROJECT_NAME} STREQUAL "__NOPROJECT__")
  432. # special case, apply to all
  433. unset(PREFIX_CLAUSE)
  434. else()
  435. set(PREFIX_CLAUSE "PREFIX;${ly_add_gem_dependencies_PROJECT_NAME}")
  436. endif()
  437. # apply the list of gem targets. Adding a gem really just means adding the appropriate dependency.
  438. foreach(gem_name ${ly_add_gem_dependencies_GEM_DEPENDENCIES})
  439. # Construct the gem target name to determine if the target exists
  440. set(gem_target ${gem_name}.${ly_add_gem_dependencies_VARIANT})
  441. # Also construct a fallback target if the target doesn't exist for the given variant, then check if the variant has a fallback
  442. set(fallback_gem_target ${gem_name}.${GEM_VARIANT_DEFAULT_${ly_add_gem_dependencies_VARIANT}})
  443. if (TARGET ${gem_target})
  444. ly_de_alias_target(${gem_target} dealiased_gem_target)
  445. elseif (TARGET ${fallback_gem_target})
  446. ly_de_alias_target(${fallback_gem_target} dealiased_gem_target)
  447. else()
  448. set(dealiased_gem_target "")
  449. message(VERBOSE "Gem \"${gem_name}\" does not expose a variant of ${ly_add_gem_dependencies_VARIANT}")
  450. endif()
  451. if (NOT "${dealiased_gem_target}" STREQUAL "")
  452. ly_add_target_dependencies(
  453. ${PREFIX_CLAUSE}
  454. TARGETS ${ly_add_gem_dependencies_TARGET}
  455. DEPENDENT_TARGETS ${dealiased_gem_target}
  456. GEM_VARIANT ${ly_add_gem_dependencies_VARIANT})
  457. endif()
  458. endforeach()
  459. endfunction()
  460. # call this before runtime dependencies are used to add any relevant targets
  461. # saved by the above function
  462. function(ly_enable_gems_delayed)
  463. # Query the list of targets that are associated with a gem variant
  464. get_property(targets_with_variants GLOBAL PROPERTY LY_TARGETS_WITH_GEM_VARIANTS)
  465. # Query the projects that have made calls to ly_enable_gems
  466. get_property(enable_gem_projects GLOBAL PROPERTY LY_DELAYED_ENABLE_GEMS)
  467. foreach(target ${targets_with_variants})
  468. if (NOT TARGET ${target})
  469. message(FATAL_ERROR "ly_set_gem_variant_to_load specified TARGET '${target}' but no such target was found.")
  470. endif()
  471. get_property(target_gem_variants GLOBAL PROPERTY LY_GEM_VARIANTS_"${target}")
  472. message(VERBOSE "Adding gem dependencies for \"${target}\" associated with the Gem variants of \"${target_gem_variants}\"")
  473. # Lookup if the target is scoped to a project
  474. # In that case the target can only use gem targets that is
  475. # - not project specific: i.e "__NOPROJECT__"
  476. # - or specific to the <LY_PROJECT_NAME> project
  477. get_property(target_project_association TARGET ${target} PROPERTY LY_PROJECT_NAME)
  478. foreach(project ${enable_gem_projects})
  479. if (target_project_association AND
  480. (NOT (project STREQUAL "__NOPROJECT__") AND NOT (project STREQUAL target_project_association)))
  481. # Skip adding the gem dependencies to this target if it is associated with a project
  482. # and the current project doesn't match
  483. continue()
  484. endif()
  485. get_property(gem_dependencies GLOBAL PROPERTY LY_DELAYED_ENABLE_GEMS_"${project}")
  486. if (NOT gem_dependencies)
  487. get_property(gem_dependencies_defined GLOBAL PROPERTY LY_DELAYED_ENABLE_GEMS_"${project}" DEFINED)
  488. if (gem_dependencies_defined)
  489. # special case, if the LY_DELAYED_ENABLE_GEMS_"${project_target_variant}" property is DEFINED
  490. # but empty, add an entry to the LY_DELAYED_LOAD_DEPENDENCIES to have the
  491. # cmake_dependencies.*.setreg file for the (project, target) tuple to be regenerated
  492. # This is needed if the ENABLED_GEMS list for a project goes from >0 to 0. In this case
  493. # the cmake_dependencies would have a stale list of gems to load unless it is regenerated
  494. foreach(variant IN LISTS target_gem_variants)
  495. get_property(delayed_load_target_set GLOBAL PROPERTY LY_DELAYED_LOAD_"${project},${target},${variant}" SET)
  496. if(NOT delayed_load_target_set)
  497. set_property(GLOBAL APPEND PROPERTY LY_DELAYED_LOAD_DEPENDENCIES "${project},${target},${variant}")
  498. set_property(GLOBAL APPEND PROPERTY LY_DELAYED_LOAD_"${project},${target},${variant}" "")
  499. endif()
  500. endforeach()
  501. endif()
  502. # Continue to the next iteration loop regardless as there are no gem dependencies
  503. continue()
  504. endif()
  505. # Gather the Gem variants associated with this target and iterate over them to combine them with the enabled
  506. # gems for the each project
  507. foreach(variant IN LISTS target_gem_variants)
  508. ly_add_gem_dependencies_to_project_variants(
  509. PROJECT_NAME ${project}
  510. TARGET ${target}
  511. VARIANT ${variant}
  512. GEM_DEPENDENCIES ${gem_dependencies})
  513. endforeach()
  514. endforeach()
  515. # Make sure that there are no targets that have dependencies on multiple gems that provide the same unique service
  516. foreach(project ${enable_gem_projects})
  517. # Skip __NOPROJECT__ since the gems won't be loaded anyways
  518. if (project STREQUAL "__NOPROJECT__")
  519. continue()
  520. endif()
  521. # Get the enabled gems for ${project} and track any unique service
  522. get_property(enabled_gems_for_project GLOBAL PROPERTY LY_DELAYED_ENABLE_GEMS_"${project}")
  523. unset(identified_unique_services)
  524. foreach(dep_gem ${enabled_gems_for_project})
  525. get_property(gem_provided_unique_service GLOBAL PROPERTY LY_GEM_PROVIDED_SERVICE_"${dep_gem}")
  526. if (gem_provided_unique_service)
  527. get_property(servicing_gem GLOBAL PROPERTY unique_service_"${project}"_"${gem_provided_unique_service}")
  528. if ((servicing_gem) AND (NOT "${dep_gem}" STREQUAL "${servicing_gem}"))
  529. message(FATAL_ERROR "Target '${project}' detected conflicting gems that provide the same service '${gem_provided_unique_service}': "
  530. "${dep_gem}, ${servicing_gem}. Disable one of these gems or any gem that has a dependency for one of these "
  531. "gems to continue.")
  532. else()
  533. set_property(GLOBAL PROPERTY unique_service_"${project}"_"${gem_provided_unique_service}" ${dep_gem})
  534. endif()
  535. endif()
  536. endforeach()
  537. endforeach()
  538. endforeach()
  539. endfunction()