Projects.cmake 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  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. # Deals with projects (e.g. game gems)
  9. include_guard()
  10. # Passing ${LY_PROJECTS} as the default since in project-centric LY_PROJECTS is defined by the project and
  11. # we want to pick up that one as the value of the variable.
  12. # Ideally this cache variable would be defined before the project sets LY_PROJECTS, but that would mean
  13. # it would have to be defined in each project.
  14. set(LY_PROJECTS "${LY_PROJECTS}" CACHE STRING "List of projects to enable, this can be a relative path to the engine root or an absolute path")
  15. # o3de_find_ancestor_project_root: Searches for the nearest project root from input source_dir
  16. #
  17. # \arg:source_dir(FILEPATH) - Filepath to walk upwards from to locate a project.json
  18. # \return:output_project_root - The directory containing the nearest project.json
  19. # \return:output_project_name - The name of the project read from the project.json
  20. function(o3de_find_ancestor_project_root output_project_root output_project_name source_dir)
  21. unset(${output_project_root} PARENT_SCOPE)
  22. if(source_dir)
  23. set(candidate_project_path ${source_dir})
  24. # Locate the root of the project by finding the project.json location
  25. cmake_path(APPEND candidate_project_path "project.json" OUTPUT_VARIABLE candidate_project_json_path)
  26. while(NOT EXISTS "${candidate_project_json_path}")
  27. cmake_path(GET candidate_project_path PARENT_PATH parent_path)
  28. # If the parent directory is the same as the candidate path then the root path has been found
  29. cmake_path(COMPARE "${candidate_project_path}" EQUAL "${parent_path}" reached_root_dir)
  30. if (reached_root_dir)
  31. # The source directory is not under a project path in this case
  32. return()
  33. endif()
  34. set(candidate_project_path ${parent_path})
  35. cmake_path(APPEND candidate_project_path "project.json" OUTPUT_VARIABLE candidate_project_json_path)
  36. endwhile()
  37. endif()
  38. if (EXISTS ${candidate_project_json_path})
  39. # Update source_dir if the project root path exists
  40. set(source_dir ${candidate_project_path})
  41. o3de_read_json_key(project_name ${candidate_project_json_path} "project_name")
  42. endif()
  43. # Set the project root output directory to the location with the project.json file within it or
  44. # the supplied project_target SOURCE_DIR location if no project.json file was found
  45. set(${output_project_root} ${source_dir} PARENT_SCOPE)
  46. # Set the project name output value to the name of the project as in the project.json file
  47. if(project_name)
  48. set(${output_project_name} ${project_name} PARENT_SCOPE)
  49. endif()
  50. endfunction()
  51. #! ly_add_target_dependencies: adds module load dependencies for this target.
  52. #
  53. # Each target may have dependencies on gems. To properly define these dependencies, users provides
  54. # this build target a list of dependencies that need to load dynamically when this target executes
  55. # So for example, the PythonBindingExample target adds a dependency on the EditorPythonBindings gem
  56. # via a tool dependencies file
  57. #
  58. # \arg:PREFIX(optional) value to prefix to the generated cmake_dependencies .setreg file for each target
  59. # found in the DEPENDENCIES_FILES. The PREFIX and each dependent target will be joined using the <dot> symbol
  60. # \arg:TARGETS names of the targets to associate the dependencies to
  61. # \arg:DEPENDENCIES_FILES file(s) that contains the load-time dependencies the TARGETS will be associated to
  62. # \arg:DEPENDENT_TARGETS additional list of targets should be added as load-time dependencies for the TARGETS list
  63. # \arg:GEM_VARIANT variant associated with TARGETS dependencies are being added to
  64. # Some variant values are "Clients", "Servers", "Tools", "Builders", "Unified"
  65. #
  66. function(ly_add_target_dependencies)
  67. set(options)
  68. set(oneValueArgs PREFIX GEM_VARIANT)
  69. set(multiValueArgs TARGETS DEPENDENCIES_FILES DEPENDENT_TARGETS)
  70. cmake_parse_arguments(ly_add_gem_dependencies "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  71. # Validate input arguments
  72. if(NOT ly_add_gem_dependencies_TARGETS)
  73. message(FATAL_ERROR "You must provide at least one target to associate the dependencies with")
  74. endif()
  75. if(NOT ly_add_gem_dependencies_DEPENDENCIES_FILES AND NOT ly_add_gem_dependencies_DEPENDENT_TARGETS)
  76. message(FATAL_ERROR "DEPENDENCIES_FILES parameter missing. It must be supplied unless the DEPENDENT_TARGETS parameter is set")
  77. endif()
  78. set(gem_variant ${ly_add_gem_dependencies_GEM_VARIANT})
  79. unset(ALL_GEM_DEPENDENCIES)
  80. foreach(dependency_file ${ly_add_gem_dependencies_DEPENDENCIES_FILES})
  81. #unset any GEM_DEPENDENCIES and include the dependencies file, that should populate GEM_DEPENDENCIES
  82. unset(GEM_DEPENDENCIES)
  83. include(${dependency_file})
  84. list(APPEND ALL_GEM_DEPENDENCIES ${GEM_DEPENDENCIES})
  85. endforeach()
  86. # Append the DEPENDENT_TARGETS to the list of ALL_GEM_DEPENDENCIES
  87. list(APPEND ALL_GEM_DEPENDENCIES ${ly_add_gem_dependencies_DEPENDENT_TARGETS})
  88. # for each target, add the dependencies and set a global property that maps
  89. # the prefix, target name and gem variant to that list of dependencies to load
  90. # This list is iterated in [SettingsRegistry.cmake](./SettingsRegistry.cmake)
  91. # to generate json with the list of active gem modules to load
  92. foreach(target ${ly_add_gem_dependencies_TARGETS})
  93. ly_add_dependencies(${target} ${ALL_GEM_DEPENDENCIES})
  94. set(delayed_load_dependency_key "${ly_add_gem_dependencies_PREFIX},${target},${gem_variant}")
  95. # Add the target to the LY_DELAYED_LOAD_DEPENDENCIES if it isn't already on the list
  96. get_property(load_dependencies_set GLOBAL PROPERTY LY_DELAYED_LOAD_DEPENDENCIES)
  97. if(NOT "${delayed_load_dependency_key}" IN_LIST load_dependencies_set)
  98. set_property(GLOBAL APPEND PROPERTY LY_DELAYED_LOAD_DEPENDENCIES "${delayed_load_dependency_key}")
  99. endif()
  100. foreach(gem_target ${ALL_GEM_DEPENDENCIES})
  101. # Add the list of gem dependencies to the
  102. # LY_TARGET_DELAYED_DEPENDENCIES_${ly_add_gem_dependencies_PREFIX},${target},${variant} property
  103. get_property(target_load_dependencies GLOBAL PROPERTY LY_DELAYED_LOAD_"${delayed_load_dependency_key}")
  104. if(NOT "${gem_target}" IN_LIST target_load_dependencies)
  105. set_property(GLOBAL APPEND PROPERTY LY_DELAYED_LOAD_"${delayed_load_dependency_key}" ${gem_target})
  106. endif()
  107. endforeach()
  108. endforeach()
  109. endfunction()
  110. #template for generating the project build_path setreg
  111. set(project_build_path_template [[
  112. {
  113. "Amazon": {
  114. "Project": {
  115. "Settings": {
  116. "Build": {
  117. "project_build_path": "@project_bin_path@"
  118. }
  119. }
  120. }
  121. }
  122. }]]
  123. )
  124. #! ly_generate_project_build_path_setreg: Generates a .setreg file that contains an absolute path to the ${CMAKE_BINARY_DIR}
  125. # This allows us to locate the directory where the project binaries are built to be located within the engine.
  126. # Which are the shared libraries and launcher executables
  127. # When a pre-built engine application runs from a directory other than the project build directory, it needs
  128. # to be able to locate the project build directory to determine the list of gems that the project depends on to load
  129. # as well the location of those gems.
  130. # For example, if the project uses an external gem not associated with the engine, that gem's dlls/so/dylib files
  131. # would be located within the project build directory and the engine SDK binary directory would need that info
  132. # NOTE: This only needed for non-monolithic host platforms.
  133. # This is because there are no dynamic gems to load on monolithic builds
  134. # Furthermore the pre-built SDK engine applications such as the Editor and AssetProcessor
  135. # can only run on the host platform
  136. # \arg:project_real_path Full path to the o3de project directory
  137. function(ly_generate_project_build_path_setreg project_real_path)
  138. # The build path isn't needed on any non-host platforms
  139. if (NOT PAL_TRAIT_BUILD_HOST_TOOLS)
  140. return()
  141. endif()
  142. # Set the project_bin_path to the ${CMAKE_BINARY_DIR} to provide the configure template
  143. # with the project build directory
  144. set(project_bin_path ${CMAKE_BINARY_DIR})
  145. string(CONFIGURE ${project_build_path_template} project_build_path_setreg_content @ONLY)
  146. set(project_user_build_path_setreg_file ${project_real_path}/user/Registry/Platform/${PAL_PLATFORM_NAME}/build_path.setreg)
  147. file(GENERATE OUTPUT ${project_user_build_path_setreg_file} CONTENT "${project_build_path_setreg_content}")
  148. endfunction()
  149. function(install_project_asset_artifacts project_real_path)
  150. # The cmake tar command has a bit of a flaw
  151. # Any paths within the archive files it creates are relative to the current working directory.
  152. # That means with the setup of:
  153. # cwd = "<project-path>/Cache/pc"
  154. # project product assets = "<project-path>/Cache/pc/*"
  155. # cmake dependency registry files = "<project-path>/build/bin/Release/Registry/*"
  156. # Running the tar command would result in the assets being placed in the to layout
  157. # correctly, but the registry files
  158. # engine.pak/
  159. # ../...build/bin/Release/Registry/cmake_dependencies.*.setreg -> Not correct
  160. # project.json -> Correct
  161. # Generate pak for project in release installs
  162. cmake_path(RELATIVE_PATH CMAKE_RUNTIME_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_BINARY_DIR} OUTPUT_VARIABLE install_base_runtime_output_directory)
  163. set(install_engine_pak_template [=[
  164. if("${CMAKE_INSTALL_CONFIG_NAME}" MATCHES "^([Rr][Ee][Ll][Ee][Aa][Ss][Ee])$")
  165. set(install_output_folder "${CMAKE_INSTALL_PREFIX}/@install_base_runtime_output_directory@/@PAL_PLATFORM_NAME@/${CMAKE_INSTALL_CONFIG_NAME}/@LY_BUILD_PERMUTATION@")
  166. set(install_pak_output_folder "${install_output_folder}/Cache/@LY_ASSET_DEPLOY_ASSET_TYPE@")
  167. set(runtime_output_directory_RELEASE @CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE@)
  168. if(NOT DEFINED LY_ASSET_DEPLOY_ASSET_TYPE)
  169. set(LY_ASSET_DEPLOY_ASSET_TYPE @LY_ASSET_DEPLOY_ASSET_TYPE@)
  170. endif()
  171. message(STATUS "Generating ${install_pak_output_folder}/engine.pak from @project_real_path@/Cache/${LY_ASSET_DEPLOY_ASSET_TYPE}")
  172. file(MAKE_DIRECTORY "${install_pak_output_folder}")
  173. cmake_path(SET cache_product_path "@project_real_path@/Cache/${LY_ASSET_DEPLOY_ASSET_TYPE}")
  174. # Copy the generated cmake_dependencies.*.setreg files for loading gems in non-monolithic to the cache
  175. file(GLOB gem_source_paths_setreg "${runtime_output_directory_RELEASE}/Registry/*.setreg")
  176. # The MergeSettingsToRegistry_TargetBuildDependencyRegistry function looks for lowercase "registry" directory
  177. file(MAKE_DIRECTORY "${cache_product_path}/registry")
  178. file(COPY ${gem_source_paths_setreg} DESTINATION "${cache_product_path}/registry")
  179. file(GLOB product_assets "${cache_product_path}/*")
  180. list(APPEND pak_artifacts ${product_assets})
  181. if(pak_artifacts)
  182. execute_process(
  183. COMMAND ${CMAKE_COMMAND} -E tar "cf" "${install_pak_output_folder}/engine.pak" --format=zip -- ${pak_artifacts}
  184. WORKING_DIRECTORY "${cache_product_path}"
  185. RESULT_VARIABLE archive_creation_result
  186. )
  187. if(archive_creation_result EQUAL 0)
  188. message(STATUS "${install_output_folder}/engine.pak generated")
  189. endif()
  190. endif()
  191. # Remove copied .setreg files from the Cache directory
  192. unset(artifacts_to_remove)
  193. foreach(gem_source_path_setreg IN LISTS gem_source_paths_setreg)
  194. cmake_path(GET gem_source_path_setreg FILENAME setreg_filename)
  195. list(APPEND artifacts_to_remove "${cache_product_path}/registry/${setreg_filename}")
  196. endforeach()
  197. if (artifacts_to_remove)
  198. file(REMOVE ${artifacts_to_remove})
  199. endif()
  200. endif()
  201. ]=])
  202. string(CONFIGURE "${install_engine_pak_template}" install_engine_pak_code @ONLY)
  203. ly_install_run_code("${install_engine_pak_code}")
  204. endfunction()
  205. #! Updates a generated <project-path>/user/cmake/engine/CMakePresets.json
  206. # file to include the path to the engine root CMakePresets.json
  207. # \arg: PROJECT_PATH path to project root.
  208. # will used to form the root of the file path to the presets file that includes the engine presets
  209. # \arg: ENGINE_PATH path to the engine root
  210. function(update_cmake_presets_for_project)
  211. set(options)
  212. set(oneValueArgs PROJECT_PATH ENGINE_PATH)
  213. set(multiValueArgs)
  214. cmake_parse_arguments("${CMAKE_CURRENT_FUNCTION}" "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
  215. set(project_path "${${CMAKE_CURRENT_FUNCTION}_PROJECT_PATH}")
  216. set(engine_path "${${CMAKE_CURRENT_FUNCTION}_ENGINE_PATH}")
  217. execute_process(COMMAND
  218. ${LY_PYTHON_CMD} "${LY_ROOT_FOLDER}/scripts/o3de/o3de/cmake.py" "update-cmake-presets-for-project" -pp "${project_path}" -ep "${engine_path}"
  219. WORKING_DIRECTORY ${LY_ROOT_FOLDER}
  220. RESULT_VARIABLE O3DE_CLI_RESULT
  221. ERROR_VARIABLE O3DE_CLI_ERROR
  222. )
  223. if(NOT O3DE_CLI_RESULT EQUAL 0)
  224. message(STATUS "Unable to update the project \"${project_path}\" CMakePresets to include the engine presets:\n${O3DE_CLI_ERROR}")
  225. endif()
  226. endfunction()
  227. # Add the projects here so the above function is found
  228. foreach(project ${LY_PROJECTS})
  229. file(REAL_PATH ${project} full_directory_path BASE_DIRECTORY ${CMAKE_SOURCE_DIR})
  230. string(SHA256 full_directory_hash ${full_directory_path})
  231. # Set the project normalized path into the global O3DE_PROJECTS_PATHS property
  232. set_property(GLOBAL APPEND PROPERTY O3DE_PROJECTS_PATHS ${full_directory_path})
  233. # Truncate the full_directory_hash down to 8 characters to avoid hitting the Windows 260 character path limit
  234. # when the external subdirectory contains relative paths of significant length
  235. string(SUBSTRING ${full_directory_hash} 0 8 full_directory_hash)
  236. cmake_path(GET project FILENAME project_folder_name )
  237. list(APPEND LY_PROJECTS_FOLDER_NAME ${project_folder_name})
  238. # Generate a setreg file with the path to cmake binary directory
  239. # into <project-path>/user/Registry/Platform/<Platform> directory
  240. ly_generate_project_build_path_setreg(${full_directory_path})
  241. # Get project name
  242. o3de_read_json_key(project_name ${full_directory_path}/project.json "project_name")
  243. # Set the project name into the global O3DE_PROJECTS_NAME property
  244. set_property(GLOBAL APPEND PROPERTY O3DE_PROJECTS_NAME ${project_name})
  245. # Append the project external directory to LY_EXTERNAL_SUBDIR_${project_name} property
  246. add_project_json_external_subdirectories(${full_directory_path} "${project_name}")
  247. # Use the install(CODE) command to archive the project cache
  248. # directory assets for use in a proejct relase layout
  249. install_project_asset_artifacts(${full_directory_path})
  250. # Update the <project-path>/user/cmake/engine/CMakePresets.json
  251. # to include the current engine CMakePresets.json file
  252. update_cmake_presets_for_project(PROJECT_PATH "${full_directory_path}" ENGINE_PATH "${LY_ROOT_FOLDER}")
  253. endforeach()
  254. # If just one project is defined we pass it as a parameter to the applications
  255. list(LENGTH LY_PROJECTS projects_length)
  256. if(projects_length EQUAL "1")
  257. list(GET LY_PROJECTS 0 project)
  258. file(REAL_PATH ${project} full_directory_path BASE_DIRECTORY ${CMAKE_SOURCE_DIR})
  259. ly_set(LY_DEFAULT_PROJECT_PATH ${full_directory_path})
  260. endif()
  261. ly_set(LY_PROJECTS_FOLDER_NAME ${LY_PROJECTS_FOLDER_NAME})