diff --git a/cmake/FileUtil.cmake b/cmake/FileUtil.cmake index 77291d8dbb..4f9cdc346a 100644 --- a/cmake/FileUtil.cmake +++ b/cmake/FileUtil.cmake @@ -124,3 +124,42 @@ function(ly_file_read path content) set(${content} ${file_content} PARENT_SCOPE) set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${path}) endfunction() + + +#! ly_get_last_path_segment_concat_sha256 : Concatenates the last path segment of the absolute path +# with the first 8 characters of the absolute path SHA256 hash to make a unique relative path segment +function(ly_get_last_path_segment_concat_sha256 absolute_path output_path) + string(SHA256 target_source_hash ${absolute_path}) + string(SUBSTRING ${target_source_hash} 0 8 target_source_hash) + cmake_path(GET absolute_path FILENAME last_path_segment) + cmake_path(SET last_path_segment_sha256_path "${last_path_segment}-${target_source_hash}") + + set(${output_path} ${last_path_segment_sha256_path} PARENT_SCOPE) +endfunction() + +#! ly_get_engine_relative_source_dir: Attempts to form a path relative to the BASE_DIRECTORY. +# If that fails the last path segment of the absolute_target_source_dir concatenated with a SHA256 hash to form a target directory +# \arg:BASE_DIRECTORY - Directory to base relative path against. Defaults to LY_ROOT_FOLDER +function(ly_get_engine_relative_source_dir absolute_target_source_dir output_source_dir) + + set(options) + set(oneValueArgs BASE_DIRECTORY) + set(multiValueArgs) + cmake_parse_arguments(ly_get_engine_relative_source_dir "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + if(NOT ly_get_engine_relative_source_dir_BASE_DIRECTORY) + set(ly_get_engine_relative_source_dir_BASE_DIRECTORY ${LY_ROOT_FOLDER}) + endif() + + # Get a relative target source directory to the LY root folder if possible + # Otherwise use the final component name + cmake_path(IS_PREFIX LY_ROOT_FOLDER ${absolute_target_source_dir} is_target_source_dir_subdirectory_of_engine) + if(is_target_source_dir_subdirectory_of_engine) + cmake_path(RELATIVE_PATH absolute_target_source_dir BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE relative_target_source_dir) + else() + ly_get_last_path_segment_concat_sha256(${absolute_target_source_dir} target_source_dir_last_path_segment) + unset(relative_target_source_dir) + cmake_path(APPEND relative_target_source_dir "External" ${target_source_dir_last_path_segment}) + endif() + + set(${output_source_dir} ${relative_target_source_dir} PARENT_SCOPE) +endfunction() diff --git a/cmake/Platform/Common/Install_common.cmake b/cmake/Platform/Common/Install_common.cmake index 7ab6006220..5b476667e2 100644 --- a/cmake/Platform/Common/Install_common.cmake +++ b/cmake/Platform/Common/Install_common.cmake @@ -5,6 +5,8 @@ # # +include(cmake/FileUtil.cmake) + set(CMAKE_INSTALL_MESSAGE NEVER) # Simplify messages to reduce output noise ly_set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME Core) @@ -18,26 +20,6 @@ cmake_path(RELATIVE_PATH CMAKE_LIBRARY_OUTPUT_DIRECTORY BASE_DIRECTORY ${CMAKE_B set(install_output_folder "\${CMAKE_INSTALL_PREFIX}/${runtime_output_directory}/${PAL_PLATFORM_NAME}/$") -function(ly_get_engine_relative_source_dir absolute_target_source_dir output_source_dir) - # Get a relative target source directory to the LY root folder if possible - # Otherwise use the final component name - cmake_path(IS_PREFIX LY_ROOT_FOLDER ${absolute_target_source_dir} is_target_prefix_of_engine_root) - if(is_target_prefix_of_engine_root) - cmake_path(RELATIVE_PATH absolute_target_source_dir BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE relative_target_source_dir) - else() - # In this case the target source directory is outside of the engine root of the target source directory and concatenate the first - # is used first 8 characters of the absolute path SHA256 hash to make a unique relative directory - # that can be used to install the generated CMakeLists.txt - # of a SHA256 hash - string(SHA256 target_source_hash ${absolute_target_source_dir}) - string(SUBSTRING ${target_source_hash} 0 8 target_source_hash) - cmake_path(GET absolute_target_source_dir FILENAME target_source_dirname) - cmake_path(SET relative_target_source_dir "${target_source_dirname}-${target_source_hash}") - endif() - - set(${output_source_dir} ${relative_target_source_dir} PARENT_SCOPE) -endfunction() - #! ly_setup_target: Setup the data needed to re-create the cmake target commands for a single target function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME absolute_target_source_dir) # De-alias target name @@ -254,7 +236,6 @@ endfunction() #! ly_setup_subdirectory: setup all targets in the subdirectory function(ly_setup_subdirectory absolute_target_source_dir) - # Get the target source directory relative to the LY roo folder ly_get_engine_relative_source_dir(${absolute_target_source_dir} relative_target_source_dir) # The builtin BUILDSYSTEM_TARGETS property isn't being used here as that returns the de-alised @@ -540,56 +521,74 @@ function(ly_setup_others) ) # Exclude transient artifacts that shouldn't be copied to the install layout list(FILTER external_subdir_files EXCLUDE REGEX "/([Bb]uild|[Cc]ache|[Uu]ser)$") - list(APPEND filtered_asset_paths ${external_subdir_files}) + # Storing a "mapping" of gem candidate directories, to external_subdirectory files using + # a DIRECTORY property for the "value" and the GLOBAL property for the "key" + set_property(DIRECTORY ${gem_candidate_dir} APPEND PROPERTY directory_filtered_asset_paths "${external_subdir_files}") + set_property(GLOBAL APPEND PROPERTY global_gem_candidate_dirs_prop ${gem_candidate_dir}) endforeach() - # At this point the filtered_assets_paths contains the list of all directories and files - # that are non-excluded candidates that can be scanned for target directories and files - # to copy over to the install layout - foreach(filtered_asset_path IN LISTS filtered_asset_paths) - if(IS_DIRECTORY ${filtered_asset_path}) - file(GLOB_RECURSE - recurse_assets_paths - LIST_DIRECTORIES TRUE - "${filtered_asset_path}/*" - ) - set(gem_file_paths ${recurse_assets_paths}) - # Make sure to prepend the current path iteration to the gem_dirs_path to filter - set(gem_dir_paths ${filtered_asset_path} ${recurse_assets_paths}) + # Iterate over each gem candidate directories and read populate a directory property + # containing the files to copy over + get_property(gem_candidate_dirs GLOBAL PROPERTY global_gem_candidate_dirs_prop) + foreach(gem_candidate_dir IN LISTS gem_candidate_dirs) + get_property(filtered_asset_paths DIRECTORY ${gem_candidate_dir} PROPERTY directory_filtered_asset_paths) + ly_get_last_path_segment_concat_sha256(${gem_candidate_dir} last_gem_root_path_segment) + # Check if the gem is a subdirectory of the engine + cmake_path(IS_PREFIX LY_ROOT_FOLDER ${gem_candidate_dir} is_gem_subdirectory_of_engine) + + # At this point the filtered_assets_paths contains the list of all directories and files + # that are non-excluded candidates that can be scanned for target directories and files + # to copy over to the install layout + foreach(filtered_asset_path IN LISTS filtered_asset_paths) + if(IS_DIRECTORY ${filtered_asset_path}) + file(GLOB_RECURSE + recurse_assets_paths + LIST_DIRECTORIES TRUE + "${filtered_asset_path}/*" + ) + set(gem_file_paths ${recurse_assets_paths}) + # Make sure to prepend the current path iteration to the gem_dirs_path to filter + set(gem_dir_paths ${filtered_asset_path} ${recurse_assets_paths}) - # Gather directories to copy over - # Currently only the Assets, Registry and Config directories are copied over - list(FILTER gem_dir_paths INCLUDE REGEX "/(Assets|Registry|Config)$") - list(APPEND gems_assets_dir_path ${gem_dir_paths}) - else() - set(gem_file_paths ${filtered_asset_path}) - endif() + # Gather directories to copy over + # Currently only the Assets, Registry and Config directories are copied over + list(FILTER gem_dir_paths INCLUDE REGEX "/(Assets|Registry|Config|Editor/Scripts)$") + set_property(DIRECTORY ${gem_candidate_dir} APPEND PROPERTY gems_assets_paths ${gem_dir_paths}) + else() + set(gem_file_paths ${filtered_asset_path}) + endif() - # Gather files to copy over - # Currently only the gem.json file is copied over - list(FILTER gem_file_paths INCLUDE REGEX "/(gem.json)$") - list(APPEND gems_assets_file_path "${gem_file_paths}") - endforeach() + # Gather files to copy over + # Currently only the gem.json file is copied over + list(FILTER gem_file_paths INCLUDE REGEX "/(gem.json|preview.png)$") + set_property(DIRECTORY ${gem_candidate_dir} APPEND PROPERTY gems_assets_paths "${gem_file_paths}") + endforeach() - # gem directories to install - foreach(gem_absolute_dir_path ${gems_assets_dir_path}) - cmake_path(RELATIVE_PATH gem_absolute_dir_path BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE gem_relative_dir_path) - if (EXISTS ${gem_absolute_dir_path}) - # The trailing slash is IMPORTANT here as that is needed to prevent - # the "Assets" folder from being copied underneath the /Assets folder - install(DIRECTORY "${gem_absolute_dir_path}/" - DESTINATION ${gem_relative_dir_path} - ) - endif() - endforeach() + # gem directories and files to install + get_property(gems_assets_dir_path DIRECTORY ${gem_candidate_dir} PROPERTY gems_assets_paths) + foreach(gem_absolute_path IN LISTS gems_assets_paths) + if(is_gem_subdirectory_of_engine) + cmake_path(RELATIVE_PATH gem_absolute_path BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE gem_install_dest_dir) + else() + # The gem resides outside of the LY_ROOT_FOLDER, so the destination is made relative to the + # gem candidate directory and placed under the "External" directory" + # directory + cmake_path(RELATIVE_PATH gem_absolute_path BASE_DIRECTORY ${gem_candidate_dir} OUTPUT_VARIABLE gem_relative_path) + unset(gem_install_dest_dir) + cmake_path(APPEND gem_install_dest_dir "External" ${last_gem_root_path_segment} ${gem_relative_path}) + endif() + + cmake_path(GET gem_install_dest_dir PARENT_PATH gem_install_dest_dir) + if (NOT gem_install_dest_dir) + cmake_path(SET gem_install_dest_dir .) + endif() + if(IS_DIRECTORY ${gem_absolute_path}) + install(DIRECTORY "${gem_absolute_path}" DESTINATION ${gem_install_dest_dir}) + elseif (EXISTS ${gem_absolute_path}) + install(FILES ${gem_absolute_path} DESTINATION ${gem_install_dest_dir}) + endif() + endforeach() - # gem files to install - foreach(gem_absolute_file_path ${gems_assets_file_path}) - cmake_path(RELATIVE_PATH gem_absolute_file_path BASE_DIRECTORY ${LY_ROOT_FOLDER} OUTPUT_VARIABLE gem_relative_file_path) - cmake_path(GET gem_relative_file_path PARENT_PATH gem_relative_parent_dir) - install(FILES ${gem_absolute_file_path} - DESTINATION ${gem_relative_parent_dir} - ) endforeach() # Templates @@ -626,3 +625,46 @@ function(ly_setup_target_generator) ) endfunction() + +#! ly_add_install_paths: Adds the list of path to copy to the install layout relative to the same folder +# \arg:PATHS - Paths to copy over to the install layout. The DESTINATION sub argument is optional +# The INPUT sub-argument is required +# \arg:BASE_DIRECTORY(Optional) - Absolute path where a relative path from the each input path will be +# based off of. Defaults to LY_ROOT_FOLDER if not supplied +function(ly_add_install_paths) + set(options) + set(oneValueArgs BASE_DIRECTORY) + set(multiValueArgs PATHS) + cmake_parse_arguments(ly_add_install_paths "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + if(NOT ly_add_install_paths_PATHS) + message(FATAL_ERROR "ly_add_install_paths requires at least one input path to copy to the destination") + endif() + + # The default is the "." directory if not supplied + if(NOT ly_add_install_paths_BASE_DIRECTORY) + cmake_path(SET ly_add_install_paths_BASE_DIRECTORY ${LY_ROOT_FOLDER}) + endif() + + # Separate each path into an INPUT and DESTINATION parameter + set(options) + set(oneValueArgs INPUT DESTINATION) + set(multiValueArgs) + foreach(install_path IN LISTS ly_add_install_paths_PATHS) + string(REPLACE " " ";" install_path ${install_path}) + cmake_parse_arguments(install "${options}" "${oneValueArgs}" "${multiValueArgs}" ${install_path}) + if(NOT install_DESTINATION) + ly_get_engine_relative_source_dir(${install_INPUT} rel_to_root_input_path + BASE_DIRECTORY ${ly_add_install_paths_BASE_DIRECTORY}) + cmake_path(GET rel_to_root_input_path PARENT_PATH install_DESTINATION) + endif() + if(NOT install_DESTINATION) + cmake_path(SET install_DESTINATION .) + endif() + if(IS_DIRECTORY ${install_INPUT}) + install(DIRECTORY ${install_INPUT} DESTINATION ${install_DESTINATION}) + elseif(EXISTS ${install_INPUT}) + install(FILES ${install_INPUT} DESTINATION ${install_DESTINATION}) + endif() + endforeach() + +endfunction()