diff --git a/CMakeLists.txt b/CMakeLists.txt index ae8a7a05f8..2ee08a8d16 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,13 +25,37 @@ include(cmake/LySet.cmake) include(cmake/Version.cmake) include(cmake/OutputDirectory.cmake) +# Set the engine_path and engine_json +set(o3de_engine_path ${CMAKE_CURRENT_LIST_DIR}) +set(o3de_engine_json ${o3de_engine_path}/engine.json) + if(NOT PROJECT_NAME) project(O3DE LANGUAGES C CXX VERSION ${LY_VERSION_STRING} ) + + # o3de manifest + include(cmake/o3de_manifest.cmake) +endif() + +################################################################################ +# Resolve this engines name and restricted path +################################################################################ +o3de_engine_name(${o3de_engine_json} o3de_engine_name) +o3de_restricted_path(${o3de_engine_json} o3de_engine_restricted_path) +message(STATUS "O3DE Engine Name: ${o3de_engine_name}") +message(STATUS "O3DE Engine Path: ${o3de_engine_path}") +if(o3de_engine_restricted_path) + message(STATUS "O3DE Engine Restricted Path: ${o3de_engine_restricted_path}") endif() +# add the engines cmake folder to the CMAKE_MODULE_PATH +list(APPEND CMAKE_MODULE_PATH "${o3de_engine_path}/cmake") + +################################################################################ +# Initialize +################################################################################ include(cmake/GeneralSettings.cmake) include(cmake/FileUtil.cmake) include(cmake/PAL.cmake) @@ -60,20 +84,19 @@ include(cmake/Projects.cmake) if(NOT INSTALLED_ENGINE) # Add the rest of the targets add_subdirectory(Code) - add_subdirectory(Gems) else() ly_find_o3de_packages() endif() +# Add external subdirectories listed in the manifest +list(APPEND LY_EXTERNAL_SUBDIRS ${o3de_engine_external_subdirectories}) + set(enabled_platforms ${PAL_PLATFORM_NAME} ${LY_PAL_TOOLS_ENABLED}) -foreach(restricted_platform ${PAL_RESTRICTED_PLATFORMS}) - if(restricted_platform IN_LIST enabled_platforms) - add_subdirectory(restricted/${restricted_platform}) - endif() -endforeach() +# Add any engine restricted platforms as external subdirs +o3de_add_engine_restricted_platform_external_subdirs() if(NOT INSTALLED_ENGINE) add_subdirectory(scripts) diff --git a/Code/Framework/AzCore/AzCore/Utils/Utils.cpp b/Code/Framework/AzCore/AzCore/Utils/Utils.cpp index 71f01cb349..7025b3977e 100644 --- a/Code/Framework/AzCore/AzCore/Utils/Utils.cpp +++ b/Code/Framework/AzCore/AzCore/Utils/Utils.cpp @@ -169,5 +169,10 @@ namespace AZ::Utils template AZ::Outcome, AZStd::string> ReadFile(AZStd::string_view filePath, size_t maxFileSize); template AZ::Outcome, AZStd::string> ReadFile(AZStd::string_view filePath, size_t maxFileSize); - + AZ::IO::FixedMaxPathString GetO3deManifestDirectory() + { + AZ::IO::FixedMaxPath path = GetHomeDirectory(); + path /= ".o3de"; + return path.Native(); + } } diff --git a/Code/Framework/AzCore/AzCore/Utils/Utils.h b/Code/Framework/AzCore/AzCore/Utils/Utils.h index f6152d0e9b..4e64ce5a39 100644 --- a/Code/Framework/AzCore/AzCore/Utils/Utils.h +++ b/Code/Framework/AzCore/AzCore/Utils/Utils.h @@ -88,6 +88,9 @@ namespace AZ //! Retrieves the project name from the settings registry AZ::SettingsRegistryInterface::FixedValueString GetProjectName(); + //! Retrieves the full directory to the Home directory, i.e. " or overrideHomeDirectory" + AZ::IO::FixedMaxPathString GetHomeDirectory(); + //! Retrieves the full directory to the O3DE manifest directory, i.e. "/.o3de" AZ::IO::FixedMaxPathString GetO3deManifestDirectory(); diff --git a/Code/Framework/AzCore/Platform/Common/Unimplemented/AzCore/Utils/Utils_Unimplemented.cpp b/Code/Framework/AzCore/Platform/Common/Unimplemented/AzCore/Utils/Utils_Unimplemented.cpp index 20a9edbe2c..0ea17e938a 100644 --- a/Code/Framework/AzCore/Platform/Common/Unimplemented/AzCore/Utils/Utils_Unimplemented.cpp +++ b/Code/Framework/AzCore/Platform/Common/Unimplemented/AzCore/Utils/Utils_Unimplemented.cpp @@ -14,7 +14,7 @@ namespace AZ::Utils { - AZ::IO::FixedMaxPathString GetO3deManifestDirectory() + AZ::IO::FixedMaxPathString GetHomeDirectory() { return {}; } diff --git a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp index 2763baac1d..9dbe8f7264 100644 --- a/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp +++ b/Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp @@ -25,8 +25,19 @@ namespace AZ void NativeErrorMessageBox(const char*, const char*) {} - AZ::IO::FixedMaxPathString GetO3deManifestDirectory() + AZ::IO::FixedMaxPathString GetHomeDirectory() { + constexpr AZStd::string_view overrideHomeDirKey = "/Amazon/Settings/override_home_dir"; + AZ::IO::FixedMaxPathString overrideHomeDir; + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + if (settingsRegistry->Get(overrideHomeDir, overrideHomeDirKey)) + { + AZ::IO::FixedMaxPath path{overrideHomeDir}; + return path.Native(); + } + } + if (const char* homePath = std::getenv("HOME"); homePath != nullptr) { AZ::IO::FixedMaxPath path{homePath}; diff --git a/Code/Framework/AzCore/Platform/Windows/AzCore/Utils/Utils_Windows.cpp b/Code/Framework/AzCore/Platform/Windows/AzCore/Utils/Utils_Windows.cpp index 2ade67955b..18cef227db 100644 --- a/Code/Framework/AzCore/Platform/Windows/AzCore/Utils/Utils_Windows.cpp +++ b/Code/Framework/AzCore/Platform/Windows/AzCore/Utils/Utils_Windows.cpp @@ -22,15 +22,25 @@ namespace AZ::Utils ::MessageBox(0, message, title, MB_OK | MB_ICONERROR); } - AZ::IO::FixedMaxPathString GetO3deManifestDirectory() + AZ::IO::FixedMaxPathString GetHomeDirectory() { + constexpr AZStd::string_view overrideHomeDirKey = "/Amazon/Settings/override_home_dir"; + AZ::IO::FixedMaxPathString overrideHomeDir; + if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr) + { + if (settingsRegistry->Get(overrideHomeDir, overrideHomeDirKey)) + { + AZ::IO::FixedMaxPath path{overrideHomeDir}; + return path.Native(); + } + } + char userProfileBuffer[AZ::IO::MaxPathLength]{}; size_t variableSize = 0; auto err = getenv_s(&variableSize, userProfileBuffer, AZ::IO::MaxPathLength, "USERPROFILE"); if (!err) { AZ::IO::FixedMaxPath path{ userProfileBuffer }; - path /= ".o3de"; return path.Native(); } diff --git a/Gems/AtomLyIntegration/AtomFont/gem.json b/Gems/AtomLyIntegration/AtomFont/gem.json index 2a422eaf53..d5ca7834fd 100644 --- a/Gems/AtomLyIntegration/AtomFont/gem.json +++ b/Gems/AtomLyIntegration/AtomFont/gem.json @@ -1,5 +1,5 @@ { - "gem_name": "AtomLyIntegration_AtomFont", + "gem_name": "AtomFont", "Dependencies": [ ], "GemFormatVersion": 4, diff --git a/Gems/CMakeLists.txt b/Gems/CMakeLists.txt deleted file mode 100644 index 8a9a140008..0000000000 --- a/Gems/CMakeLists.txt +++ /dev/null @@ -1,83 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -add_subdirectory(TextureAtlas) -add_subdirectory(LmbrCentral) -add_subdirectory(LyShine) -add_subdirectory(Maestro) -add_subdirectory(SceneProcessing) -add_subdirectory(SurfaceData) -add_subdirectory(EMotionFX) -add_subdirectory(Achievements) -add_subdirectory(CameraFramework) -add_subdirectory(HttpRequestor) -add_subdirectory(Gestures) -add_subdirectory(FastNoise) -add_subdirectory(GradientSignal) -add_subdirectory(AudioSystem) -add_subdirectory(AudioEngineWwise) -add_subdirectory(GraphCanvas) -add_subdirectory(DebugDraw) -add_subdirectory(ImGui) -add_subdirectory(InAppPurchases) -add_subdirectory(LocalUser) -add_subdirectory(LyShineExamples) -add_subdirectory(ExpressionEvaluation) -add_subdirectory(MessagePopup) -add_subdirectory(PhysX) -add_subdirectory(PhysXDebug) -add_subdirectory(ScriptEvents) -add_subdirectory(AssetValidation) -add_subdirectory(VirtualGamepad) -add_subdirectory(SaveData) -add_subdirectory(Camera) -add_subdirectory(GameState) -add_subdirectory(Vegetation) -add_subdirectory(GameStateSamples) -add_subdirectory(SliceFavorites) -add_subdirectory(Metastream) -add_subdirectory(ScriptCanvas) -add_subdirectory(ScriptedEntityTweener) -add_subdirectory(StartingPointMovement) -add_subdirectory(StartingPointInput) -add_subdirectory(ScriptCanvasPhysics) -add_subdirectory(StartingPointCamera) -add_subdirectory(Presence) -add_subdirectory(ScriptCanvasTesting) -add_subdirectory(TestAssetBuilder) -add_subdirectory(ScriptCanvasDeveloper) -add_subdirectory(CustomAssetExample) -add_subdirectory(AutomatedLauncherTesting) -add_subdirectory(NvCloth) -add_subdirectory(AssetMemoryAnalyzer) -add_subdirectory(CertificateManager) -add_subdirectory(TickBusOrderViewer) -add_subdirectory(MultiplayerCompression) -add_subdirectory(Multiplayer) -add_subdirectory(SceneLoggingExample) -add_subdirectory(VideoPlaybackFramework) -add_subdirectory(CrashReporting) -add_subdirectory(Twitch) -add_subdirectory(EditorPythonBindings) -add_subdirectory(QtForPython) -add_subdirectory(Microphone) -add_subdirectory(Atom) -add_subdirectory(AtomLyIntegration) -add_subdirectory(RADTelemetry) -add_subdirectory(GraphModel) -add_subdirectory(LandscapeCanvas) -add_subdirectory(WhiteBox) -add_subdirectory(Blast) -add_subdirectory(PythonAssetBuilder) -add_subdirectory(Prefab) -add_subdirectory(AWSClientAuth) -add_subdirectory(AWSCore) -add_subdirectory(AWSMetrics) diff --git a/Gems/EMotionFX/gem.json b/Gems/EMotionFX/gem.json index 022f3b3077..f116a6a2c0 100644 --- a/Gems/EMotionFX/gem.json +++ b/Gems/EMotionFX/gem.json @@ -1,5 +1,5 @@ { - "gem_name": "EmotionFX", + "gem_name": "EMotionFX", "Dependencies": [ { "Uuid": "ff06785f7145416b9d46fde39098cb0c", diff --git a/Templates/DefaultGem/Template/CMakeLists.txt b/Templates/DefaultGem/Template/CMakeLists.txt index eaf5cdff3e..fb63008782 100644 --- a/Templates/DefaultGem/Template/CMakeLists.txt +++ b/Templates/DefaultGem/Template/CMakeLists.txt @@ -9,13 +9,18 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # {END_LICENSE} +set(o3de_gem_path ${CMAKE_CURRENT_LIST_DIR}) +set(o3de_gem_json ${o3de_gem_path}/gem.json) +o3de_gem_name(${o3de_gem_json} o3de_gem_name) +o3de_restricted_path(${o3de_gem_json} o3de_gem_restricted_path) + # Currently we are in the DefaultProjectSource folder: ${CMAKE_CURRENT_LIST_DIR} # Get the platform specific folder ${pal_dir} for the current folder: ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} # Note: ly_get_list_relative_pal_filename will take care of the details for us, as this may be a restricted platform # in which case it will see if that platform is present here or in the restricted folder. # i.e. It could here: DefaultProjectSource/Platform/ or # //DefaultProjectSource -ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) +ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} ${o3de_gem_restricted_path} ${o3de_gem_path} ${o3de_gem_name}) # Now that we have the platform abstraction layer (PAL) folder for this folder, thats where we will find the # project cmake for this platform. diff --git a/Templates/DefaultGem/Template/Code/CMakeLists.txt b/Templates/DefaultGem/Template/Code/CMakeLists.txt index 8510dc53d5..3511f8ff83 100644 --- a/Templates/DefaultGem/Template/Code/CMakeLists.txt +++ b/Templates/DefaultGem/Template/Code/CMakeLists.txt @@ -15,7 +15,7 @@ # in which case it will see if that platform is present here or in the restricted folder. # i.e. It could here in our gem : Gems/${Name}/Code/Platform/ or # //Gems/${Name}/Code -ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) +ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} ${o3de_gem_restricted_path} ${o3de_gem_path} ${o3de_gem_name}) # Now that we have the platform abstraction layer (PAL) folder for this folder, thats where we will find the # traits for this platform. Traits for a platform are defines for things like whether or not something in this gem diff --git a/Templates/DefaultGem/Template/gem.json b/Templates/DefaultGem/Template/gem.json index 0d233d0c50..5b8fb3fde0 100644 --- a/Templates/DefaultGem/Template/gem.json +++ b/Templates/DefaultGem/Template/gem.json @@ -4,7 +4,11 @@ "license": "What license ${Name} uses goes here: i.e. https://opensource.org/licenses/MIT", "display_name": "${Name}", "summary": "A short description of ${Name}.", - "canonical_tags": ["Gem"], - "user_tags": ["${Name}"], + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "${Name}" + ], "icon_path": "preview.png" } diff --git a/Templates/DefaultGem/template.json b/Templates/DefaultGem/template.json index 9b14a7af3a..22d4eb27e6 100644 --- a/Templates/DefaultGem/template.json +++ b/Templates/DefaultGem/template.json @@ -1,5 +1,7 @@ { "template_name": "DefaultGem", + "restricted": "o3de", + "restricted_platform_relative_path": "Templates", "origin": "The primary repo for DefaultGem goes here: i.e. http://www.mydomain.com", "license": "What license DefaultGem uses goes here: i.e. https://opensource.org/licenses/MIT", "display_name": "DefaultGem", @@ -270,6 +272,10 @@ } ], "createDirectories": [ + { + "dir": "Assets", + "origin": "Assets" + }, { "dir": "Code", "origin": "Code" @@ -339,4 +345,4 @@ "origin": "Platform/iOS" } ] -} \ No newline at end of file +} diff --git a/Templates/DefaultProject/Template/CMakeLists.txt b/Templates/DefaultProject/Template/CMakeLists.txt index c314f0da5c..ad0a4c869d 100644 --- a/Templates/DefaultProject/Template/CMakeLists.txt +++ b/Templates/DefaultProject/Template/CMakeLists.txt @@ -23,38 +23,64 @@ function(add_vs_debugger_arguments) endforeach() endfunction() +set(o3de_project_path ${CMAKE_CURRENT_LIST_DIR}) +set(o3de_project_json ${o3de_project_path}/project.json) + if(NOT PROJECT_NAME) cmake_minimum_required(VERSION 3.19) project(${Name} LANGUAGES C CXX VERSION 1.0.0.0 ) - include(EngineFinder.cmake OPTIONAL) - find_package(o3de REQUIRED) - o3de_initialize() - add_vs_debugger_arguments() -else() - # Add the project_name to global LY_PROJECTS_TARGET_NAME property - file(READ "${CMAKE_CURRENT_LIST_DIR}/project.json" project_json) - string(JSON project_target_name ERROR_VARIABLE json_error GET ${project_json} "project_name") - if(json_error) - message(FATAL_ERROR "Unable to read key 'project_name' from 'project.json'") + # set this project as the only project + set(LY_PROJECTS ${CMAKE_CURRENT_LIST_DIR}) + + # o3de manifest + include(o3de_manifest.cmake) + + ################################################################################ + # Set the engine_path and resolve this engines restricted path if it has one + ################################################################################ + o3de_engine_path(${o3de_project_json} o3de_engine_path) + o3de_project_name(${o3de_project_json} o3de_project_name) + o3de_restricted_path(${o3de_project_json} o3de_project_restricted_path) + message(STATUS "O3DE Project Name: ${o3de_project_name}") + message(STATUS "O3DE Project Path: ${o3de_project_path}") + if(o3de_project_restricted_path) + message(STATUS "O3DE Project Restricted Path: ${o3de_project_restricted_path}") endif() - set_property(GLOBAL APPEND PROPERTY LY_PROJECTS_TARGET_NAME ${project_target_name}) + # add the engines cmake folder to the CMAKE_MODULE_PATH + list(APPEND CMAKE_MODULE_PATH "${o3de_engine_path}/cmake") + + # add subdirectory on the engine path for this project + add_subdirectory(${o3de_engine_path} o3de) - # Currently we are in the ${Name} folder: ${CMAKE_CURRENT_LIST_DIR} - # Get the platform specific folder ${pal_dir} for the current folder: ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} + # add this --project-path arguments to visual studio debugger + add_vs_debugger_arguments() + +else() + ###################################################### + # the engine is calling add sub_directory() on us + ###################################################### + o3de_project_name(${o3de_project_json} o3de_project_name) + o3de_restricted_path(${o3de_project_json} o3de_project_restricted_path) + + # Currently we are in the folder: ${CMAKE_CURRENT_LIST_DIR} + # Get the platform specific folder ${pal_dir} for the folder: ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} # Note: ly_get_list_relative_pal_filename will take care of the details for us, as this may be a restricted platform # in which case it will see if that platform is present here or in the restricted folder. - # i.e. It could here: ${Name}/Platform/ or - # //${Name} - ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) + # i.e. It could here: TestDP/Platform/ or + # //TestDP + ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} ${o3de_project_restricted_path} ${o3de_project_path} ${o3de_project_name}) # Now that we have the platform abstraction layer (PAL) folder for this folder, thats where we will find the # project cmake for this platform. include(${pal_dir}/${PAL_PLATFORM_NAME_LOWERCASE}_project.cmake) + # Add the project_name to global LY_PROJECTS_TARGET_NAME property + set_property(GLOBAL APPEND PROPERTY LY_PROJECTS_TARGET_NAME ${o3de_project_name}) + add_subdirectory(Code) endif() diff --git a/Templates/DefaultProject/Template/Code/CMakeLists.txt b/Templates/DefaultProject/Template/Code/CMakeLists.txt index cb98254b3b..38999c031c 100644 --- a/Templates/DefaultProject/Template/Code/CMakeLists.txt +++ b/Templates/DefaultProject/Template/Code/CMakeLists.txt @@ -15,7 +15,7 @@ # in which case it will see if that platform is present here or in the restricted folder. # i.e. It could here : ${Name}/Code/Platform/ or # //${Name}/Code -ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) +ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} ${o3de_project_restricted_path} ${o3de_project_path} ${o3de_project_name}) # Now that we have the platform abstraction layer (PAL) folder for this folder, thats where we will find the # traits for this platform. Traits for a platform are defines for things like whether or not something in this project diff --git a/Templates/DefaultProject/Template/EngineFinder.cmake b/Templates/DefaultProject/Template/EngineFinder.cmake deleted file mode 100644 index d1b83b6b6c..0000000000 --- a/Templates/DefaultProject/Template/EngineFinder.cmake +++ /dev/null @@ -1,56 +0,0 @@ -# {BEGIN_LICENSE} -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# -# {END_LICENSE} - -# This file is copied during engine registration. Edits to this file will be lost next -# time a registration happens. -include_guard() - -# Read the engine name from the project_json file -file(READ ${CMAKE_CURRENT_LIST_DIR}/project.json project_json) -string(JSON LY_ENGINE_NAME_TO_USE ERROR_VARIABLE json_error GET ${project_json} engine) -if(json_error) - message(FATAL_ERROR "Unable to read key 'engine' from 'project.json', error: ${json_error}") -endif() - -# Read the list of paths from ~.o3de/o3de_manifest.json -file(TO_CMAKE_PATH "$ENV{USERPROFILE}" home_directory) # Windows -if((NOT home_directory) OR (NOT EXISTS ${home_directory})) - file(TO_CMAKE_PATH "$ENV{HOME}" home_directory)# Unix -endif() - -if (NOT home_directory) - message(FATAL_ERROR "Cannot find user home directory, the o3de manifest cannot be found") -endif() -# Set manifest path to path in the user home directory -set(manifest_path ${home_directory}/.o3de/o3de_manifest.json) - -if(EXISTS ${manifest_path}) - file(READ ${manifest_path} manifest_json) - string(JSON engines_count ERROR_VARIABLE json_error LENGTH ${manifest_json} engines) - if(json_error) - message(FATAL_ERROR "Unable to read key 'engines' from '${manifest_path}', error: ${json_error}") - endif() - math(EXPR engines_count "${engines_count}-1") - foreach(engine_path_index RANGE ${engines_count}) - string(JSON engine_path ERROR_VARIABLE json_error GET ${manifest_json} engines ${engine_path_index}) - if(${json_error}) - message(FATAL_ERROR "Unable to read engines[${engine_path_index}] '${manifest_path}', error: ${json_error}") - endif() - list(APPEND CMAKE_MODULE_PATH "${engine_path}/cmake") - endforeach() -endif() - - - - - diff --git a/Templates/DefaultProject/Template/ShaderLib/raytracingscenesrg.srgi b/Templates/DefaultProject/Template/ShaderLib/raytracingscenesrg.srgi index ac1d663bb8..ac27571828 100644 --- a/Templates/DefaultProject/Template/ShaderLib/raytracingscenesrg.srgi +++ b/Templates/DefaultProject/Template/ShaderLib/raytracingscenesrg.srgi @@ -1,3 +1,4 @@ +// {BEGIN_LICENSE} /* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. @@ -9,6 +10,7 @@ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ +// {END_LICENSE} #pragma once diff --git a/Templates/DefaultProject/Template/project.json b/Templates/DefaultProject/Template/project.json index 5c4a152caf..7f6b5d3b78 100644 --- a/Templates/DefaultProject/Template/project.json +++ b/Templates/DefaultProject/Template/project.json @@ -4,8 +4,12 @@ "license": "What license ${Name} uses goes here: i.e. https://opensource.org/licenses/MIT", "display_name": "${Name}", "summary": "A short description of ${Name}.", - "canonical_tags": ["Project"], - "user_tags": ["${Name}"], + "canonical_tags": [ + "Project" + ], + "user_tags": [ + "${Name}" + ], "icon_path": "preview.png", "engine": "o3de" -} \ No newline at end of file +} diff --git a/Templates/DefaultProject/template.json b/Templates/DefaultProject/template.json index 10e3079f2e..56278a6b04 100644 --- a/Templates/DefaultProject/template.json +++ b/Templates/DefaultProject/template.json @@ -1,5 +1,7 @@ { "template_name": "DefaultProject", + "restricted": "o3de", + "restricted_platform_relative_path": "Templates", "origin": "The primary repo for DefaultProject goes here: i.e. http://www.mydomain.com", "license": "What license DefaultProject uses goes here: i.e. https://opensource.org/licenses/MIT", "display_name": "DefaultProject", @@ -268,12 +270,6 @@ "isTemplated": false, "isOptional": false }, - { - "file": "EngineFinder.cmake", - "origin": "EngineFinder.cmake", - "isTemplated": true, - "isOptional": false - }, { "file": "Platform/Android/android_project.cmake", "origin": "Platform/Android/android_project.cmake", @@ -568,6 +564,12 @@ "isTemplated": true, "isOptional": false }, + { + "file": "ShaderLib/raytracingscenesrg.srgi", + "origin": "ShaderLib/raytracingscenesrg.srgi", + "isTemplated": true, + "isOptional": false + }, { "file": "ShaderLib/scenesrg.srgi", "origin": "ShaderLib/scenesrg.srgi", diff --git a/cmake/PAL.cmake b/cmake/PAL.cmake index c7adb660f0..7802ac8c58 100644 --- a/cmake/PAL.cmake +++ b/cmake/PAL.cmake @@ -22,7 +22,7 @@ file(GLOB detection_files "cmake/Platform/*/PALDetection_*.cmake") foreach(detection_file ${detection_files}) include(${detection_file}) endforeach() -file(GLOB detection_files "restricted/*/cmake/PALDetection_*.cmake") +file(GLOB detection_files ${o3de_engine_restricted_path}/*/cmake/PALDetection_*.cmake) foreach(detection_file ${detection_files}) include(${detection_file}) endforeach() @@ -36,43 +36,99 @@ string(TOLOWER ${PAL_HOST_PLATFORM_NAME} PAL_HOST_PLATFORM_NAME_LOWERCASE) ly_set(PAL_HOST_PLATFORM_NAME_LOWERCASE ${PAL_HOST_PLATFORM_NAME_LOWERCASE}) set(PAL_RESTRICTED_PLATFORMS) -file(GLOB pal_restricted_files "restricted/*/cmake/PAL_*.cmake") + +string(LENGTH ${o3de_engine_restricted_path} engine_restricted_length) +file(GLOB pal_restricted_files ${o3de_engine_restricted_path}/*/cmake/PAL_*.cmake) foreach(pal_restricted_file ${pal_restricted_files}) - string(FIND ${pal_restricted_file} "restricted/" start) - math(EXPR start "${start} + 11") string(FIND ${pal_restricted_file} "/cmake/PAL" end) if(${end} GREATER -1) - math(EXPR length "${end} - ${start}") - string(SUBSTRING ${pal_restricted_file} ${start} ${length} platform) + math(EXPR platform_length "${end} - ${engine_restricted_length} - 1") + math(EXPR platform_start "${engine_restricted_length} + 1") + string(SUBSTRING ${pal_restricted_file} ${platform_start} ${platform_length} platform) list(APPEND PAL_RESTRICTED_PLATFORMS "${platform}") endif() endforeach() ly_set(PAL_RESTRICTED_PLATFORMS ${PAL_RESTRICTED_PLATFORMS}) function(ly_get_absolute_pal_filename out_name in_name) - if(${ARGC} GREATER 2) - set(repo_dir ${ARGV2}) - else() - if(DEFINED LY_ROOT_FOLDER) - set(repo_dir ${LY_ROOT_FOLDER}) - else() - set(repo_dir ${CMAKE_CURRENT_SOURCE_DIR}) + set(full_name ${in_name}) + + if(${ARGC} GREATER 4) + # The user has supplied an object restricted path, the object path and the object name for consideration + set(object_restricted_path ${ARGV2}) + set(object_path ${ARGV3}) + set(object_name ${ARGV4}) + + # if the file is not in the object path then we cannot determine a PAL file for it + file(RELATIVE_PATH relative_path ${object_path} ${full_name}) + if (NOT (IS_ABSOLUTE relative_path OR relative_path MATCHES [[^(\.\./)+(.*)]])) + if (NOT EXISTS ${full_name}) + string(REGEX MATCH "${object_path}/(.*)/Platform/([^/]*)/?(.*)$" match ${full_name}) + if(NOT CMAKE_MATCH_1) + string(REGEX MATCH "${object_path}/Platform/([^/]*)/?(.*)$" match ${full_name}) + set(full_name ${object_restricted_path}/${CMAKE_MATCH_1}/${object_name}) + if(CMAKE_MATCH_2) + string(APPEND full_name "/" ${CMAKE_MATCH_2}) + endif() + elseif("${CMAKE_MATCH_2}" IN_LIST PAL_RESTRICTED_PLATFORMS) + set(full_name ${object_restricted_path}/${CMAKE_MATCH_2}/${object_name}/${CMAKE_MATCH_1}) + if(CMAKE_MATCH_3) + string(APPEND full_name "/" ${CMAKE_MATCH_3}) + endif() + endif() + endif() endif() - endif() + set(${out_name} ${full_name} PARENT_SCOPE) - set(full_name ${in_name}) - if (NOT EXISTS ${full_name}) - string(REGEX MATCH "${repo_dir}/(.*)/Platform/([^/]*)/?(.*)$" match ${full_name}) - if(PAL_RESTRICTED_PLATFORMS) - if("${CMAKE_MATCH_2}" IN_LIST PAL_RESTRICTED_PLATFORMS) - set(full_name ${repo_dir}/restricted/${CMAKE_MATCH_2}/${CMAKE_MATCH_1}) - if(NOT "${CMAKE_MATCH_3}" STREQUAL "") - string(APPEND full_name "/" ${CMAKE_MATCH_3}) + elseif(${ARGC} GREATER 3) + # The user has supplied an object restricted path, the object path for consideration + set(object_restricted_path ${ARGV2}) + set(object_path ${ARGV3}) + + # if the file is not in the object path then we cannot determine a PAL file for it + file(RELATIVE_PATH relative_path ${object_path} ${full_name}) + if (NOT (IS_ABSOLUTE relative_path OR relative_path MATCHES [[^(\.\./)+(.*)]])) + if (NOT EXISTS ${full_name}) + string(REGEX MATCH "${object_path}/(.*)/Platform/([^/]*)/?(.*)$" match ${full_name}) + if(NOT CMAKE_MATCH_1) + string(REGEX MATCH "${object_path}/Platform/([^/]*)/?(.*)$" match ${full_name}) + set(full_name ${object_restricted_path}/${CMAKE_MATCH_1}) + if(CMAKE_MATCH_2) + string(APPEND full_name "/" ${CMAKE_MATCH_2}) + endif() + elseif("${CMAKE_MATCH_2}" IN_LIST PAL_RESTRICTED_PLATFORMS) + set(full_name ${object_restricted_path}/${CMAKE_MATCH_2}/${CMAKE_MATCH_1}) + if(CMAKE_MATCH_3) + string(APPEND full_name "/" ${CMAKE_MATCH_3}) + endif() endif() endif() endif() + set(${out_name} ${full_name} PARENT_SCOPE) + + else() + # The user has not supplied any path so we must assume it is the o3de engine restricted and o3de engine path + # if the file is not in the o3de engine path then we cannot determine a PAL file for it + file(RELATIVE_PATH relative_path ${o3de_engine_path} ${full_name}) + if (NOT (IS_ABSOLUTE relative_path OR relative_path MATCHES [[^(\.\./)+(.*)]])) + if (NOT EXISTS ${full_name}) + string(REGEX MATCH "${o3de_engine_path}/(.*)/Platform/([^/]*)/?(.*)$" match ${full_name}) + if(NOT CMAKE_MATCH_1) + string(REGEX MATCH "${o3de_engine_path}/Platform/([^/]*)/?(.*)$" match ${full_name}) + set(full_name ${o3de_engine_restricted_path}/${CMAKE_MATCH_1}) + if(CMAKE_MATCH_2) + string(APPEND full_name "/" ${CMAKE_MATCH_2}) + endif() + elseif("${CMAKE_MATCH_2}" IN_LIST PAL_RESTRICTED_PLATFORMS) + set(full_name ${o3de_engine_restricted_path}/${CMAKE_MATCH_2}/${CMAKE_MATCH_1}) + if(CMAKE_MATCH_3) + string(APPEND full_name "/" ${CMAKE_MATCH_3}) + endif() + endif() + endif() + endif() + set(${out_name} ${full_name} PARENT_SCOPE) endif() - set(${out_name} ${full_name} PARENT_SCOPE) endfunction() function(ly_get_list_relative_pal_filename out_name in_name) @@ -93,3 +149,25 @@ set(LY_DISABLE_TEST_MODULES FALSE CACHE BOOL "Option to forcibly disable the inc if(LY_DISABLE_TEST_MODULES) ly_set(PAL_TRAIT_BUILD_TESTS_SUPPORTED FALSE) endif() + +################################################################################ +# Add each restricted platform in the engines restricted folder +# If the enabled restricted platform does not have a folder add one. +# If the restricted platform folder does not have a CMakeLists.txt, create one +# so the add_subdirectory on the external folder does not fail. +################################################################################ +function(o3de_add_engine_restricted_platform_external_subdirs) + foreach(restricted_platform ${PAL_RESTRICTED_PLATFORMS}) + if(restricted_platform IN_LIST enabled_platforms) + set(o3de_engine_restricted_platform_folder ${o3de_engine_restricted_path}/${restricted_platform}) + if(NOT EXISTS ${o3de_engine_restricted_platform_folder}) + file(MAKE_DIRECTORY ${o3de_engine_restricted_platform_folder}) + endif() + set(o3de_engine_restricted_platform_folder_cmakelists ${o3de_engine_restricted_platform_folder}/CMakeLists.txt) + if(NOT EXISTS ${o3de_engine_restricted_platform_folder_cmakelists}) + file(TOUCH ${o3de_engine_restricted_platform_folder_cmakelists}) + endif() + list(APPEND LY_EXTERNAL_SUBDIRS ${o3de_engine_restricted_platform_folder}) + endif() + endforeach() +endfunction() diff --git a/cmake/Tools/add_remove_gem.py b/cmake/Tools/add_remove_gem.py deleted file mode 100755 index fab488bdbc..0000000000 --- a/cmake/Tools/add_remove_gem.py +++ /dev/null @@ -1,634 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -import argparse -import logging -import os -import sys -import pathlib - -from cmake.Tools import utils -from cmake.Tools import common - -logger = logging.getLogger() -logging.basicConfig() - - -def add_gem_dependency(cmake_file: str, - gem_target: str) -> int: - """ - adds a gem dependency to a cmake file - :param cmake_file: path to the cmake file - :param gem_target: name of the cmake target - :return: 0 for success or non 0 failure code - """ - if not os.path.isfile(cmake_file): - logger.error(f'Failed to locate cmake file {cmake_file}') - return 1 - - # on a line by basis, see if there already is Gem::{gem_name} - # find the first occurrence of a gem, copy its formatting and replace - # the gem name with the new one and append it - # if the gem is already present fail - t_data = [] - added = False - with open(cmake_file, 'r') as s: - for line in s: - if f'Gem::{gem_target}' in line: - logger.warning(f'{gem_target} is already a gem dependency.') - return 0 - if not added and r'Gem::' in line: - new_gem = ' ' * line.find(r'Gem::') + f'Gem::{gem_target}\n' - t_data.append(new_gem) - added = True - t_data.append(line) - - # if we didn't add it the set gem dependencies could be empty so - # add a new gem, if empty the correct format is 1 tab=4spaces - if not added: - index = 0 - for line in t_data: - index = index + 1 - if r'set(GEM_DEPENDENCIES' in line: - t_data.insert(index, f' Gem::{gem_target}\n') - added = True - break - - # if we didn't add it then it's not here, add a whole new one - if not added: - t_data.append('\n') - t_data.append('set(GEM_DEPENDENCIES\n') - t_data.append(f' Gem::{gem_target}\n') - t_data.append(')\n') - - # write the cmake - os.unlink(cmake_file) - with open(cmake_file, 'w') as s: - s.writelines(t_data) - - return 0 - - -def get_project_gem_list(project_path: str, - platform: str = 'Common') -> set: - runtime_gems = get_gem_list(get_dependencies_cmake(project_path, 'runtime', platform)) - tool_gems = get_gem_list(get_dependencies_cmake(project_path, 'tool', platform)) - server_gems = get_gem_list(get_dependencies_cmake(project_path, 'server', platform)) - return runtime_gems.union(tool_gems.union(server_gems)) - - -def get_gem_list(cmake_file: str) -> set: - """ - Gets a list of declared gem dependencies of a cmake file - :param cmake_file: path to the cmake file - :return: set of gems found - """ - if not os.path.isfile(cmake_file): - logger.error(f'Failed to locate cmake file {cmake_file}') - return set() - - gem_list = set() - with open(cmake_file, 'r') as s: - for line in s: - gem_name = line.split('Gem::') - if len(gem_name) > 1: - # Only take the name as everything leading up to the first '.' if found - # Gem naming conventions will have GemName.Editor, GemName.Server, and GemName - # as different targets of the GemName Gem - gem_list.add(gem_name[1].split('.')[0].replace('\n', '')) - return gem_list - - -def remove_gem_dependency(cmake_file: str, - gem_target: str) -> int: - """ - removes a gem dependency from a cmake file - :param cmake_file: path to the cmake file - :param gem_target: cmake target name - :return: 0 for success or non 0 failure code - """ - if not os.path.isfile(cmake_file): - logger.error(f'Failed to locate cmake file {cmake_file}') - return 1 - - # on a line by basis, remove any line with Gem::{gem_name} - t_data = [] - # Remove the gem from the cmake_dependencies file by skipping the gem name entry - removed = False - with open(cmake_file, 'r') as s: - for line in s: - if f'Gem::{gem_target}' in line: - removed = True - else: - t_data.append(line) - - if not removed: - logger.error(f'Failed to remove Gem::{gem_target} from cmake file {cmake_file}') - return 1 - - # write the cmake - os.unlink(cmake_file) - with open(cmake_file, 'w') as s: - s.writelines(t_data) - - return 0 - - -def add_gem_subdir(cmake_file: str, - gem_name: str) -> int: - """ - adds a gem subdir to a cmake file - :param cmake_file: path to the cmake file - :param gem_name: name of the gem to add - :return: 0 for success or non 0 failure code - """ - if not os.path.isfile(cmake_file): - logger.error(f'Failed to locate cmake file {cmake_file}') - return 1 - - if not utils.validate_identifier(gem_name): - logger.error(f'{gem_name} is not a valid gem name') - return 1 - - # on a line by basis, see if there already is Gem::{gem_name} - # find the first occurrence of a gem, copy its formatting and replace - # the gem name with the new one and append it - # if the gem is already present fail - t_data = [] - added = False - add_line = f'add_subdirectory({gem_name})' - with open(cmake_file, 'r') as s: - for line in s: - if add_line in line: - logger.info(f'{add_line} is already in {cmake_file} - skipping.') - return 0 - if not added and r'add_subdirectory(' in line: - new_gem = ' ' * line.find(r'add_subdirectory(') + f'add_subdirectory({gem_name})\n' - t_data.append(new_gem) - added = True - t_data.append(line) - - if not added: - logger.error(f'Failed to add add_subdirectory({gem_name}) from {cmake_file}.') - return 1 - - # write the cmake - os.unlink(cmake_file) - with open(cmake_file, 'w') as s: - s.writelines(t_data) - - return 0 - - -def remove_gem_subdir(cmake_file: str, - gem_name: str) -> int: - """ - removes a gem subdir form a cmake file - :param cmake_file: path to the cmake file - :param gem_name: name of the gem to remove - :return: 0 for success or non 0 failure code - """ - if not os.path.isfile(cmake_file): - logger.error(f'Failed to locate cmake file {cmake_file}') - return 1 - - # on a line by basis, remove any line with Gem::{gem_name} - t_data = [] - # Remove the gem from the cmake_dependencies file by skipping the gem name entry - removed = False - with open(cmake_file, 'r') as s: - for line in s: - if f'add_subdirectory({gem_name})' in line: - removed = True - else: - t_data.append(line) - - if not removed: - logger.error(f'Failed to remove add_subdirectory({gem_name}) from {cmake_file}') - return 1 - - # write the cmake - os.unlink(cmake_file) - with open(cmake_file, 'w') as s: - s.writelines(t_data) - - return 0 - - -def get_dependencies_cmake(project_path: str, - dependency_type: str = 'runtime', - platform: str = 'Common') -> str: - if not project_path: - return '' - if platform == 'Common': - dependencies_file = f'{dependency_type}_dependencies.cmake' - gem_path = os.path.join(project_path, 'Gem', 'Code', dependencies_file) - if os.path.isfile(gem_path): - return gem_path - return os.path.join(project_path, 'Code', dependencies_file) - else: - dependencies_file = f'{platform.lower()}_{dependency_type}_dependencies.cmake' - gem_path = os.path.join(project_path, 'Gem', 'Code', 'Platform', platform, dependencies_file) - if os.path.isfile(gem_path): - return gem_path - return os.path.join(project_path, 'Code', 'Platform', platform, dependencies_file) - -# this function is making some not so good assumptions about gem naming -def find_all_gems(gem_folder_list: list) -> list: - """ - Find all Modules which appear to be gems living within a list of folders - This method can be replaced or improved on with something more deterministic when LYN-1942 is done - :param gem_folder_list: - :return: list of gem objects containing gem Name and Path - """ - module_gems = {} - for folder in gem_folder_list: - root_len = len(os.path.normpath(folder).split(os.sep)) - for root, dirs, files in os.walk(folder): - for file in files: - if file == 'CMakeLists.txt': - with open(os.path.join(root, file), 'r') as s: - for line in s: - trimmed = line.lstrip() - if trimmed.startswith('NAME '): - trimmed = trimmed.rstrip(' \n') - split_trimmed = trimmed.split(' ') - # Is this a type indicating a gem lives here - if len(split_trimmed) == 3 and split_trimmed[2] in ['MODULE', 'GEM_MODULE', - '${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}']: - # Hold our "Name parts" such as Gem.MyGem.Editor as ['Gem', 'MyGem', 'Editor'] - name_split = split_trimmed[1].split('.') - split_folders = os.path.normpath(root).split(os.sep) - # This verifies we were passed a folder lower in the folder structure than the - # gem we've found, so we'll name it by the first directory. This is the common - # case currently of being handed "Gems" where things that live under Gems are - # known by the directory name - if len(split_folders) > root_len: - # Assume the gem is named by the root folder. May modules may exist - # together within the gem that are added collectively - gem_name = split_folders[root_len] - else: - # We were passed the folder with the gem already inside it, we have to name - # it by the gem name we found - gem_name = name_split[0] - this_gem = module_gems.get(gem_name, None) - if not this_gem: - if len(split_folders) > root_len: - # Assume the gem is named by the root folder. May modules may exist - # together within the gem that are added collectively - this_gem = {'Name': gem_name, - 'Path': os.path.join(folder, split_folders[root_len])} - else: - this_gem = {'Name': gem_name, 'Path': folder} - # Add any module types we know to be tools only here - if len(name_split) > 1 and name_split[1] in ['Editor', 'Builder']: - this_gem['Tools'] = True - else: - this_gem['Runtime'] = True - this_gem['Tools'] = True - - module_gems[gem_name] = this_gem - break - - return sorted(list(module_gems.values()), key=lambda x: x.get('Name')) - - -def find_gem_modules(gem_folder: str) -> list: - """ - Find all gem modules which appear to be gems living in this folder - :param gem_folder: the folder to walk down - :return: list of gem targets - """ - module_identifiers = [ - 'MODULE', - 'GEM_MODULE', - '${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}' - ] - modules = [] - for root, dirs, files in os.walk(gem_folder): - for file in files: - if file == 'CMakeLists.txt': - with open(os.path.join(root, file), 'r') as s: - for line in s: - trimmed = line.lstrip() - if trimmed.startswith('NAME '): - trimmed = trimmed.rstrip(' \n') - split_trimmed = trimmed.split(' ') - if len(split_trimmed) == 3 and split_trimmed[2] in module_identifiers: - modules.append(split_trimmed[1]) - return modules - - -def add_remove_gem(add: bool, - dev_root: str, - gem_path: str, - gem_target: str, - project_path: str, - dependencies_file: str or None, - project_restricted_path: str = 'restricted', - runtime_dependency: bool = False, - tool_dependency: bool = False, - server_dependency: bool = False, - platforms: str = 'Common', - gem_cmake: str = None) -> int: - """ - add a gem to a project - :param add: should we add a gem, if false we remove a gem - :param dev_root: the dev root of the engine - :param gem_path: path to the gem to add - :param gem_target: the name of teh cmake gem module - :param project_path: path to the project to add the gem to - :param dependencies_file: if this dependency goes/is in a specific file - :param project_restricted_path: path to the projects restricted folder - :param runtime_dependency: bool to specify this is a runtime gem for the game - :param tool_dependency: bool to specify this is a tool gem for the editor - :param server_dependency: bool to specify this is a server gem for the server - :param platforms: str to specify common or which specific platforms - :param gem_cmake: str to specify that this gem should be removed from the Gems/CMakeLists.txt - :return: 0 for success or non 0 failure code - """ - - # if no dev root error - if not dev_root: - logger.error('Dev root cannot be empty.') - return 1 - if not os.path.isabs(dev_root): - dev_root = os.path.abspath(dev_root) - dev_root = pathlib.Path(dev_root) - if not dev_root.is_dir(): - logger.error(f'Dev root {dev_root} is not a folder.') - return 1 - - # if no project path error - if not project_path: - logger.error('Project path cannot be empty.') - return 1 - if not os.path.isabs(project_path): - project_path = f'{dev_root}/{project_path}' - project_path = pathlib.Path(project_path) - if not project_path.is_dir(): - logger.error(f'Project path {project_path} is not a folder.') - return 1 - - project_restricted_path = project_restricted_path.replace('\\', '/') - if not os.path.isabs(project_restricted_path): - project_restricted_path = f'{dev_root}/{project_restricted_path}' - project_restricted_path = pathlib.Path(project_restricted_path) - if not project_restricted_path.is_dir(): - logger.error(f'Project Restricted path {project_restricted_path} is not a folder.') - return 1 - - # if no gem path error - if not gem_path: - logger.error('Gem path cannot be empty.') - return 1 - if not os.path.isabs(gem_path): - gem_path = f'{dev_root}/Gems/{gem_path}' - gem_path = pathlib.Path(gem_path) - # make sure this gem already exists if we're adding. We can always remove a gem. - if add and not gem_path.is_dir(): - logger.error(f'{gem_path} dir does not exist.') - return 1 - - # find all available modules in this gem_path - modules = find_gem_modules(gem_path) - if len(modules) == 0: - logger.error(f'No gem modules found.') - return 1 - - # if gem target not specified, see if there is only 1 module - if not gem_target: - if len(modules) == 1: - gem_target = modules[0] - else: - logger.error(f'Gem target not specified: {modules}') - return 1 - elif gem_target not in modules: - logger.error(f'Gem target not in gem modules: {modules}') - return 1 - - # gem cmake list text file is assumed to be in the parent folder is not specified - if add or isinstance(gem_cmake, str): - if not gem_cmake: - gem_cmake = gem_path.parent / 'CMakeLists.txt' - if not os.path.isabs(gem_cmake): - gem_cmake = f'{dev_root}/{gem_cmake}' - gem_cmake = pathlib.Path(gem_cmake) - if not gem_cmake.is_file(): - logger.error(f'CMakeLists.txt file {gem_cmake} does not exist.') - return 1 - - # if the user has not specified either we will assume they meant the most common which is runtime - if not runtime_dependency and not tool_dependency and not server_dependency and not dependencies_file: - logger.warning("Dependency type not specified: Assuming '--runtime-dependency'") - runtime_dependency = True - - ret_val = 0 - - # if the user has specified the dependencies file then ignore the runtime_dependency and tool_dependency flags - if dependencies_file: - dependencies_file = pathlib.Path(dependencies_file) - # make sure this is a project has a dependencies_file - if not dependencies_file.is_file(): - logger.error(f'Dependencies file {dependencies_file} is not present.') - return 1 - if add: - # add the dependency - ret_val = add_gem_dependency(dependencies_file, gem_target) - else: - # remove the dependency - ret_val = remove_gem_dependency(dependencies_file, gem_target) - else: - if ',' in platforms: - platforms = platforms.split(',') - else: - platforms = [platforms] - for platform in platforms: - if runtime_dependency: - # make sure this is a project has a runtime_dependencies.cmake file - project_runtime_dependencies_file = pathlib.Path(get_dependencies_cmake(project_path, 'runtime', platform)) - if not project_runtime_dependencies_file.is_file(): - logger.error(f'Runtime dependencies file {project_runtime_dependencies_file} is not present.') - return 1 - - if add: - # add the dependency - ret_val = add_gem_dependency(project_runtime_dependencies_file, gem_target) - else: - # remove the dependency - ret_val = remove_gem_dependency(project_runtime_dependencies_file, gem_target) - - if (ret_val == 0 or not add) and tool_dependency: - # make sure this is a project has a tool_dependencies.cmake file - project_tool_dependencies_file = pathlib.Path(get_dependencies_cmake(project_path, 'tool', platform)) - if not project_tool_dependencies_file.is_file(): - logger.error(f'Tool dependencies file {project_tool_dependencies_file} is not present.') - return 1 - - if add: - # add the dependency - ret_val = add_gem_dependency(project_tool_dependencies_file, gem_target) - else: - # remove the dependency - ret_val = remove_gem_dependency(project_tool_dependencies_file, gem_target) - - if (ret_val == 0 or not add) and server_dependency: - # make sure this is a project has a tool_dependencies.cmake file - project_server_dependencies_file = pathlib.Path(get_dependencies_cmake(project_path, 'server', platform)) - if not project_server_dependencies_file.is_file(): - logger.error(f'Server dependencies file {project_server_dependencies_file} is not present.') - return 1 - - if add: - # add the dependency - ret_val = add_gem_dependency(project_server_dependencies_file, gem_target) - else: - # remove the dependency - ret_val = remove_gem_dependency(project_server_dependencies_file, gem_target) - - if add: - # add the gem subdir to the CMakeList.txt - ret_val = add_gem_subdir(gem_cmake, gem_path.name) - elif gem_cmake: - # remove the gem subdir from the CMakeList.txt - ret_val = remove_gem_subdir(gem_cmake, gem_path.name) - - return ret_val - - -def _run_add_gem(args: argparse) -> int: - return add_remove_gem(True, - common.determine_engine_root(), - args.gem_path, - args.gem_target, - args.project_path, - args.dependencies_file, - args.project_restricted_path, - args.runtime_dependency, - args.tool_dependency, - args.server_dependency, - args.platforms, - args.add_to_cmake) - - -def _run_remove_gem(args: argparse) -> int: - return add_remove_gem(False, - common.determine_engine_root(), - args.gem_path, - args.gem_target, - args.project_path, - args.dependencies_file, - args.project_restricted_path, - args.runtime_dependency, - args.tool_dependency, - args.server_dependency, - args.platforms, - args.remove_from_cmake) - - -def add_args(parser, subparsers) -> None: - """ - add_args is called to add expected parser arguments and subparsers arguments to each command such that it can be - invoked locally or aggregated by a central python file. - Ex. Directly run from this file alone with: python add_remove_gem.py add_gem --gem TestGem --project TestProject - OR - lmbr.py can aggregate commands by importing add_remove_gem, call add_args and - execute: python lmbr.py add_gem --gem TestGem --project TestProject - :param parser: the caller instantiates a parser and passes it in here - :param subparsers: the caller instantiates subparsers and passes it in here - """ - add_gem_subparser = subparsers.add_parser('add-gem') - add_gem_subparser.add_argument('-pp', '--project-path', type=str, required=True, - help='The path to the project, can be absolute or dev root relative') - add_gem_subparser.add_argument('-prp', '--project-restricted-path', type=str, required=False, - default='restricted', - help='The path to the projects restricted folder, can be absolute or dev root' - ' relative') - add_gem_subparser.add_argument('-gp', '--gem-path', type=str, required=True, - help='The path to the gem, can be absolute or dev root/Gems relative') - add_gem_subparser.add_argument('-gt', '--gem-target', type=str, required=False, - help='The cmake target name to add. If not specified it will assume gem_name') - add_gem_subparser.add_argument('-df', '--dependencies-file', type=str, required=False, - help='The cmake dependencies file in which the gem dependencies are specified.' - 'If not specified it will assume ') - add_gem_subparser.add_argument('-rd', '--runtime-dependency', action='store_true', required=False, - default=False, - help='Optional toggle if this gem should be added as a runtime dependency') - add_gem_subparser.add_argument('-td', '--tool-dependency', action='store_true', required=False, - default=False, - help='Optional toggle if this gem should be added as a tool dependency') - add_gem_subparser.add_argument('-sd', '--server-dependency', action='store_true', required=False, - default=False, - help='Optional toggle if this gem should be added as a server dependency') - add_gem_subparser.add_argument('-pl', '--platforms', type=str, required=False, - default='Common', - help='Optional list of platforms this gem should be added to.' - ' Ex. --platforms Mac,Windows,Linux') - add_gem_subparser.add_argument('-a', '--add-to-cmake', type=str, required=False, - default=None, - help='Add the gem folder to the CMakeLists.txt so that it builds. If not' - ' specified it will assume the CMakeLists.txt file in the parent folder of' - ' the gem_path.') - add_gem_subparser.set_defaults(func=_run_add_gem) - - remove_gem_subparser = subparsers.add_parser('remove-gem') - remove_gem_subparser.add_argument('-pp', '--project-path', type=str, required=True, - help='The path to the project, can be absolute or dev root relative') - remove_gem_subparser.add_argument('-prp', '--project-restricted-path', type=str, required=False, - default='restricted', - help='The path to the projects restricted folder, can be absolute or dev root' - ' relative') - remove_gem_subparser.add_argument('-gp', '--gem-path', type=str, required=True, - help='The path to the gem, can be absolute or dev root/Gems relative') - remove_gem_subparser.add_argument('-gt', '--gem-target', type=str, required=False, - help='The cmake target name to add. If not specified it will assume gem_name') - remove_gem_subparser.add_argument('-df', '--dependencies-file', type=str, required=False, - help='The cmake dependencies file in which the gem dependencies are specified.' - 'If not specified it will assume ') - remove_gem_subparser.add_argument('-rd', '--runtime-dependency', action='store_true', required=False, - default=False, - help='Optional toggle if this gem should be removed as a runtime dependency') - remove_gem_subparser.add_argument('-td', '--tool-dependency', action='store_true', required=False, - default=False, - help='Optional toggle if this gem should be removed as a server dependency') - remove_gem_subparser.add_argument('-sd', '--server-dependency', action='store_true', required=False, - default=False, - help='Optional toggle if this gem should be removed as a server dependency') - remove_gem_subparser.add_argument('-pl', '--platforms', type=str, required=False, - default='Common', - help='Optional list of platforms this gem should be removed from' - ' Ex. --platforms Mac,Windows,Linux') - remove_gem_subparser.add_argument('-r', '--remove-from-cmake', type=str, required=False, - default=None, - help='Remove the gem folder from the CMakeLists.txt that includes it so that it ' - 'no longer builds build. If not specified it will assume you dont want to' - ' remove it.') - remove_gem_subparser.set_defaults(func=_run_remove_gem) - - -if __name__ == "__main__": - # parse the command line args - the_parser = argparse.ArgumentParser() - - # add subparsers - the_subparsers = the_parser.add_subparsers(help='sub-command help') - - # add args to the parser - add_args(the_parser, the_subparsers) - - # parse args - the_args = the_parser.parse_args() - - # run - ret = the_args.func(the_args) - - # return - sys.exit(ret) diff --git a/cmake/Tools/current_project.py b/cmake/Tools/current_project.py deleted file mode 100755 index 8be18fd3c9..0000000000 --- a/cmake/Tools/current_project.py +++ /dev/null @@ -1,117 +0,0 @@ -# -# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or -# its licensors. -# -# For complete copyright and license terms please see the LICENSE at the root of this -# distribution (the "License"). All use of this software is governed by the License, -# or, if provided, by the license below or the license accompanying this file. Do not -# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# - -import argparse -import logging -import os -import sys -import re - -from cmake.Tools import common - -logger = logging.getLogger() -logging.basicConfig() - - -def set_current_project(dev_root: str, - project_path: str) -> int: - """ - set what the current project is - :param dev_root: the dev root of the engine - :param project_path: the path of the project you want to set - :return: 0 for success or non 0 failure code - """ - project_path = project_path.strip() - if not project_path.isalnum(): - logger.error('Project Name invalid. Set current project failed.') - return 1 - try: - with open(os.path.join(dev_root, 'bootstrap.cfg'), 'r') as s: - data = s.read() - data = re.sub(r'(.*project_path\s*?[=:]\s*?)([^\n]+)\n', r'\1 {}\n'.format(project_path), - data, flags=re.IGNORECASE) - if os.path.isfile(os.path.join(dev_root, 'bootstrap.cfg')): - os.unlink(os.path.join(dev_root, 'bootstrap.cfg')) - with open(os.path.join(dev_root, 'bootstrap.cfg'), 'w') as s: - s.write(data) - except Exception as e: - logger.error('Set current project failed.' + str(e)) - return 1 - return 0 - - -def get_current_project(dev_root: str) -> str: - """ - get what the current project set is - :param dev_root: the dev root of the engine - :return: project_path or None on failure - """ - try: - with open(os.path.join(dev_root, 'bootstrap.cfg'), 'r') as s: - data = s.read() - project_path = re.search(r'(.*project_path\s*?[=:]\s*?)(?P[^\n]+)\n', - data, flags=re.IGNORECASE).group('project_path').strip() - except Exception as e: - logger.error('Failed to get current project. Exception: ' + str(e)) - return '' - return project_path - - -def _run_get_current_project(args: argparse) -> int: - project_path = get_current_project(common.determine_engine_root()) - if project_path: - print(project_path) - return 0 - return 1 - - -def _run_set_current_project(args: argparse) -> int: - return set_current_project(common.determine_engine_root(), args.project_path) - - -def add_args(parser, subparsers) -> None: - """ - add_args is called to add expected parser arguments and subparsers arguments to each command such that it can be - invoked locally or aggregated by a central python file. - Ex. Directly run from this file alone with: python current_project.py set_current_project --project TestProject - OR - lmbr.py can aggregate commands by importing current_project, call add_args and - execute: python lmbr.py set_current_project --project TestProject - :param parser: the caller instantiates a parser and passes it in here - :param subparsers: the caller instantiates subparsers and passes it in here - """ - get_current_project_subparser = subparsers.add_parser('get-current-project') - get_current_project_subparser.set_defaults(func=_run_get_current_project) - - set_current_project_subparser = subparsers.add_parser('set-current-project') - set_current_project_subparser.add_argument('-pp', '--project-path', required=True, - help='The path to the project, can be absolute or dev root relative') - set_current_project_subparser.set_defaults(func=_run_set_current_project) - - -if __name__ == "__main__": - # parse the command line args - the_parser = argparse.ArgumentParser() - - # add subparsers - the_subparsers = the_parser.add_subparsers(help='sub-command help') - - # add args to the parser - add_args(the_parser, the_subparsers) - - # parse args - the_args = the_parser.parse_args() - - # run - ret = the_args.func(the_args) - - # return - sys.exit(ret) diff --git a/cmake/Tools/engine_template.py b/cmake/Tools/engine_template.py index bf1ca93e57..eefb8c5541 100755 --- a/cmake/Tools/engine_template.py +++ b/cmake/Tools/engine_template.py @@ -21,7 +21,7 @@ import uuid import re from cmake.Tools import utils -from cmake.Tools import common +import cmake.Tools.registration as registration logger = logging.getLogger() logging.basicConfig() @@ -117,7 +117,7 @@ def _transform(s_data: str, end = t_data.find('{END_LICENSE}') end = t_data.find('\n', end) if end != -1: - t_data = t_data[:line_start] + t_data[end+1:] + t_data = t_data[:line_start] + t_data[end + 1:] ################################################################### return t_data @@ -131,8 +131,6 @@ def _transform_copy(source_file: str, :param source_file: the source file to be transformed :param destination_file: the destination file, this is the transformed file :param replacements: list of transformation pairs A->B - :param keep_restricted_in_instance: whether or not you want ot keep the templates restricted files in your instance - or separate them out into the restricted folder :param keep_license_text: whether or not you want to keep license text """ # if its a known binary type just copy it, else try to transform it. @@ -159,7 +157,7 @@ def _transform_copy(source_file: str, pass -def _execute_template_json(json_data: str, +def _execute_template_json(json_data: dict, destination_path: str, template_path: str, replacements: list, @@ -203,7 +201,7 @@ def _execute_template_json(json_data: str, shutil.copy(in_file, out_file) -def _execute_restricted_template_json(json_data: str, +def _execute_restricted_template_json(json_data: dict, restricted_platform: str, destination_name, template_name, @@ -215,11 +213,22 @@ def _execute_restricted_template_json(json_data: str, replacements: list, keep_restricted_in_instance: bool = False, keep_license_text: bool = False) -> None: + # if we are not keeping restricted in instance make restricted.json if not present + if not keep_restricted_in_instance: + restricted_json = f"{destination_restricted_path}/restricted.json".replace('//', '/') + os.makedirs(os.path.dirname(restricted_json), exist_ok=True) + if not os.path.isfile(restricted_json): + with open(restricted_json, 'w') as s: + restricted_json_data = {} + restricted_json_data.update({"restricted_name": destination_name}) + s.write(json.dumps(restricted_json_data, indent=4)) + # create dirs first # for each createDirectory entry, transform the folder name for create_directory in json_data['createDirectories']: # construct the new folder name - new_dir = f"{destination_restricted_path}/{restricted_platform}/{destination_restricted_platform_relative_path}/{destination_name}/{create_directory['dir']}".replace('//', '/') + new_dir = f"{destination_restricted_path}/{restricted_platform}/{destination_restricted_platform_relative_path}/{destination_name}/{create_directory['dir']}".replace( + '//', '/') if keep_restricted_in_instance: new_dir = f"{destination_path}/{create_directory['origin']}".replace('//', '/') @@ -233,7 +242,8 @@ def _execute_restricted_template_json(json_data: str, # regular copy if not templated for copy_file in json_data['copyFiles']: # construct the input file name - in_file = f"{template_restricted_path}/{restricted_platform}/{template_restricted_platform_relative_path}/{template_name}/Template/{copy_file['file']}".replace('//', '/') + in_file = f"{template_restricted_path}/{restricted_platform}/{template_restricted_platform_relative_path}/{template_name}/Template/{copy_file['file']}".replace( + '//', '/') # the file can be marked as optional, if it is and it does not exist skip if copy_file['isOptional'] and copy_file['isOptional'] == 'true': @@ -241,7 +251,8 @@ def _execute_restricted_template_json(json_data: str, continue # construct the output file name - out_file = f"{destination_restricted_path}/{restricted_platform}/{destination_restricted_platform_relative_path}/{destination_name}/{copy_file['file']}".replace('//', '/') + out_file = f"{destination_restricted_path}/{restricted_platform}/{destination_restricted_platform_relative_path}/{destination_name}/{copy_file['file']}".replace( + '//', '/') if keep_restricted_in_instance: out_file = f"{destination_path}/{copy_file['origin']}".replace('//', '/') @@ -258,7 +269,8 @@ def _execute_restricted_template_json(json_data: str, shutil.copy(in_file, out_file) -def _instantiate_template(destination_name: str, +def _instantiate_template(template_json_data: dict, + destination_name: str, template_name: str, destination_path: str, template_path: str, @@ -272,47 +284,47 @@ def _instantiate_template(destination_name: str, """ Internal function to create a concrete instance from a template - :param destination_path: the folder you want to instantiate the template in, absolute or dev_root relative + :param template_json_data: the template json data + :param destination_name: the name of folder you want to instantiate the template in + :param template_name: the name of the template + :param destination_path: the path you want to instantiate the template in :param template_path: the path of the template - :param template_path_rel: the relative path of the template + :param destination_restricted_path: the path of the restricted destination :param template_restricted_path: the path of the restricted template + :param destination_restricted_platform_relative_path: any path after the Platform of the restricted destination + :param template_restricted_platform_relative_path: any path after the Platform of the restricted template :param replacements: optional list of strings uses to make concrete names out of templated parameters. X->Y pairs Ex. ${Name},TestGem,${Player},TestGemPlayer This will cause all references to ${Name} be replaced by TestGem, and all ${Player} replaced by 'TestGemPlayer' - :param keep_restricted_in_instance: whether or not you want ot keep the templates restricted files in your instance + :param keep_restricted_in_instance: whether or not you want to keep the templates restricted files in your instance or separate them out into the restricted folder - :param keep_license_text: whether or not you want ot keep the templates license text in your instance. + :param keep_license_text: whether or not you want to keep the templates license text in your instance. template can have license blocks starting with {BEGIN_LICENSE} and ending with {END_LICENSE}, this controls if you want to keep the license text from the template in the new instance. It is false by default - because most customers will not want license text in there instances, but we may want to keep them. + because most customers will not want license text in their instances, but we may want to keep them. :return: 0 for success or non 0 failure code """ - # make sure the template json is found - template_json = f'{template_path}/{template_file_name}' - if not os.path.isfile(template_json): - logger.error(f'Template json {template_json} not found.') - return 1 - - # load the template json and execute it - with open(template_json, 'r') as s: - try: - json_data = json.load(s) - except Exception as e: - logger.error(f'Failed to load {template_json}: ' + str(e)) - return 1 - _execute_template_json(json_data, - destination_path, - template_path, - replacements, - keep_license_text) + # execute the template json + _execute_template_json(template_json_data, + destination_path, + template_path, + replacements, + keep_license_text) # execute restricted platform jsons if any - if os.path.isdir(template_restricted_path): + if template_restricted_path: for restricted_platform in os.listdir(template_restricted_path): + if os.path.isfile(restricted_platform): + continue template_restricted_platform = f'{template_restricted_path}/{restricted_platform}' template_restricted_platform_path_rel = f'{template_restricted_platform}/{template_restricted_platform_relative_path}/{template_name}' platform_json = f'{template_restricted_platform_path_rel}/{template_file_name}'.replace('//', '/') + if os.path.isfile(platform_json): + if not registration.valid_o3de_template_json(platform_json): + logger.error(f'Template json {platform_json} is invalid.') + return 1 + # load the template json and execute it with open(platform_json, 'r') as s: try: @@ -320,72 +332,64 @@ def _instantiate_template(destination_name: str, except Exception as e: logger.error(f'Failed to load {platform_json}: ' + str(e)) return 1 - _execute_restricted_template_json(json_data, - restricted_platform, - destination_name, - template_name, - destination_path, - destination_restricted_path, - template_restricted_path, - destination_restricted_platform_relative_path, - template_restricted_platform_relative_path, - replacements, - keep_restricted_in_instance, - keep_license_text) + else: + _execute_restricted_template_json(json_data, + restricted_platform, + destination_name, + template_name, + destination_path, + destination_restricted_path, + template_restricted_path, + destination_restricted_platform_relative_path, + template_restricted_platform_relative_path, + replacements, + keep_restricted_in_instance, + keep_license_text) return 0 -def create_template(dev_root: str, - source_path: str, +def create_template(source_path: str, template_path: str, - source_restricted_path: str, - template_restricted_path: str, - source_restricted_platform_relative_path: str, - template_restricted_platform_relative_path: str, + source_restricted_path: str = None, + source_restricted_name: str = None, + template_restricted_path: str = None, + template_restricted_name: str = None, + source_restricted_platform_relative_path: str = None, + template_restricted_platform_relative_path: str = None, keep_restricted_in_template: bool = False, keep_license_text: bool = False, replace: list = None) -> int: """ - Create a generic template from a source directory using replacement + Create a template from a source directory using replacement - :param dev_root: the path to dev root of the engine - :param source_path: source folder, absolute or dev_root relative - :param template_path: the path of the template to create, can be absolute or relative to dev root/Templates, if none - then it will be the source_name - :param source_restricted_path: path to the projects restricted folder + :param source_path: The path to the source that you want to make into a template + :param template_path: the path of the template to create, can be absolute or relative to default templates path + :param source_restricted_path: path to the source restricted folder + :param source_restricted_name: name of the source restricted folder :param template_restricted_path: path to the templates restricted folder + :param template_restricted_name: name of the templates restricted folder + :param source_restricted_platform_relative_path: any path after the platform in the source restricted + :param template_restricted_platform_relative_path: any path after the platform in the template restricted :param replace: optional list of strings uses to make templated parameters out of concrete names. X->Y pairs Ex. TestGem,${Name},TestGemPlayer,${Player} This will cause all references to 'TestGem' be replaced by ${Name}, and all 'TestGemPlayer' replaced by ${Player} Note these replacements are executed in order, so if you have larger matches, do them first, i.e. TestGemPlayer,${Player},TestGem,${Name} TestGemPlayer will get matched first and become ${Player} and will not become ${Name}Player - :param keep_restricted_in_template: whether or not you want ot keep the templates restricted in your template. - :param keep_license_text: whether or not you want ot keep the templates license text in your instance. - template can have license blocks starting with {BEGIN_LICENSE} and ending with {END_LICENSE}, + :param keep_restricted_in_template: whether or not you want to keep the templates restricted in your template. + :param keep_license_text: whether or not you want to keep the templates license text in your instance. + Templated files can have license blocks starting with {BEGIN_LICENSE} and ending with {END_LICENSE}, this controls if you want to keep the license text from the template in the new instance. It is false by default - because most customers will not want license text in there instances, but we may want to keep them. + because most people will not want license text in their instances. :return: 0 for success or non 0 failure code """ - # if no dev root error - if not dev_root: - logger.error('Dev root cannot be empty.') - return 1 - dev_root = dev_root.replace('\\', '/') - if not os.path.isabs(dev_root): - dev_root = os.path.abspath(dev_root) - if not os.path.isdir(dev_root): - logger.error(f'Dev root {dev_root} is not a folder.') - return 1 # if no src path error if not source_path: logger.error('Src path cannot be empty.') return 1 source_path = source_path.replace('\\', '/') - if not os.path.isabs(source_path): - source_path = f'{dev_root}/{source_path}' if not os.path.isdir(source_path): logger.error(f'Src path {source_path} is not a folder.') return 1 @@ -393,13 +397,15 @@ def create_template(dev_root: str, # source_name is now the last component of the source_path source_name = os.path.basename(source_path) - # if no new template path error + # if no template path, error if not template_path: - logger.warning('Template path empty. Using source name') + logger.info(f'Template path empty. Using source name {source_name}') template_path = source_name template_path = template_path.replace('\\', '/') if not os.path.isabs(template_path): - template_path = f'{dev_root}/Templates/{template_path}' + default_templates_folder = registration.get_registered(default_folder='templates') + template_path = f'{default_templates_folder}/{template_path}' + logger.info(f'Template path not a full path. Using default templates folder {template_path}') if os.path.isdir(template_path): logger.error(f'Template path {template_path} is already exists.') return 1 @@ -412,26 +418,85 @@ def create_template(dev_root: str, logger.error(f'Template path cannot be a restricted name. {template_name}') return 1 + if source_restricted_name and not source_restricted_path: + source_restricted_path = registration.get_registered(restricted_name=source_restricted_name) + # source_restricted_path - source_restricted_path = source_restricted_path.replace('\\', '/') - if not os.path.isabs(source_restricted_path): - source_restricted_path = f'{dev_root}/{source_restricted_path}' - if not os.path.isdir(source_restricted_path): - logger.error(f'Src restricted path {source_restricted_path} is not a folder.') - return 1 + if source_restricted_path: + source_restricted_path = source_restricted_path.replace('\\', '/') + if not os.path.isabs(source_restricted_path): + engine_json = f'{registration.get_this_engine_path()}/engine.json' + if not registration.valid_o3de_engine_json(engine_json): + logger.error(f"Engine json {engine_json} is not valid.") + return 1 + with open(engine_json) as s: + try: + engine_json_data = json.load(s) + except Exception as e: + logger.error(f"Failed to read engine json {engine_json}: {str(e)}") + return 1 + try: + engine_restricted = engine_json_data['restricted'] + except Exception as e: + logger.error(f"Engine json {engine_json} restricted not found.") + return 1 + engine_restricted_folder = registration.get_registered(restricted_name=engine_restricted) + new_source_restricted_path = f'{engine_restricted_folder}/{source_restricted_path}' + logger.info(f'Source restricted path {source_restricted_path} not a full path. We must assume this engines' + f' restricted folder {new_source_restricted_path}') + if not os.path.isdir(source_restricted_path): + logger.error(f'Source restricted path {source_restricted_path} is not a folder.') + return 1 + + if template_restricted_name and not template_restricted_path: + template_restricted_path = registration.get_registered(restricted_name=template_restricted_name) + + if not template_restricted_name: + template_restricted_name = template_name # template_restricted_path - template_restricted_path = template_restricted_path.replace('\\', '/') - if not os.path.isabs(template_restricted_path): - template_restricted_path = f'{dev_root}/{template_restricted_path}' - if not os.path.isdir(template_restricted_path): - os.makedirs(template_restricted_path) + if template_restricted_path: + template_restricted_path = template_restricted_path.replace('\\', '/') + if not os.path.isabs(template_restricted_path): + default_templates_restricted_folder = registration.get_registered(restricted_name='templates') + new_template_restricted_path = f'{default_templates_restricted_folder}/{template_restricted_path}' + logger.info(f'Template restricted path {template_restricted_path} not a full path. We must assume the' + f' default templates restricted folder {new_template_restricted_path}') + template_restricted_path = new_template_restricted_path + + if os.path.isdir(template_restricted_path): + # see if this is already a restricted path, if it is get the "restricted_name" from the restricted json + # so we can set "restricted" to it for this template + restricted_json = f'{template_restricted_path}/restricted.json' + if os.path.isfile(restricted_json): + if not registration.valid_o3de_restricted_json(restricted_json): + logger.error(f'{restricted_json} is not valid.') + return 1 + with open(restricted_json, 'r') as s: + try: + restricted_json_data = json.load(s) + except Exception as e: + logger.error(f'Failed to load {restricted_json}: ' + str(e)) + return 1 + try: + template_restricted_name = restricted_json_data['restricted_name'] + except Exception as e: + logger.error(f'Failed to read restricted_name from {restricted_json}') + return 1 + else: + os.makedirs(template_restricted_path) # source restricted relative - source_restricted_platform_relative_path = source_restricted_platform_relative_path.replace('\\', '/') + if source_restricted_platform_relative_path: + source_restricted_platform_relative_path = source_restricted_platform_relative_path.replace('\\', '/') + else: + source_restricted_platform_relative_path = '' # template restricted relative - template_restricted_platform_relative_path = template_restricted_platform_relative_path.replace('\\', '/') + if template_restricted_platform_relative_path: + template_restricted_platform_relative_path = template_restricted_platform_relative_path.replace('\\', '/') + else: + template_restricted_platform_relative_path = '' logger.info(f'Processing Src: {source_path}') @@ -452,7 +517,7 @@ def create_template(dev_root: str, def _transform_into_template(s_data: object) -> (bool, object): """ Internal function to transform any data into templated data - :param source_data: the input data, this could be file data or file name data + :param s_data: the input data, this could be file data or file name data :return: bool: whether or not the returned data MAY need to be transformed to instantiate it t_data: potentially transformed data 0 for success or non 0 failure code """ @@ -507,7 +572,7 @@ def create_template(dev_root: str, platform: str) -> (bool, object): """ Internal function to transform a restricted platform file name into restricted template file name - :param source_data: the input data, this could be file data or file name data + :param s_data: the input data, this could be file data or file name data :return: bool: whether or not the returned data MAY need to be transformed to instantiate it t_data: potentially transformed data 0 for success or non 0 failure code """ @@ -581,14 +646,14 @@ def create_template(dev_root: str, else: after.append(components[x]) else: - for x in range(0, num_components-1): + for x in range(0, num_components - 1): relative += f'{components[x]}/' if os.path.isdir(f'{source_path}/{relative}'): before.append(components[x]) else: after.append(components[x]) - after.append(components[num_components-1]) + after.append(components[num_components - 1]) before.append("Platform") warn_if_not_platform = f'{source_path}/{"/".join(before)}' @@ -598,7 +663,8 @@ def create_template(dev_root: str, origin_entry_rel = '/'.join(before) if not os.path.isdir(warn_if_not_platform): - logger.warning(f'{entry_abs} -> {origin_entry_rel}: Other Platforms not found in {warn_if_not_platform}') + logger.warning( + f'{entry_abs} -> {origin_entry_rel}: Other Platforms not found in {warn_if_not_platform}') destination_entry_rel = origin_entry_rel destination_entry_abs = f'{template_path}/Template/{origin_entry_rel}' @@ -666,7 +732,8 @@ def create_template(dev_root: str, "dir": destination_entry_rel, "origin": origin_entry_rel }) - _transform_restricted_into_copyfiles_and_createdirs(source_path, restricted_platform, root_abs, entry_abs) + _transform_restricted_into_copyfiles_and_createdirs(source_path, restricted_platform, root_abs, + entry_abs) def _transform_dir_into_copyfiles_and_createdirs(root_abs: str, path_abs: str = None) -> None: @@ -722,6 +789,11 @@ def create_template(dev_root: str, # if not then create a normal relative and abs dst entry name _, origin_entry_rel = _transform_into_template(entry_rel) if platform and found_platform in restricted_platforms: + # if we don't have a template restricted path and we found restricted files... warn and skip + # the file/dir + if not template_restricted_path: + logger.warning("Restricted platform files found!!! {entry_rel}, {found_platform}") + continue _, destination_entry_rel = _transform_into_template_restricted_filename(entry_rel, found_platform) destination_entry_abs = f'{template_restricted_path}/{found_platform}/{template_restricted_platform_relative_path}/{template_name}/Template/{destination_entry_rel}' else: @@ -831,10 +903,13 @@ def create_template(dev_root: str, # every src may have a matching restricted folder per restricted platform # run the transformation on each src restricted folder - for restricted_platform in os.listdir(source_restricted_path): - restricted_platform_src_path_abs = f'{source_restricted_path}/{restricted_platform}/{source_restricted_platform_relative_path}/{source_name}'.replace('//', '/') - if os.path.isdir(restricted_platform_src_path_abs): - _transform_restricted_into_copyfiles_and_createdirs(source_path, restricted_platform, restricted_platform_src_path_abs) + if source_restricted_path: + for restricted_platform in os.listdir(source_restricted_path): + restricted_platform_src_path_abs = f'{source_restricted_path}/{restricted_platform}/{source_restricted_platform_relative_path}/{source_name}'.replace( + '//', '/') + if os.path.isdir(restricted_platform_src_path_abs): + _transform_restricted_into_copyfiles_and_createdirs(source_path, restricted_platform, + restricted_platform_src_path_abs) # sort copy_files.sort(key=lambda x: x['file']) @@ -845,12 +920,17 @@ def create_template(dev_root: str, json_data = {} json_data.update({'template_name': template_name}) json_data.update({'origin': f'The primary repo for {template_name} goes here: i.e. http://www.mydomain.com'}) - json_data.update({'license': f'What license {template_name} uses goes here: i.e. https://opensource.org/licenses/MIT'}) + json_data.update( + {'license': f'What license {template_name} uses goes here: i.e. https://opensource.org/licenses/MIT'}) json_data.update({'display_name': template_name}) json_data.update({'summary': f"A short description of {template_name}."}) json_data.update({'canonical_tags': []}) json_data.update({'user_tags': [f"{template_name}"]}) json_data.update({'icon_path': "preview.png"}) + if template_restricted_path: + json_data.update({'restricted': template_restricted_name}) + if template_restricted_platform_relative_path != '': + json_data.update({'template_restricted_platform_relative_path': template_restricted_platform_relative_path}) json_data.update({'copyFiles': copy_files}) json_data.update({'createDirectories': create_dirs}) @@ -862,174 +942,289 @@ def create_template(dev_root: str, with open(json_name, 'w') as s: s.write(json.dumps(json_data, indent=4)) - #copy the default preview.png + # copy the default preview.png this_script_parent = os.path.dirname(os.path.realpath(__file__)) preview_png_src = f'{this_script_parent}/preview.png' preview_png_dst = f'{template_path}/Template/preview.png' if not os.path.isfile(preview_png_dst): shutil.copy(preview_png_src, preview_png_dst) - # now write out each restricted platform template json separately - for restricted_platform in restricted_platform_entries: - restricted_template_path = f'{template_restricted_path}/{restricted_platform}/{template_restricted_platform_relative_path}/{template_name}'.replace('//', '/') - - #sort - restricted_platform_entries[restricted_platform]['copyFiles'].sort(key=lambda x: x['file']) - restricted_platform_entries[restricted_platform]['createDirs'].sort(key=lambda x: x['dir']) - - json_data = {} - json_data.update({'template_name': template_name}) - json_data.update({'origin': f'The primary repo for {template_name} goes here: i.e. http://www.mydomain.com'}) - json_data.update({'license': f'What license {template_name} uses goes here: i.e. https://opensource.org/licenses/MIT'}) - json_data.update({'display_name': template_name}) - json_data.update({'summary': f"A short description of {template_name}."}) - json_data.update({'canonical_tags': []}) - json_data.update({'user_tags': [f'{template_name}']}) - json_data.update({'icon_path': "preview.png"}) - json_data.update({'copyFiles': restricted_platform_entries[restricted_platform]['copyFiles']}) - json_data.update({'createDirectories': restricted_platform_entries[restricted_platform]['createDirs']}) - - json_name = f'{restricted_template_path}/{template_file_name}' - os.makedirs(os.path.dirname(json_name), exist_ok=True) - - # if the json file we are about to write already exists for some reason, delete it - if os.path.isfile(json_name): - os.unlink(json_name) - with open(json_name, 'w') as s: - s.write(json.dumps(json_data, indent=4)) - - preview_png_dst = f'{restricted_template_path}/Template/preview.png' - if not os.path.isfile(preview_png_dst): - shutil.copy(preview_png_src, preview_png_dst) + # if no restricted template path was given and restricted platform files were found + if not template_restricted_path and len(restricted_platform_entries): + logger.info(f'Restricted platform files found!!! and no template restricted path was found...') + + if template_restricted_path: + # now write out each restricted platform template json separately + for restricted_platform in restricted_platform_entries: + restricted_template_path = f'{template_restricted_path}/{restricted_platform}/{template_restricted_platform_relative_path}/{template_name}'.replace( + '//', '/') + + # sort + restricted_platform_entries[restricted_platform]['copyFiles'].sort(key=lambda x: x['file']) + restricted_platform_entries[restricted_platform]['createDirs'].sort(key=lambda x: x['dir']) + + json_data = {} + json_data.update({'template_name': template_name}) + json_data.update( + {'origin': f'The primary repo for {template_name} goes here: i.e. http://www.mydomain.com'}) + json_data.update( + {'license': f'What license {template_name} uses goes here: i.e. https://opensource.org/licenses/MIT'}) + json_data.update({'display_name': template_name}) + json_data.update({'summary': f"A short description of {template_name}."}) + json_data.update({'canonical_tags': []}) + json_data.update({'user_tags': [f'{template_name}']}) + json_data.update({'icon_path': "preview.png"}) + json_data.update({'copyFiles': restricted_platform_entries[restricted_platform]['copyFiles']}) + json_data.update({'createDirectories': restricted_platform_entries[restricted_platform]['createDirs']}) + + json_name = f'{restricted_template_path}/{template_file_name}' + os.makedirs(os.path.dirname(json_name), exist_ok=True) + + # if the json file we are about to write already exists for some reason, delete it + if os.path.isfile(json_name): + os.unlink(json_name) + with open(json_name, 'w') as s: + s.write(json.dumps(json_data, indent=4)) + + preview_png_dst = f'{restricted_template_path}/Template/preview.png' + if not os.path.isfile(preview_png_dst): + shutil.copy(preview_png_src, preview_png_dst) return 0 -def find_all_gem_templates(template_folder_list: list) -> list: - """ - Find all subfolders in the given list of folders which appear to be gem templates - :param template_folder_list: List of folders to search - :return: list of tuples of TemplateName, AbsolutePath - """ - return find_all_templates(template_folder_list, 'gem.json') - - -def find_all_project_templates(template_folder_list: list) -> list: - """ - Find all subfolders in the given list of folders which appear to be project templates - :param template_folder_list: List of folders to search - :return: list of tuples of TemplateName, AbsolutePath - """ - return find_all_templates(template_folder_list, 'project.json') - - -def find_all_templates(template_folder_list: list, template_marker_file: str) -> list: - """ - Find all subfolders in the given list of folders which appear to be templates - :param template_folder_list: List of folders to search - :param template_marker_file: file expected in the template folder - :return: list of tuples of TemplateName, AbsolutePath - """ - templates_found = [] - for folder in template_folder_list: - for root, dirs, files in os.walk(folder): - for dir in dirs: - if os.path.isfile(os.path.join(root, dir, template_file_name)) and \ - os.path.isfile(os.path.join(root, dir, 'Template', template_marker_file)): - templates_found.append((dir, os.path.join(root, dir))) - break # We only want root folders containing templates, do not recurse - return templates_found - - -def create_from_template(dev_root: str, - destination_path: str, - template_path: str, - destination_restricted_path: str, - template_restricted_path: str, - destination_restricted_platform_relative_path: str, - template_restricted_platform_relative_path: str, +def create_from_template(destination_path: str, + template_path: str = None, + template_name: str = None, + destination_restricted_path: str = None, + destination_restricted_name: str = None, + template_restricted_path: str = None, + template_restricted_name: str = None, + destination_restricted_platform_relative_path: str = None, + template_restricted_platform_relative_path: str = None, keep_restricted_in_instance: bool = False, keep_license_text: bool = False, replace: list = None) -> int: """ - Generic template instantiation. - :param dev_root: the path to the dev root of the engine - :param destination_path: the folder you want to put the instantiate template in, absolute or dev_root relative - :param template_path: the name of the template you want to instance + Generic template instantiation for non o3de object templates. This function makes NO assumptions! + Assumptions are made only for specializations like create_project or create_gem etc... So this function + will NOT try to divine intent. + :param destination_path: the folder you want to instantiate the template into + :param template_path: the path to the template you want to instance + :param template_name: the name of the template you want to instance, resolves template_path :param destination_restricted_path: path to the projects restricted folder - :param template_restricted_path: path to the templates restricted folder - :param keep_restricted_in_instance: whether or not you want ot keep the templates restricted files in your instance + :param destination_restricted_name: name of the projects restricted folder, resolves destination_restricted_path + :param template_restricted_path: path of the templates restricted folder + :param template_restricted_name: name of the templates restricted folder, resolves template_restricted_path + :param destination_restricted_platform_relative_path: any path after the platform in the destination restricted + :param template_restricted_platform_relative_path: any path after the platform in the template restricted + :param keep_restricted_in_instance: whether or not you want to keep the templates restricted files in your instance or separate them out into the restricted folder - :param keep_license_text: whether or not you want ot keep the templates license text in your instance. + :param keep_license_text: whether or not you want to keep the templates license text in your instance. template can have license blocks starting with {BEGIN_LICENSE} and ending with {END_LICENSE}, this controls if you want to keep the license text from the template in the new instance. It is false by default - because most customers will not want license text in there instances, but we may want to keep them. + because most customers will not want license text in their instances, but we may want to keep them. :param replace: optional list of strings uses to make concrete names out of templated parameters. X->Y pairs Ex. ${Name},TestGem,${Player},TestGemPlayer This will cause all references to ${Name} be replaced by TestGem, and all ${Player} replaced by 'TestGemPlayer' :return: 0 for success or non 0 failure code """ - # if no dev root error - if not dev_root: - logger.error('Dev root cannot be empty.') + if template_name and template_path: + logger.error(f'Template Name and Template Path provided, these are mutually exclusive.') return 1 - dev_root = dev_root.replace('\\', '/') - if not os.path.isabs(dev_root): - dev_root = os.path.abspath(dev_root) - if not os.path.isdir(dev_root): - logger.error(f'Dev root {dev_root} is not a folder.') + + if destination_restricted_name and destination_restricted_path: + logger.error(f'Destination Restricted Name and Destination Restricted Path provided, these are mutually' + f' exclusive.') return 1 - # if no destination_path error - if not destination_path: - logger.error('Dst path cannot be empty.') + if template_restricted_name and template_restricted_path: + logger.error(f'Template Restricted Name and Template Restricted Path provided, these are mutually exclusive.') return 1 - destination_path = destination_path.replace('\\', '/') - if not os.path.isabs(destination_path): - destination_path = f'{dev_root}/{destination_path}' - # dst name is now the last component of the destination_path - destination_name = os.path.basename(destination_path) + # need either the template name or path + if not template_path and not template_name: + logger.error(f'Template Name or Template Path must be specified.') + return 1 - # destination name cannot be the same as a restricted platform name - if destination_name in restricted_platforms: - logger.error(f'Destination path cannot be a restricted name. {destination_name}') + if template_name: + template_path = registration.get_registered(template_name=template_name) + + if not os.path.isdir(template_path): + logger.error(f'Could not find the template {template_name}=>{template_path}') return 1 - # if no template_path error - if not template_path: - logger.error('Template path cannot be empty.') + # template folder name is now the last component of the template_path + template_folder_name = os.path.basename(template_path) + + # the template.json should be in the template_path, make sure it's there a nd valid + template_json = f'{template_path}/template.json' + if not registration.valid_o3de_template_json(template_json): + logger.error(f'Template json {template_path} is invalid.') return 1 - template_path = template_path.replace('\\', '/') - if not os.path.isabs(template_path): - template_path = f'{dev_root}/Templates/{template_path}' - if not os.path.isdir(template_path): - logger.error(f'Could not find the template {template_path}') + + # read in the template.json + with open(template_json) as s: + try: + template_json_data = json.load(s) + except Exception as e: + logger.error(f'Could read template json {template_json}: {str(e)}.') + return 1 + + # read template name from the json + try: + template_name = template_json_data['template_name'] + except Exception as e: + logger.error(f'Could not read "template_name" from template json {template_json}: {str(e)}.') return 1 - # template name is now the last component of the template_path - template_name = os.path.basename(template_path) + # if the user has not specified either a restricted name or restricted path + # see if the template itself specifies a restricted name + if not template_restricted_name and not template_restricted_path: + try: + template_json_restricted_name = template_json_data['restricted'] + except Exception as e: + # the template json doesn't have a 'restricted' element warn and use it + logger.info(f'The template does not specify a "restricted".') + else: + template_restricted_name = template_json_restricted_name + + # if no restricted name or path we continue on as if there is no template restricted files. + if template_restricted_name or template_restricted_path: + # If the user specified a --template-restricted-name we need to check that against the templates + # 'restricted' if it has one and see if they match. If they match then we don't have a problem. + # If they don't then we error out. If supplied but not present in the template we warn and use it. + # If not supplied we set what's in the template. If not supplied and not in the template we continue + # on as if there is no template restricted files. + if template_restricted_name: + # The user specified a --template-restricted-name + try: + template_json_restricted_name = template_json_data['restricted'] + except Exception as e: + # the template json doesn't have a 'restricted' element warn and use it + logger.info(f'The template does not specify a "restricted".' + f' Using supplied {template_restricted_name}') + else: + if template_json_restricted_name != template_restricted_name: + logger.error( + f'The supplied --template-restricted-name {template_restricted_name} does not match the' + f' templates "restricted". Either the the --template-restricted-name is incorrect or the' + f' templates "restricted" is wrong. Note that since this template specifies "restricted" as' + f' {template_json_restricted_name}, --template-restricted-name need not be supplied.') + + template_restricted_path = registration.get_registered(restricted_name=template_restricted_name) + else: + # The user has supplied the --template-restricted-path, see if that matches the template specifies. + # If it does then we do not have a problem. If it doesn't match then error out. If not specified + # in the template then warn and use the --template-restricted-path + template_restricted_path = template_restricted_path.replace('\\', '/') + try: + template_json_restricted_name = template_json_data['restricted'] + except Exception as e: + # the template json doesn't have a 'restricted' element warn and use it + logger.info(f'The template does not specify a "restricted".' + f' Using supplied {template_restricted_path}') + else: + template_json_restricted_path = registration.get_registered( + restricted_name=template_json_restricted_name) + if template_json_restricted_path != template_restricted_path: + logger.error( + f'The supplied --template-restricted-path {template_restricted_path} does not match the' + f' templates "restricted" {template_restricted_name} => {template_json_restricted_path}.' + f' Either the the supplied --template-restricted-path is incorrect or the templates' + f' "restricted" is wrong. Note that since this template specifies "restricted" as' + f' {template_json_restricted_name} --template-restricted-path need not be supplied' + f' and {template_json_restricted_path} will be used.') + return 1 + + # check and make sure the restricted exists + if not os.path.isdir(template_restricted_path): + logger.error(f'Template restricted path {template_restricted_path} does not exist.') + return 1 - # destination_restricted_path - destination_restricted_path = destination_restricted_path.replace('\\', '/') - if not os.path.isabs(destination_restricted_path): - destination_restricted_path = f'{dev_root}/{destination_restricted_path}' - if not os.path.isdir(destination_restricted_path): - os.makedirs(destination_restricted_path) + # If the user specified a --template-restricted-platform-relative-path we need to check that against + # the templates 'restricted_platform_relative_path' and see if they match. If they match we don't have + # a problem. If they don't match then we error out. If supplied but not present in the template we warn + # and use --template-restricted-platform-relative-path. If not supplied we set what's in the template. + # If not supplied and not in the template set empty string. + if template_restricted_platform_relative_path: + # The user specified a --template-restricted-platform-relative-path + template_restricted_platform_relative_path = template_restricted_platform_relative_path.replace( + '\\', '/') + try: + template_json_restricted_platform_relative_path = template_json_data[ + 'restricted_platform_relative_path'] + except Exception as e: + # the template json doesn't have a 'restricted_platform_relative_path' element warn and use it + logger.info(f'The template does not specify a "restricted_platform_relative_path".' + f' Using {template_restricted_platform_relative_path}') + else: + # the template has a 'restricted_platform_relative_path', if it matches we are fine, if not + # something is wrong with either the --template-restricted-platform-relative or the template is. + if template_restricted_platform_relative_path != template_json_restricted_platform_relative_path: + logger.error(f'The supplied --template-restricted-platform-relative-path does not match the' + f' templates "restricted_platform_relative_path". Either' + f' --template-restricted-platform-relative-path is incorrect or the templates' + f' "restricted_platform_relative_path" is wrong. Note that since this template' + f' specifies "restricted_platform_relative_path" it need not be supplied and' + f' {template_json_restricted_platform_relative_path} will be used.') + return 1 + else: + # The user has not supplied --template-restricted-platform-relative-path, try to read it from + # the template json. + try: + template_restricted_platform_relative_path = template_json_data[ + 'restricted_platform_relative_path'] + except Exception as e: + # The template json doesn't have a 'restricted_platform_relative_path' element, set empty string. + template_restricted_platform_relative_path = '' - # template_restricted_path - template_restricted_path = template_restricted_path.replace('\\', '/') - if not os.path.isabs(template_restricted_path): - template_restricted_path = f'{dev_root}/{template_restricted_path}' - if not os.path.isdir(template_restricted_path): - logger.error(f'Template restricted path {template_restricted_path} is not a folder.') + if not template_restricted_platform_relative_path: + template_restricted_platform_relative_path = '' + + # if no destination_path, error + if not destination_path: + logger.error('Destination path cannot be empty.') + return 1 + destination_path = destination_path.replace('\\', '/') + if os.path.isdir(destination_path): + logger.error(f'Destination path {destination_path} already exists.') return 1 + else: + os.makedirs(destination_path) - # destination restricted relative - destination_restricted_platform_relative_path = destination_restricted_platform_relative_path.replace('\\', '/') + # destination name is now the last component of the destination_path + destination_name = os.path.basename(destination_path) - # template restricted relative - template_restricted_platform_relative_path = template_restricted_platform_relative_path.replace('\\', '/') + # destination name cannot be the same as a restricted platform name + if destination_name in restricted_platforms: + logger.error(f'Destination path cannot be a restricted name. {destination_name}') + return 1 + + # destination restricted name + if destination_restricted_name: + destination_restricted_path = registration.get_registered(restricted_name=destination_restricted_name) + + # destination restricted path + elif destination_restricted_path: + destination_restricted_path = destination_restricted_path.replace('\\', '/') + if os.path.isabs(destination_restricted_path): + restricted_default_path = registration.get_registered(default='restricted') + new_destination_restricted_path = f'{restricted_default_path}/{destination_restricted_path}' + logger.info(f'{destination_restricted_path} is not a full path, making it relative' + f' to default restricted path = {new_destination_restricted_path}') + destination_restricted_path = new_destination_restricted_path + elif template_restricted_path: + restricted_default_path = registration.get_registered(default='restricted') + logger.info(f'--destination-restricted-path is not specified, using default restricted path / destination name' + f' = {restricted_default_path}') + destination_restricted_path = restricted_default_path + + # destination restricted relative + if destination_restricted_platform_relative_path: + destination_restricted_platform_relative_path = destination_restricted_platform_relative_path.replace('\\', '/') + else: + destination_restricted_platform_relative_path = '' # any user supplied replacements replacements = list() @@ -1043,23 +1238,56 @@ def create_from_template(dev_root: str, replacements.append(("${NameUpper}", destination_name.upper())) replacements.append(("${NameLower}", destination_name.lower())) - return _instantiate_template(destination_name, - template_name, - destination_path, - template_path, - template_restricted_path, - replacements, - keep_restricted_in_instance, - keep_license_text) - - -def create_project(dev_root: str, - project_path: str, - template_path: str, - project_restricted_path: str = "restricted", - template_restricted_path: str = "restricted", - project_restricted_platform_relative_path: str or None = '', - template_restricted_platform_relative_path: str = "Templates", + if _instantiate_template(template_json_data, + destination_name, + template_name, + destination_path, + template_path, + destination_restricted_path, + template_restricted_path, + destination_restricted_platform_relative_path, + template_restricted_platform_relative_path, + replacements, + keep_restricted_in_instance, + keep_license_text): + logger.error(f'Instantiation of the template has failed.') + return 1 + + # We created the destination, now do anything extra that a destination requires + + # If we are not keeping the restricted in the destination read the destination restricted this might be a new + # restricted folder, so make sure the restricted has this destinations name + # Note there is no linking of non o3de objects to o3de restricted. So this will make no attempt to figure out + # if this destination was actually an o3de object and try to alter the .json + if not keep_restricted_in_instance: + if destination_restricted_path: + os.makedirs(destination_restricted_path, exist_ok=True) + + # read the restricted_name from the destination restricted.json + restricted_json = f"{destination_restricted_path}/restricted.json".replace('//', '/') + if not os.path.isfile(restricted_json): + with open(restricted_json, 'w') as s: + restricted_json_data = {} + restricted_json_data.update({'restricted_name': destination_name}) + s.write(json.dumps(restricted_json_data, indent=4)) + + logger.warning(f'Instantiation successful. NOTE: This is a generic instantiation of the template. If this' + f' was a template of an o3de object like a project, gem, template, etc. then you should have used' + f' specialization that knows how to link that object type via its project.json or gem.json, etc.' + f' Create from template is meant only to instance a template of a non o3de object.') + + return 0 + + +def create_project(project_path: str, + template_path: str = None, + template_name: str = None, + project_restricted_path: str = None, + project_restricted_name: str = None, + template_restricted_path: str = None, + template_restricted_name: str = None, + project_restricted_platform_relative_path: str = None, + template_restricted_platform_relative_path: str = None, keep_restricted_in_project: bool = False, keep_license_text: bool = False, replace: list = None, @@ -1067,61 +1295,196 @@ def create_project(dev_root: str, editor_system_component_class_id: str = None, module_id: str = None) -> int: """ - Template instantiation that make all default assumptions for a Project template instantiation, reducing the effort - needed in instancing a project - :param dev_root: the path to the dev root of the engine - :param project_path: the project path, can be absolute or dev_root relative - :param template_path: the path to the template you want to instance, can be abs or relative, - defaults to DefaultProject - :param project_restricted_path: path to the projects restricted folder - :param template_restricted_path: path to the templates restricted folder - :param project_restricted_platform_relative_path: path to append to the project-restricted-path - :param template_restricted_platform_relative_path: path to append to the template_restricted_path - :param keep_restricted_in_project: whether or not you want ot keep the templates restricted files in your project or + Template instantiation specialization that makes all default assumptions for a Project template instantiation, + reducing the effort needed in instancing a project + :param project_path: the project path, can be absolute or relative to default projects path + :param template_path: the path to the template you want to instance, can be absolute or relative to default templates path + :param template_name: the name the registered template you want to instance, defaults to DefaultProject, resolves template_path + :param project_restricted_path: path to the projects restricted folder, can be absolute or relative to the restricted='projects' + :param project_restricted_name: name of the registered projects restricted path, resolves project_restricted_path + :param template_restricted_path: templates restricted path can be absolute or relative to restricted='templates' + :param template_restricted_name: name of the registered templates restricted path, resolves template_restricted_path + :param project_restricted_platform_relative_path: any path after the platform to append to the project_restricted_path + :param template_restricted_platform_relative_path: any path after the platform to append to the template_restricted_path + :param keep_restricted_in_project: whether or not you want to keep the templates restricted files in your project or separate them out into the restricted folder - :param keep_license_text: whether or not you want ot keep the templates license text in your instance. + :param keep_license_text: whether or not you want to keep the templates license text in your instance. template can have license blocks starting with {BEGIN_LICENSE} and ending with {END_LICENSE}, this controls if you want to keep the license text from the template in the new instance. It is false by default - because most customers will not want license text in there instances, but we may want to keep them. + because most customers will not want license text in their instances, but we may want to keep them. :param replace: optional list of strings uses to make concrete names out of templated parameters. X->Y pairs Ex. ${Name},TestGem,${Player},TestGemPlayer This will cause all references to ${Name} be replaced by TestGem, and all ${Player} replaced by 'TestGemPlayer' :param system_component_class_id: optionally specify a uuid for the system component class, default is random uuid - :param editor_system_component_class_id: optionally specify a uuid for the editor system component class, default is random uuid + :param editor_system_component_class_id: optionally specify a uuid for the editor system component class, default is + random uuid :param module_id: optionally specify a uuid for the module class, default is random uuid :return: 0 for success or non 0 failure code """ - # if no dev root error - if not dev_root: - logger.error('Dev root cannot be empty.') + if template_name and template_path: + logger.error(f'Template Name and Template Path provided, these are mutually exclusive.') return 1 - dev_root = dev_root.replace('\\', '/') - if not os.path.isabs(dev_root): - dev_root = os.path.abspath(dev_root) - if not os.path.isdir(dev_root): - logger.error(f'Dev root {dev_root} is not a folder.') + + if project_restricted_name and project_restricted_path: + logger.error(f'Project Restricted Name and Project Restricted Path provided, these are mutually exclusive.') return 1 - if not template_path: - logger.error('Template path cannot be empty.') + if template_restricted_name and template_restricted_path: + logger.error(f'Template Restricted Name and Template Restricted Path provided, these are mutually exclusive.') return 1 - template_path = template_path.replace('\\', '/') - if not os.path.isabs(template_path): - template_path = f'{dev_root}/Templates/{template_path}' + + if not template_path and not template_name: + template_name = 'DefaultProject' + + if template_name and not template_path: + template_path = registration.get_registered(template_name=template_name) + if not os.path.isdir(template_path): - logger.error(f'Could not find the template {template_path}') + logger.error(f'Could not find the template {template_name}=>{template_path}') return 1 - # template name is now the last component of the template_path - template_name = os.path.basename(template_path) + # template folder name is now the last component of the template_path + template_folder_name = os.path.basename(template_path) + + # the template.json should be in the template_path, make sure it's there and valid + template_json = f'{template_path}/template.json' + if not registration.valid_o3de_template_json(template_json): + logger.error(f'Template json {template_path} is not valid.') + return 1 + + # read in the template.json + with open(template_json) as s: + try: + template_json_data = json.load(s) + except Exception as e: + logger.error(f'Could read template json {template_json}: {str(e)}.') + return 1 + + # read template name from the json + try: + template_name = template_json_data['template_name'] + except Exception as e: + logger.error(f'Could not read "template_name" from template json {template_json}: {str(e)}.') + return 1 - # project path + # if the user has not specified either a restricted name or restricted path + # see if the template itself specifies a restricted name + if not template_restricted_name and not template_restricted_path: + try: + template_json_restricted_name = template_json_data['restricted'] + except Exception as e: + # the template json doesn't have a 'restricted' element warn and use it + logger.info(f'The template does not specify a "restricted".') + else: + template_restricted_name = template_json_restricted_name + + # if no restricted name or path we continue on as if there is no template restricted files. + if template_restricted_name or template_restricted_path: + # If the user specified a --template-restricted-name we need to check that against the templates + # 'restricted' if it has one and see if they match. If they match then we don't have a problem. + # If they don't then we error out. If supplied but not present in the template we warn and use it. + # If not supplied we set what's in the template. If not supplied and not in the template we continue + # on as if there is no template restricted files. + if template_restricted_name and not template_restricted_path: + # The user specified a --template-restricted-name + try: + template_json_restricted_name = template_json_data['restricted'] + except Exception as e: + # the template json doesn't have a 'restricted' element warn and use it + logger.info(f'The template does not specify a "restricted".' + f' Using supplied {template_restricted_name}') + else: + if template_json_restricted_name != template_restricted_name: + logger.error( + f'The supplied --template-restricted-name {template_restricted_name} does not match the' + f' templates "restricted". Either the the --template-restricted-name is incorrect or the' + f' templates "restricted" is wrong. Note that since this template specifies "restricted" as' + f' {template_json_restricted_name}, --template-restricted-name need not be supplied.') + + template_restricted_path = registration.get_registered(restricted_name=template_restricted_name) + else: + # The user has supplied the --template-restricted-path, see if that matches the template specifies. + # If it does then we do not have a problem. If it doesn't match then error out. If not specified + # in the template then warn and use the --template-restricted-path + template_restricted_path = template_restricted_path.replace('\\', '/') + try: + template_json_restricted_name = template_json_data['restricted'] + except Exception as e: + # the template json doesn't have a 'restricted' element warn and use it + logger.info(f'The template does not specify a "restricted".' + f' Using supplied {template_restricted_path}') + else: + template_json_restricted_path = registration.get_registered( + restricted_name=template_json_restricted_name) + if template_json_restricted_path != template_restricted_path: + logger.error( + f'The supplied --template-restricted-path {template_restricted_path} does not match the' + f' templates "restricted" {template_restricted_name} => {template_json_restricted_path}.' + f' Either the the supplied --template-restricted-path is incorrect or the templates' + f' "restricted" is wrong. Note that since this template specifies "restricted" as' + f' {template_json_restricted_name} --template-restricted-path need not be supplied' + f' and {template_json_restricted_path} will be used.') + return 1 + + # check and make sure the restricted exists + if not os.path.isdir(template_restricted_path): + logger.error(f'Template restricted path {template_restricted_path} does not exist.') + return 1 + + # If the user specified a --template-restricted-platform-relative-path we need to check that against + # the templates 'restricted_platform_relative_path' and see if they match. If they match we don't have + # a problem. If they don't match then we error out. If supplied but not present in the template we warn + # and use --template-restricted-platform-relative-path. If not supplied we set what's in the template. + # If not supplied and not in the template set empty string. + if template_restricted_platform_relative_path: + # The user specified a --template-restricted-platform-relative-path + template_restricted_platform_relative_path = template_restricted_platform_relative_path.replace('\\', '/') + try: + template_json_restricted_platform_relative_path = template_json_data[ + 'restricted_platform_relative_path'] + except Exception as e: + # the template json doesn't have a 'restricted_platform_relative_path' element warn and use it + logger.info(f'The template does not specify a "restricted_platform_relative_path".' + f' Using {template_restricted_platform_relative_path}') + else: + # the template has a 'restricted_platform_relative_path', if it matches we are fine, if not + # something is wrong with either the --template-restricted-platform-relative or the template is. + if template_restricted_platform_relative_path != template_json_restricted_platform_relative_path: + logger.error(f'The supplied --template-restricted-platform-relative-path does not match the' + f' templates "restricted_platform_relative_path". Either' + f' --template-restricted-platform-relative-path is incorrect or the templates' + f' "restricted_platform_relative_path" is wrong. Note that since this template' + f' specifies "restricted_platform_relative_path" it need not be supplied and' + f' {template_json_restricted_platform_relative_path} will be used.') + return 1 + else: + # The user has not supplied --template-restricted-platform-relative-path, try to read it from + # the template json. + try: + template_restricted_platform_relative_path = template_json_data[ + 'restricted_platform_relative_path'] + except Exception as e: + # The template json doesn't have a 'restricted_platform_relative_path' element, set empty string. + template_restricted_platform_relative_path = '' + if not template_restricted_platform_relative_path: + template_restricted_platform_relative_path = '' + + # if no project path, error if not project_path: logger.error('Project path cannot be empty.') return 1 project_path = project_path.replace('\\', '/') if not os.path.isabs(project_path): - project_path = f'{dev_root}/{project_path}' + default_projects_folder = registration.get_registered(default_folder='projects') + new_project_path = f'{default_projects_folder}/{project_path}' + logger.info(f'Project Path {project_path} is not a full path, we must assume its relative' + f' to default projects path = {new_project_path}') + project_path = new_project_path + if os.path.isdir(project_path): + logger.error(f'Project path {project_path} already exists.') + return 1 + else: + os.makedirs(project_path) # project name is now the last component of the project_path project_name = os.path.basename(project_path) @@ -1131,26 +1494,30 @@ def create_project(dev_root: str, logger.error(f'Project path cannot be a restricted name. {project_name}') return 1 - # project_restricted_path - project_restricted_path = project_restricted_path.replace('\\', '/') - if not os.path.isabs(project_restricted_path): - project_restricted_path = f'{dev_root}/{project_restricted_path}' - if not os.path.isdir(project_restricted_path): - os.makedirs(project_restricted_path) - - # template_restricted_path - template_restricted_path = template_restricted_path.replace('\\', '/') - if not os.path.isabs(template_restricted_path): - template_restricted_path = f'{dev_root}/{template_restricted_path}' - if not os.path.isdir(template_restricted_path): - logger.error(f'Template restricted path {template_restricted_path} is not a folder.') - return 1 - - # project restricted relative - project_restricted_platform_relative_path = project_restricted_platform_relative_path.replace('\\', '/') - - # template restricted relative - template_restricted_platform_relative_path = template_restricted_platform_relative_path.replace('\\', '/') + # project restricted name + if project_restricted_name and not project_restricted_path: + project_restricted_path = registration.get_registered(restricted_name=project_restricted_name) + + # project restricted path + elif project_restricted_path: + project_restricted_path = project_restricted_path.replace('\\', '/') + if not os.path.isabs(project_restricted_path): + default_projects_restricted_folder = registration.get_registered(restricted_name='projects') + new_project_restricted_path = f'{default_projects_restricted_folder}/{project_restricted_path}' + logger.info(f'Project restricted path {project_restricted_path} is not a full path, we must assume its' + f' relative to default projects restricted path = {new_project_restricted_path}') + project_restricted_path = new_project_restricted_path + elif template_restricted_path: + project_restricted_default_path = registration.get_registered(restricted_name='projects') + logger.info(f'--project-restricted-path is not specified, using default project restricted path / project name' + f' = {project_restricted_default_path}') + project_restricted_path = project_restricted_default_path + + # project restricted relative path + if project_restricted_platform_relative_path: + project_restricted_platform_relative_path = project_restricted_platform_relative_path.replace('\\', '/') + else: + project_restricted_platform_relative_path = '' # any user supplied replacements replacements = list() @@ -1185,14 +1552,15 @@ def create_project(dev_root: str, if editor_system_component_class_id: if '{' not in editor_system_component_class_id or '-' not in editor_system_component_class_id: logger.error( - f'Editor System component class id {editor_system_component_class_id} is malformed. Should look like Ex.' + - '{b60c92eb-3139-454b-a917-a9d3c5819594}') + f'Editor System component class id {editor_system_component_class_id} is malformed. Should look like' + f' Ex.' + '{b60c92eb-3139-454b-a917-a9d3c5819594}') return 1 replacements.append(("${EditorSysCompClassId}", editor_system_component_class_id)) else: replacements.append(("${EditorSysCompClassId}", '{' + str(uuid.uuid4()) + '}')) - if _instantiate_template(project_name, + if _instantiate_template(template_json_data, + project_name, template_name, project_path, template_path, @@ -1202,14 +1570,65 @@ def create_project(dev_root: str, template_restricted_platform_relative_path, replacements, keep_restricted_in_project, - keep_license_text) == 0: + keep_license_text): + logger.error(f'Instantiation of the template has failed.') + return 1 + + # We created the project, now do anything extra that a project requires - # we created the project, now do anything extra that a project requires + # If we are not keeping the restricted in the project read the name of the restricted folder from the + # restricted json and set that as this projects restricted + if not keep_restricted_in_project: + if project_restricted_path: + os.makedirs(project_restricted_path, exist_ok=True) + + # read the restricted_name from the projects restricted.json + restricted_json = f"{project_restricted_path}/restricted.json".replace('//', '/') + if os.path.isfile(restricted_json): + if not registration.valid_o3de_restricted_json(restricted_json): + logger.error(f'Restricted json {restricted_json} is not valid.') + return 1 + else: + with open(restricted_json, 'w') as s: + restricted_json_data = {} + restricted_json_data.update({'restricted_name': project_name}) + s.write(json.dumps(restricted_json_data, indent=4)) + + with open(restricted_json, 'r') as s: + try: + restricted_json_data = json.load(s) + except Exception as e: + logger.error(f'Failed to load restricted json {restricted_json}.') + return 1 + + try: + restricted_name = restricted_json_data["restricted_name"] + except Exception as e: + logger.error(f'Failed to read "restricted_name" from restricted json {restricted_json}.') + return 1 + + # set the "restricted": "restricted_name" element of the project.json + project_json = f"{project_path}/project.json".replace('//', '/') + if not registration.valid_o3de_project_json(project_json): + logger.error(f'Project json {project_json} is not valid.') + return 1 + + with open(project_json, 'r') as s: + try: + project_json_data = json.load(s) + except Exception as e: + logger.error(f'Failed to load project json {project_json}.') + return 1 + + project_json_data.update({"restricted": restricted_name}) + os.unlink(project_json) + with open(project_json, 'w') as s: + try: + s.write(json.dumps(project_json_data, indent=4)) + except Exception as e: + logger.error(f'Failed to write project json {project_json}.') + return 1 - # If we do not keep the restricted folders in the project then we have to make sure - # the restricted project folder has a CMakeLists.txt file in it so that when the restricted - # folders CMakeLists.txt is executed it will find a CMakeLists.txt in the restricted project root - if not keep_restricted_in_project: for restricted_platform in restricted_platforms: restricted_project = f'{project_restricted_path}/{restricted_platform}/{project_name}' os.makedirs(restricted_project, exist_ok=True) @@ -1218,25 +1637,38 @@ def create_project(dev_root: str, with open(cmakelists_file_name, 'w') as d: if keep_license_text: d.write('# {BEGIN_LICENSE}\n') - d.write('# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or\n') + d.write('# All or portions of this file Copyright (c) Amazon.com, Inc. or its' + ' affiliates or\n') d.write('# its licensors.\n') d.write('#\n') - d.write('# For complete copyright and license terms please see the LICENSE at the root of this\n') - d.write('# distribution (the "License"). All use of this software is governed by the License,\n') - d.write('# or, if provided, by the license below or the license accompanying this file. Do not\n') - d.write('# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,\n') + d.write('# For complete copyright and license terms please see the LICENSE at the' + ' root of this\n') + d.write('# distribution (the "License"). All use of this software is governed by' + ' the License,\n') + d.write('# or, if provided, by the license below or the license accompanying this' + ' file. Do not\n') + d.write('# remove or modify any license notices. This file is distributed on an' + ' "AS IS" BASIS,\n') d.write('# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n') d.write('# {END_LICENSE}\n') + + # copy the o3de_manifest.cmake into the project root + engine_path = registration.get_this_engine_path() + o3de_manifest_cmake = f'{engine_path}/cmake/o3de_manifest.cmake' + shutil.copy(o3de_manifest_cmake, project_path) + return 0 -def create_gem(dev_root: str, - gem_path: str, - template_path: str, - gem_restricted_path: str, - template_restricted_path: str, - gem_restricted_platform_relative_path: str or None, - template_restricted_platform_relative_path: str, +def create_gem(gem_path: str, + template_path: str = None, + template_name: str = None, + gem_restricted_path: str = None, + gem_restricted_name: str = None, + template_restricted_path: str = None, + template_restricted_name: str = None, + gem_restricted_platform_relative_path: str = None, + template_restricted_platform_relative_path: str = None, keep_restricted_in_gem: bool = False, keep_license_text: bool = False, replace: list = None, @@ -1244,46 +1676,194 @@ def create_gem(dev_root: str, editor_system_component_class_id: str = None, module_id: str = None) -> int: """ - Template instantiation that make all default assumptions for a Gem template instantiation, reducing the effort - needed in instancing a gem - :param dev_root: the path to the dev root of the engine - :param gem_path: the gem path, can be absolute or dev_root/Gems relative - :param gem_restricted_path: the gem restricted path, can be absolute or dev_root/Gems relative - :param template_path: the template path you want to instance, abs or dev_root/Templates relative, - defaults to DefaultGem - :param template_restricted_path: path to the templates restricted folder - :param keep_restricted_in_gem: whether or not you want ot keep the templates restricted files in your instance or - seperate them out into the restricted folder - :param keep_license_text: whether or not you want ot keep the templates license text in your instance. template can + Template instantiation specialization that makes all default assumptions for a Gem template instantiation, + reducing the effort needed in instancing a gem + :param gem_path: the gem path, can be absolute or relative to default gems path + :param template_path: the template path you want to instance, can be absolute or relative to default templates path + :param template_name: the name of the registered template you want to instance, defaults to DefaultGem, resolves template_path + :param gem_restricted_path: path to the gems restricted folder, can be absolute or relative to the restricted='gems' + :param gem_restricted_name: str = name of the registered gems restricted path, resolves gem_restricted_path + :param template_restricted_path: the templates restricted path, can be absolute or relative to the restricted='templates' + :param template_restricted_name: name of the registered templates restricted path, resolves template_restricted_path + :param gem_restricted_platform_relative_path: any path after the platform to append to the gem_restricted_path + :param template_restricted_platform_relative_path: any path after the platform to append to the template_restricted_path + :param keep_restricted_in_gem: whether or not you want to keep the templates restricted files in your instance or + separate them out into the restricted folder + :param keep_license_text: whether or not you want to keep the templates license text in your instance. template can have license blocks starting with {BEGIN_LICENSE} and ending with {END_LICENSE}, this controls if you want to keep the license text from the template in the new instance. It is false by default because most customers will not - want license text in there instances, but we may want to keep them. + want license text in their instances, but we may want to keep them. :param replace: optional list of strings uses to make concrete names out of templated parameters. X->Y pairs Ex. ${Name},TestGem,${Player},TestGemPlayer This will cause all references to ${Name} be replaced by TestGem, and all ${Player} replaced by 'TestGemPlayer' :param system_component_class_id: optionally specify a uuid for the system component class, default is random uuid - :param editor_system_component_class_id: optionally specify a uuid for the editor system component class, default is random uuid + :param editor_system_component_class_id: optionally specify a uuid for the editor system component class, default is + random uuid :param module_id: optionally specify a uuid for the module class, default is random uuid :return: 0 for success or non 0 failure code """ - # if no dev root error - if not dev_root: - logger.error('Dev root cannot be empty.') + if template_name and template_path: + logger.error(f'Template Name and Template Path provided, these are mutually exclusive.') + return 1 + + if gem_restricted_name and gem_restricted_path: + logger.error(f'Gem Restricted Name and Gem Restricted Path provided, these are mutually exclusive.') + return 1 + + if template_restricted_name and template_restricted_path: + logger.error(f'Template Restricted Name and Template Restricted Path provided, these are mutually exclusive.') + return 1 + + if not template_name and not template_path: + template_name = 'DefaultGem' + + if template_name and not template_path: + template_path = registration.get_registered(template_name=template_name) + + if not os.path.isdir(template_path): + logger.error(f'Could not find the template {template_name}=>{template_path}') + return 1 + + # template name is now the last component of the template_path + template_folder_name = os.path.basename(template_path) + + # the template.json should be in the template_path, make sure it's there and valid + template_json = f'{template_path}/template.json' + if not registration.valid_o3de_template_json(template_json): + logger.error(f'Template json {template_path} is not valid.') return 1 - dev_root = dev_root.replace('\\', '/') - if not os.path.isabs(dev_root): - dev_root = os.path.abspath(dev_root) - if not os.path.isdir(dev_root): - logger.error(f'Dev root {dev_root} is not a folder.') + + # read in the template.json + with open(template_json) as s: + try: + template_json_data = json.load(s) + except Exception as e: + logger.error(f'Could read template json {template_json}: {str(e)}.') + return 1 + + # read template name from the json + try: + template_name = template_json_data['template_name'] + except Exception as e: + logger.error(f'Could not read "template_name" from template json {template_json}: {str(e)}.') return 1 - # if no destination_path error + # if the user has not specified either a restricted name or restricted path + # see if the template itself specifies a restricted name + if not template_restricted_name and not template_restricted_path: + try: + template_json_restricted_name = template_json_data['restricted'] + except Exception as e: + # the template json doesn't have a 'restricted' element warn and use it + logger.info(f'The template does not specify a "restricted".') + else: + template_restricted_name = template_json_restricted_name + + # if no restricted name or path we continue on as if there is no template restricted files. + if template_restricted_name or template_restricted_path: + # if the user specified a --template-restricted-name we need to check that against the templates 'restricted' + # if it has one and see if they match. If they match then we don't have a problem. If they don't then we error + # out. If supplied but not present in the template we warn and use it. If not supplied we set what's in the + # template. If not supplied and not in the template we continue on as if there is no template restricted files. + if template_restricted_name and not template_restricted_path: + # The user specified a --template-restricted-name + try: + template_json_restricted_name = template_json_data['restricted'] + except Exception as e: + # the template json doesn't have a 'restricted' element warn and use it + logger.info(f'The template does not specify a "restricted".' + f' Using supplied {template_restricted_name}') + else: + if template_json_restricted_name != template_restricted_name: + logger.error( + f'The supplied --template-restricted-name {template_restricted_name} does not match the' + f' templates "restricted". Either the the --template-restricted-name is incorrect or the' + f' templates "restricted" is wrong. Note that since this template specifies "restricted" as' + f' {template_json_restricted_name}, --template-restricted-name need not be supplied.') + + template_restricted_path = registration.get_registered(restricted_name=template_restricted_name) + else: + # The user has supplied the --template-restricted-path, see if that matches the template specifies. + # If it does then we do not have a problem. If it doesn't match then error out. If not specified + # in the template then warn and use the --template-restricted-path + template_restricted_path = template_restricted_path.replace('\\', '/') + try: + template_json_restricted_name = template_json_data['restricted'] + except Exception as e: + # the template json doesn't have a 'restricted' element warn and use it + logger.info(f'The template does not specify a "restricted".' + f' Using supplied {template_restricted_path}') + else: + template_json_restricted_path = registration.get_registered( + restricted_name=template_json_restricted_name) + if template_json_restricted_path != template_restricted_path: + logger.error( + f'The supplied --template-restricted-path {template_restricted_path} does not match the' + f' templates "restricted" {template_restricted_name} => {template_json_restricted_path}.' + f' Either the the supplied --template-restricted-path is incorrect or the templates' + f' "restricted" is wrong. Note that since this template specifies "restricted" as' + f' {template_json_restricted_name} --template-restricted-path need not be supplied' + f' and {template_json_restricted_path} will be used.') + return 1 + # check and make sure the restricted path exists + if not os.path.isdir(template_restricted_path): + logger.error(f'Template restricted path {template_restricted_path} does not exist.') + return 1 + + # If the user specified a --template-restricted-platform-relative-path we need to check that against + # the templates 'restricted_platform_relative_path' and see if they match. If they match we don't have + # a problem. If they don't match then we error out. If supplied but not present in the template we warn + # and use --template-restricted-platform-relative-path. If not supplied we set what's in the template. + # If not supplied and not in the template set empty string. + if template_restricted_platform_relative_path: + # The user specified a --template-restricted-platform-relative-path + template_restricted_platform_relative_path = template_restricted_platform_relative_path.replace('\\', '/') + try: + template_json_restricted_platform_relative_path = template_json_data[ + 'restricted_platform_relative_path'] + except Exception as e: + # the template json doesn't have a 'restricted_platform_relative_path' element warn and use it + logger.info(f'The template does not specify a "restricted_platform_relative_path".' + f' Using {template_restricted_platform_relative_path}') + else: + # the template has a 'restricted_platform_relative_path', if it matches we are fine, if not something is + # wrong with either the --template-restricted-platform-relative or the template is + if template_restricted_platform_relative_path != template_json_restricted_platform_relative_path: + logger.error(f'The supplied --template-restricted-platform-relative-path does not match the' + f' templates "restricted_platform_relative_path". Either' + f' --template-restricted-platform-relative-path is incorrect or the templates' + f' "restricted_platform_relative_path" is wrong. Note that since this template' + f' specifies "restricted_platform_relative_path" it need not be supplied and' + f' {template_json_restricted_platform_relative_path} will be used.') + return 1 + else: + # The user has not supplied --template-restricted-platform-relative-path, try to read it from + # the template json. + try: + template_restricted_platform_relative_path = template_json_data[ + 'restricted_platform_relative_path'] + except Exception as e: + # The template json doesn't have a 'restricted_platform_relative_path' element, set empty string. + template_restricted_platform_relative_path = '' + if not template_restricted_platform_relative_path: + template_restricted_platform_relative_path = '' + + # if no gem_path, error if not gem_path: logger.error('Gem path cannot be empty.') return 1 gem_path = gem_path.replace('\\', '/') if not os.path.isabs(gem_path): - gem_path = f'{dev_root}/Gems/{gem_path}' + default_gems_folder = registration.get_registered(default_folder='gems') + new_gem_path = f'{default_gems_folder}/{gem_path}' + logger.info(f'Gem Path {gem_path} is not a full path, we must assume its relative' + f' to default gems path = {new_gem_path}') + gem_path = new_gem_path + if os.path.isdir(gem_path): + logger.error(f'Gem path {gem_path} already exists.') + return 1 + else: + os.makedirs(gem_path) # gem name is now the last component of the gem_path gem_name = os.path.basename(gem_path) @@ -1293,39 +1873,30 @@ def create_gem(dev_root: str, logger.error(f'Gem path cannot be a restricted name. {gem_name}') return 1 - if not template_path: - logger.error('Template path cannot be empty.') - return 1 - template_path = template_path.replace('\\', '/') - if not os.path.isabs(template_path): - template_path = f'{dev_root}/Templates/{template_path}' - if not os.path.isdir(template_path): - logger.error(f'Could not find the template {template_path}') - return 1 - - # template name is now the last component of the template_path - template_name = os.path.basename(template_path) - - # gem_restricted_path - gem_restricted_path = gem_restricted_path.replace('\\', '/') - if not os.path.isabs(gem_restricted_path): - gem_restricted_path = f'{dev_root}/{gem_restricted_path}' - if not os.path.isdir(gem_restricted_path): - os.makedirs(gem_restricted_path) - - # template_restricted_path - template_restricted_path = template_restricted_path.replace('\\', '/') - if not os.path.isabs(template_restricted_path): - template_restricted_path = f'{dev_root}/{template_restricted_path}' - if not os.path.isdir(template_restricted_path): - logger.error(f'Template restricted path {template_restricted_path} is not a folder.') - return 1 + # gem restricted name + if gem_restricted_name and not gem_restricted_path: + gem_restricted_path = registration.get_registered(restricted_name=gem_restricted_name) + + # gem restricted path + elif gem_restricted_path: + gem_restricted_path = gem_restricted_path.replace('\\', '/') + if not os.path.isabs(gem_restricted_path): + default_gems_restricted_folder = registration.get_registered(restricted_name='gems') + new_gem_restricted_path = f'{default_gems_restricted_folder}/{gem_restricted_path}' + logger.info(f'Gem restricted path {gem_restricted_path} is not a full path, we must assume its' + f' relative to default gems restricted path = {new_gem_restricted_path}') + gem_restricted_path = new_gem_restricted_path + elif template_restricted_path: + gem_restricted_default_path = registration.get_registered(restricted_name='gems') + logger.info(f'--gem-restricted-path is not specified, using default gem restricted path / gem name' + f' = {gem_restricted_default_path}') + gem_restricted_path = gem_restricted_default_path # gem restricted relative - gem_restricted_platform_relative_path = gem_restricted_platform_relative_path.replace('\\', '/') - - # template restricted relative - template_restricted_platform_relative_path = template_restricted_platform_relative_path.replace('\\', '/') + if gem_restricted_platform_relative_path: + gem_restricted_platform_relative_path = gem_restricted_platform_relative_path.replace('\\', '/') + else: + gem_restricted_platform_relative_path = '' # any user supplied replacements replacements = list() @@ -1360,32 +1931,115 @@ def create_gem(dev_root: str, if editor_system_component_class_id: if '{' not in editor_system_component_class_id or '-' not in editor_system_component_class_id: logger.error( - f'Editor System component class id {editor_system_component_class_id} is malformed. Should look like Ex.' + - '{b60c92eb-3139-454b-a917-a9d3c5819594}') + f'Editor System component class id {editor_system_component_class_id} is malformed. Should look like' + f' Ex.' + '{b60c92eb-3139-454b-a917-a9d3c5819594}') return 1 replacements.append(("${EditorSysCompClassId}", editor_system_component_class_id)) else: replacements.append(("${EditorSysCompClassId}", '{' + str(uuid.uuid4()) + '}')) - return _instantiate_template(gem_name, - template_name, - gem_path, - template_path, - gem_restricted_path, - template_restricted_path, - gem_restricted_platform_relative_path, - template_restricted_platform_relative_path, - replacements, - keep_restricted_in_gem, - keep_license_text) + if _instantiate_template(template_json_data, + gem_name, + template_name, + gem_path, + template_path, + gem_restricted_path, + template_restricted_path, + gem_restricted_platform_relative_path, + template_restricted_platform_relative_path, + replacements, + keep_restricted_in_gem, + keep_license_text): + logger.error(f'Instantiation of the template has failed.') + return 1 + + # We created the gem, now do anything extra that a gem requires + + # If we are not keeping the restricted in the gem read the name of the restricted folder from the + # restricted json and set that as this gems restricted + if not keep_restricted_in_gem: + if gem_restricted_path: + os.makedirs(gem_restricted_path, exist_ok=True) + + # read the restricted_name from the gems restricted.json + restricted_json = f"{gem_restricted_path}/restricted.json".replace('//', '/') + if os.path.isfile(restricted_json): + if not registration.valid_o3de_restricted_json(restricted_json): + logger.error(f'Restricted json {restricted_json} is not valid.') + return 1 + else: + with open(restricted_json, 'w') as s: + restricted_json_data = {} + restricted_json_data.update({'restricted_name': gem_name}) + s.write(json.dumps(restricted_json_data, indent=4)) + + with open(restricted_json, 'r') as s: + try: + restricted_json_data = json.load(s) + except Exception as e: + logger.error(f'Failed to load restricted json {restricted_json}.') + return 1 + + try: + restricted_name = restricted_json_data["restricted_name"] + except Exception as e: + logger.error(f'Failed to read "restricted_name" from restricted json {restricted_json}.') + return 1 + + # set the "restricted": "restricted_name" element of the gem.json + gem_json = f"{gem_path}/gem.json".replace('//', '/') + if not registration.valid_o3de_gem_json(gem_json): + logger.error(f'Gem json {gem_json} is not valid.') + return 1 + + with open(gem_json, 'r') as s: + try: + gem_json_data = json.load(s) + except Exception as e: + logger.error(f'Failed to load gem json {gem_json}.') + return 1 + + gem_json_data.update({"restricted": restricted_name}) + os.unlink(gem_json) + with open(gem_json, 'w') as s: + try: + s.write(json.dumps(gem_json_data, indent=4)) + except Exception as e: + logger.error(f'Failed to write project json {gem_json}.') + return 1 + + for restricted_platform in restricted_platforms: + restricted_gem = f'{gem_restricted_path}/{restricted_platform}/{gem_name}' + os.makedirs(restricted_gem, exist_ok=True) + cmakelists_file_name = f'{restricted_gem}/CMakeLists.txt' + if not os.path.isfile(cmakelists_file_name): + with open(cmakelists_file_name, 'w') as d: + if keep_license_text: + d.write('# {BEGIN_LICENSE}\n') + d.write( + '# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or\n') + d.write('# its licensors.\n') + d.write('#\n') + d.write( + '# For complete copyright and license terms please see the LICENSE at the root of this\n') + d.write( + '# distribution (the "License"). All use of this software is governed by the License,\n') + d.write( + '# or, if provided, by the license below or the license accompanying this file. Do not\n') + d.write( + '# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,\n') + d.write('# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n') + d.write('# {END_LICENSE}\n') + return 0 def _run_create_template(args: argparse) -> int: - return create_template(common.determine_engine_root(), - args.source_path, + return create_template(args.source_path, args.template_path, args.source_restricted_path, + args.source_restricted_name, args.template_restricted_path, + args.template_restricted_name, args.source_restricted_platform_relative_path, args.template_restricted_platform_relative_path, args.keep_restricted_in_template, @@ -1394,11 +2048,13 @@ def _run_create_template(args: argparse) -> int: def _run_create_from_template(args: argparse) -> int: - return create_from_template(common.determine_engine_root(), - args.destination_path, + return create_from_template(args.destination_path, args.template_path, + args.template_name, args.destination_restricted_path, + args.destination_restricted_name, args.template_restricted_path, + args.template_restricted_name, args.destination_restricted_platform_relative_path, args.template_restricted_platform_relative_path, args.keep_restricted_in_instance, @@ -1407,11 +2063,13 @@ def _run_create_from_template(args: argparse) -> int: def _run_create_project(args: argparse) -> int: - return create_project(common.determine_engine_root(), - args.project_path, + return create_project(args.project_path, args.template_path, + args.template_name, args.project_restricted_path, + args.project_restricted_name, args.template_restricted_path, + args.template_restricted_name, args.project_restricted_platform_relative_path, args.template_restricted_platform_relative_path, args.keep_restricted_in_project, @@ -1423,11 +2081,13 @@ def _run_create_project(args: argparse) -> int: def _run_create_gem(args: argparse) -> int: - return create_gem(common.determine_engine_root(), - args.gem_path, + return create_gem(args.gem_path, args.template_path, + args.template_name, args.gem_restricted_path, + args.gem_restricted_name, args.template_restricted_path, + args.template_restricted_name, args.gem_restricted_platform_relative_path, args.template_restricted_platform_relative_path, args.keep_restricted_in_gem, @@ -1452,49 +2112,55 @@ def add_args(parser, subparsers) -> None: # turn a directory into a template create_template_subparser = subparsers.add_parser('create-template') create_template_subparser.add_argument('-sp', '--source-path', type=str, required=True, - help='The path to the source that you want to make into a template,' - ' can be absolute or dev root relative.' - 'Ex. C:/o3de/Test' - 'Test = ') - create_template_subparser.add_argument('-srp', '--source-restricted-path', type=str, required=False, - default='restricted', - help='The path to the src restricted folder if any to read from, can be' - ' absolute or dev root relative, default is the dev root/restricted.') - create_template_subparser.add_argument('-srprp', '--source-restricted-platform-relative-path', type=str, required=False, - default='', + help='The path to the source that you want to make into a template') + create_template_subparser.add_argument('-tp', '--template-path', type=str, required=False, + help='The path to the template to create, can be absolute or relative' + ' to default templates path') + group = create_template_subparser.add_mutually_exclusive_group(required=True) + group.add_argument('-srp', '--source-restricted-path', type=str, required=False, + default=None, + help='The path to the source restricted folder.') + group.add_argument('-srn', '--source-restricted-name', type=str, required=False, + default=None, + help='The name of the source restricted folder. If supplied this will resolve' + ' the --source-restricted-path.') + + group = create_template_subparser.add_mutually_exclusive_group(required=True) + group.add_argument('-trp', '--template-restricted-path', type=str, required=False, + default=None, + help='The path to the templates restricted folder.') + group.add_argument('-trn', '--template-restricted-name', type=str, required=False, + default=None, + help='The name of the templates restricted folder. If supplied this will resolve' + ' the --template-restricted-path.') + + create_template_subparser.add_argument('-srprp', '--source-restricted-platform-relative-path', type=str, + required=False, + default=None, help='Any path to append to the --source-restricted-path/' - ' to where the restricted source is.' + ' to where the restricted source is. EX.' ' --source-restricted-path C:/restricted' ' --source-restricted-platform-relative-path some/folder' ' => C:/restricted//some/folder/') - create_template_subparser.add_argument('-tp', '--template-path', type=str, required=False, - help='The path to the template you wish to create,' - ' can be absolute or dev root/Templates relative, default is' - ' source_name which is the last component of source path' - ' Ex. C:/o3de/TestTemplate' - ' Test = ') - create_template_subparser.add_argument('-trp', '--template-restricted-path', type=str, required=False, - default='restricted', - help='The path to where to put the templates restricted folders write to if' - ' any, can be absolute or dev root relative, default is' - ' dev root/restricted.') create_template_subparser.add_argument('-trprp', '--template-restricted-platform-relative-path', type=str, - required=False, - default='Templates', - help='Any path to append to the --template-restricted-path/' - ' to where the restricted template source is.' - ' --template-restricted-path C:/restricted' - ' --template-restricted-platform-relative-path some/folder' - ' => C:/restricted//some/folder/') + required=False, + default=None, + help='Any path to append to the --template-restricted-path/' + ' to where the restricted template source is.' + ' --template-restricted-path C:/restricted' + ' --template-restricted-platform-relative-path some/folder' + ' => C:/restricted//some/folder/') create_template_subparser.add_argument('-kr', '--keep-restricted-in-template', action='store_true', default=False, help='Should the template keep the restricted platforms in the template, or' ' create the restricted files in the restricted folder, default is' - ' False') + ' False so it will create a restricted folder by default') create_template_subparser.add_argument('-kl', '--keep-license-text', action='store_true', default=False, - help='Should license text be kept in the instantiation,' - ' default is False') + help='Should license in the template files text be kept in the' + ' instantiation, default is False, so will not keep license text' + ' by default. License text is defined as all lines of text starting' + ' on a line with {BEGIN_LICENSE} and ending line {END_LICENSE}.') create_template_subparser.add_argument('-r', '--replace', type=str, required=False, nargs='*', help='String that specifies A->B replacement pairs.' @@ -1505,34 +2171,51 @@ def add_args(parser, subparsers) -> None: ' Note: is automatically ${NameUpper}') create_template_subparser.set_defaults(func=_run_create_template) - # creation from a template + # create from template create_from_template_subparser = subparsers.add_parser('create-from-template') create_from_template_subparser.add_argument('-dp', '--destination-path', type=str, required=True, help='The path to where you want the template instantiated,' ' can be absolute or dev root relative.' 'Ex. C:/o3de/Test' 'Test = ') - create_from_template_subparser.add_argument('-drp', '--destination-restricted-path', type=str, required=False, - default='restricted', - help='The path to the dst restricted folder to read from if any, can be' - ' absolute or dev root relative. default is dev root/restricted') - create_from_template_subparser.add_argument('-drprp', '--destination-restricted-platform-relative-path', type=str, required=False, + + group = create_from_template_subparser.add_mutually_exclusive_group(required=True) + group.add_argument('-tp', '--template-path', type=str, required=False, + help='The path to the template you want to instantiate, can be absolute' + ' or dev root/Templates relative.' + 'Ex. C:/o3de/Template/TestTemplate' + 'TestTemplate = ') + group.add_argument('-tn', '--template-name', type=str, required=False, + help='The name to the registered template you want to instantiate. If supplied this will' + ' resolve the --template-path.') + + group = create_from_template_subparser.add_mutually_exclusive_group(required=True) + group.add_argument('-drp', '--destination-restricted-path', type=str, required=False, + default=None, + help='The destination restricted path is where the restricted files' + ' will be written to.') + group.add_argument('-drn', '--destination-restricted-name', type=str, required=False, + default=None, + help='The name the registered restricted path where the restricted files' + ' will be written to. If supplied this will resolve the --destination-restricted-path.') + + group = create_from_template_subparser.add_mutually_exclusive_group(required=False) + group.add_argument('-trp', '--template-restricted-path', type=str, required=False, + default=None, + help='The template restricted path to read from if any') + group.add_argument('-trn', '--template-restricted-name', type=str, required=False, + default=None, + help='The name of the registered restricted path to read from if any. If supplied this will' + ' resolve the --template-restricted-path.') + + create_from_template_subparser.add_argument('-drprp', '--destination-restricted-platform-relative-path', type=str, + required=False, default='', help='Any path to append to the --destination-restricted-path/' - ' to where the restricted destination is.' - ' --destination-restricted-path C:/instance' - ' --destination-restricted-platform-relative-path some/folder' - ' => C:/instance//some/folder/') - create_from_template_subparser.add_argument('-tp', '--template-path', type=str, required=True, - help='The path to the template you want to instantiate, can be absolute' - ' or dev root/Templates relative.' - 'Ex. C:/o3de/Template/TestTemplate' - 'TestTemplate = ') - create_from_template_subparser.add_argument('-trp', '--template-restricted-path', type=str, required=False, - default='restricted', - help='The path to the template restricted folder to write to if any,' - ' can be absolute or dev root relative, default is' - ' dev root/restricted') + ' to where the restricted destination is.' + ' --destination-restricted-path C:/instance' + ' --destination-restricted-platform-relative-path some/folder' + ' => C:/instance//some/folder/') create_from_template_subparser.add_argument('-trprp', '--template-restricted-platform-relative-path', type=str, required=False, default='Templates', @@ -1548,8 +2231,10 @@ def add_args(parser, subparsers) -> None: ' is False') create_from_template_subparser.add_argument('-kl', '--keep-license-text', action='store_true', default=False, - help='Should license text be kept in the instantiation,' - ' default is False') + help='Should license in the template files text be kept in the instantiation,' + ' default is False, so will not keep license text by default.' + ' License text is defined as all lines of text starting on a line' + ' with {BEGIN_LICENSE} and ending line {END_LICENSE}.') create_from_template_subparser.add_argument('-r', '--replace', type=str, required=False, nargs='*', help='String that specifies A->B replacement pairs.' @@ -1567,31 +2252,48 @@ def add_args(parser, subparsers) -> None: ' can be an absolute path or dev root relative.' ' Ex. C:/o3de/TestProject' ' TestProject = ') - create_project_subparser.add_argument('-prp', '--project-restricted-path', type=str, required=False, - default='restricted', - help='The path to the project restricted folder to write to if any, can be' - ' absolute or dev root relative, default is dev root/restricted') + + group = create_project_subparser.add_mutually_exclusive_group(required=False) + group.add_argument('-tp', '--template-path', type=str, required=False, + default=None, + help='the path to the template you want to instance, can be absolute or' + ' relative to default templates path') + group.add_argument('-tn', '--template-name', type=str, required=False, + default=None, + help='The name the registered template you want to instance, defaults' + ' to DefaultProject. If supplied this will resolve the --template-path.') + + group = create_project_subparser.add_mutually_exclusive_group(required=False) + group.add_argument('-prp', '--project-restricted-path', type=str, required=False, + default=None, + help='path to the projects restricted folder, can be absolute or relative' + ' to the restricted="projects"') + group.add_argument('-prn', '--project-restricted-name', type=str, required=False, + default=None, + help='The name of the registered projects restricted path. If supplied this will resolve' + ' the --project-restricted-path.') + + group = create_project_subparser.add_mutually_exclusive_group(required=False) + group.add_argument('-trp', '--template-restricted-path', type=str, required=False, + default=None, + help='The templates restricted path can be absolute or relative to' + ' restricted="templates"') + group.add_argument('-trn', '--template-restricted-name', type=str, required=False, + default=None, + help='The name of the registered templates restricted path. If supplied this will resolve' + ' the --template-restricted-path.') + create_project_subparser.add_argument('-prprp', '--project-restricted-platform-relative-path', type=str, required=False, - default='', + default=None, help='Any path to append to the --project-restricted-path/' ' to where the restricted project is.' ' --project-restricted-path C:/restricted' ' --project-restricted-platform-relative-path some/folder' ' => C:/restricted//some/folder/') - create_project_subparser.add_argument('-tp', '--template-path', type=str, required=False, - default='DefaultProject', - help='The path to the template you want to instantiate, can be absolute' - ' or dev root/Templates relative, defaults to the DefaultProject.' - ' Ex. C:/o3de/Template/TestTemplate' - ' TestTemplate = ') - create_project_subparser.add_argument('-trp', '--template-restricted-path', type=str, required=False, - default='restricted', - help='The path to the template restricted folder to read from if any, can be' - ' absolute or dev root relative, default is dev root/restricted') create_project_subparser.add_argument('-trprp', '--template-restricted-platform-relative-path', type=str, required=False, - default='Templates', + default=None, help='Any path to append to the --template-restricted-path/' ' to where the restricted template is.' ' --template-restricted-path C:/restricted' @@ -1603,7 +2305,10 @@ def add_args(parser, subparsers) -> None: 'create the restricted files in the restricted folder, default is False') create_project_subparser.add_argument('-kl', '--keep-license-text', action='store_true', default=False, - help='Should license text be kept in the instantiation, default is False') + help='Should license in the template files text be kept in the instantiation,' + ' default is False, so will not keep license text by default.' + ' License text is defined as all lines of text starting on a line' + ' with {BEGIN_LICENSE} and ending line {END_LICENSE}.') create_project_subparser.add_argument('-r', '--replace', required=False, nargs='*', help='String that specifies ADDITIONAL A->B replacement pairs. ${Name} and' @@ -1618,9 +2323,10 @@ def add_args(parser, subparsers) -> None: create_project_subparser.add_argument('--system-component-class-id', type=utils.validate_uuid4, required=False, help='The uuid you want to associate with the system class component, default' ' is a random uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}') - create_project_subparser.add_argument('--editor-system-component-class-id', type=utils.validate_uuid4, required=False, - help='The uuid you want to associate with the editor system class component, default' - ' is a random uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}') + create_project_subparser.add_argument('--editor-system-component-class-id', type=utils.validate_uuid4, + required=False, + help='The uuid you want to associate with the editor system class component,' + ' default is a random uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}') create_project_subparser.add_argument('--module-id', type=utils.validate_uuid4, required=False, help='The uuid you want to associate with the module, default is a random' ' uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}') @@ -1629,33 +2335,50 @@ def add_args(parser, subparsers) -> None: # creation of a gem from a template (like create from template but makes gem assumptions) create_gem_subparser = subparsers.add_parser('create-gem') create_gem_subparser.add_argument('-gp', '--gem-path', type=str, required=True, - help='The path to the gem you want to create, can be absolute or dev root/Gems' - ' relative.' - ' Ex. C:/o3de/TestGem' - ' TestGem = ') - create_gem_subparser.add_argument('-grp', '--gem-restricted-path', type=str, required=False, - default='restricted', - help='The path to the gem restricted to write to folder if any, can be' - 'absolute or dev root relative, default is dev root/restricted.') + help='The gem path, can be absolute or relative to default gems path') + + group = create_gem_subparser.add_mutually_exclusive_group(required=False) + group.add_argument('-tp', '--template-path', type=str, required=False, + default=None, + help='The template path you want to instance, can be absolute or relative' + ' to default templates path') + group.add_argument('-tn', '--template-name', type=str, required=False, + default=None, + help='The name of the registered template you want to instance, defaults' + ' to DefaultGem. If supplied this will resolve the --template-path.') + + group = create_gem_subparser.add_mutually_exclusive_group(required=False) + group.add_argument('-grp', '--gem-restricted-path', type=str, required=False, + default=None, + help='The path to the gem restricted to write to folder if any, can be' + 'absolute or dev root relative, default is dev root/restricted.') + group.add_argument('-grn', '--gem-restricted-name', type=str, required=False, + default=None, + help='The path to the gem restricted to write to folder if any, can be' + 'absolute or dev root relative, default is dev root/restricted. If supplied' + ' this will resolve the --gem-restricted-path.') + + group = create_gem_subparser.add_mutually_exclusive_group(required=False) + group.add_argument('-trp', '--template-restricted-path', type=str, required=False, + default=None, + help='The templates restricted path, can be absolute or relative to' + ' the restricted="templates"') + group.add_argument('-trn', '--template-restricted-name', type=str, required=False, + default=None, + help='The name of the registered templates restricted path. If supplied' + ' this will resolve the --template-restricted-path.') + create_gem_subparser.add_argument('-grprp', '--gem-restricted-platform-relative-path', type=str, required=False, - default='Gems', + default=None, help='Any path to append to the --gem-restricted-path/' ' to where the restricted template is.' ' --gem-restricted-path C:/restricted' ' --gem-restricted-platform-relative-path some/folder' ' => C:/restricted//some/folder/') - create_gem_subparser.add_argument('-tp', '--template-path', type=str, required=False, - default='DefaultGem', - help='The path to the template you want to instantiate, can be absolute or' - ' relative to the dev root/Templates. Default is DefaultGem.') - create_gem_subparser.add_argument('-trp', '--template-restricted-path', type=str, required=False, - default='restricted', - help='The path to the gem restricted folder to read from if any, can be' - 'absolute or dev root relative, default is dev root/restricted.') create_gem_subparser.add_argument('-trprp', '--template-restricted-platform-relative-path', type=str, required=False, - default='Templates', + default=None, help='Any path to append to the --template-restricted-path/' ' to where the restricted template is.' ' --template-restricted-path C:/restricted' @@ -1678,14 +2401,17 @@ def add_args(parser, subparsers) -> None: 'create the restricted files in the restricted folder, default is False') create_gem_subparser.add_argument('-kl', '--keep-license-text', action='store_true', default=False, - help='Should license text be kept in the instantiation, default is False') + help='Should license in the template files text be kept in the instantiation,' + ' default is False, so will not keep license text by default.' + ' License text is defined as all lines of text starting on a line' + ' with {BEGIN_LICENSE} and ending line {END_LICENSE}.') create_gem_subparser.add_argument('--system-component-class-id', type=utils.validate_uuid4, required=False, help='The uuid you want to associate with the system class component, default' ' is a random uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}') create_gem_subparser.add_argument('--editor-system-component-class-id', type=utils.validate_uuid4, required=False, - help='The uuid you want to associate with the editor system class component, default' - ' is a random uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}') + help='The uuid you want to associate with the editor system class component,' + ' default is a random uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}') create_gem_subparser.add_argument('--module-id', type=utils.validate_uuid4, required=False, help='The uuid you want to associate with the gem module,' ' default is a random uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}') diff --git a/cmake/Tools/global_project.py b/cmake/Tools/global_project.py new file mode 100644 index 0000000000..1d84d9dcfb --- /dev/null +++ b/cmake/Tools/global_project.py @@ -0,0 +1,168 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +import argparse +import logging +import os +import sys +import re +import pathlib +import json +import cmake.Tools.registration as registration + +logger = logging.getLogger() +logging.basicConfig() + + +def set_global_project(project_name: str or None, + project_path: str or pathlib.Path or None) -> int: + """ + set what the current project is + :param project_name: the name of the project you want to set, resolves project_path + :param project_path: the path of the project you want to set + :return: 0 for success or non 0 failure code + """ + if project_path and project_name: + logger.error(f'Project Name and Project Path provided, these are mutually exclusive.') + return 1 + + if not project_name and not project_path: + logger.error('Must specify either a Project name or Project Path.') + return 1 + + if project_name and not project_path: + project_path = registration.get_registered(project_name=project_name) + + if not project_path: + logger.error(f'Project Path {project_path} has not been registered.') + return 1 + + project_path = pathlib.Path(project_path).resolve() + + bootstrap_setreg_file = registration.get_o3de_registry_folder() / 'bootstrap.setreg' + if bootstrap_setreg_file.is_file(): + with bootstrap_setreg_file.open('r') as f: + try: + json_data = json.load(f) + except Exception as e: + logger.error(f'Bootstrap.setreg failed to load: {str(e)}') + else: + try: + json_data["Amazon"]["AzCore"]["Bootstrap"]["project_path"] = project_path + except Exception as e: + logger.error(f'Bootstrap.setreg failed to load: {str(e)}') + else: + try: + os.unlink(bootstrap_setreg_file) + except Exception as e: + logger.error(f'Failed to unlink bootstrap file {bootstrap_setreg_file}: {str(e)}') + return 1 + else: + json_data = {} + json_data.update({"Amazon":{"AzCore":{"Bootstrap":{"project_path":project_path.as_posix()}}}}) + + with bootstrap_setreg_file.open('w') as s: + s.write(json.dumps(json_data, indent=4)) + + return 0 + + +def get_global_project() -> pathlib.Path or None: + """ + get what the current project set is + :return: project_path or None on failure + """ + bootstrap_setreg_file = registration.get_o3de_registry_folder() / 'bootstrap.setreg' + if not bootstrap_setreg_file.is_file(): + logger.error(f'Bootstrap.setreg file {bootstrap_setreg_file} does not exist.') + return None + + with bootstrap_setreg_file.open('r') as f: + try: + json_data = json.load(f) + except Exception as e: + logger.error(f'Bootstrap.setreg failed to load: {str(e)}') + else: + try: + project_path = json_data["Amazon"]["AzCore"]["Bootstrap"]["project_path"] + except Exception as e: + logger.error(f'Bootstrap.setreg cannot find Amazon:AzCore:Bootstrap:project_path: {str(e)}') + else: + return pathlib.Path(project_path).resolve() + return None + +def _run_get_global_project(args: argparse) -> int: + if args.override_home_folder: + registration.override_home_folder = args.override_home_folder + + project_path = get_global_project() + if project_path: + print(project_path.as_posix()) + return 0 + return 1 + + +def _run_set_global_project(args: argparse) -> int: + if args.override_home_folder: + registration.override_home_folder = args.override_home_folder + + return set_global_project(args.project_name, + args.project_path) + + +def add_args(parser, subparsers) -> None: + """ + add_args is called to add expected parser arguments and subparsers arguments to each command such that it can be + invoked locally or aggregated by a central python file. + Ex. Directly run from this file alone with: python global_project.py set_global_project --project-name TestProject + OR + o3de.py can aggregate commands by importing global_project, call add_args and + execute: python o3de.py set_global_project --project-path C:/TestProject + :param parser: the caller instantiates a parser and passes it in here + :param subparsers: the caller instantiates subparsers and passes it in here + """ + get_global_project_subparser = subparsers.add_parser('get-global-project') + get_global_project_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') + + get_global_project_subparser.set_defaults(func=_run_get_global_project) + + set_global_project_subparser = subparsers.add_parser('set-global-project') + group = set_global_project_subparser.add_mutually_exclusive_group(required=True) + group.add_argument('-pn', '--project-name', required=False, + help='The name of the project. If supplied this will resolve the --project-path.') + group.add_argument('-pp', '--project-path', required=False, + help='The path to the project') + + set_global_project_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') + + set_global_project_subparser.set_defaults(func=_run_set_global_project) + + +if __name__ == "__main__": + # parse the command line args + the_parser = argparse.ArgumentParser() + + # add subparsers + the_subparsers = the_parser.add_subparsers(help='sub-command help') + + # add args to the parser + add_args(the_parser, the_subparsers) + + # parse args + the_args = the_parser.parse_args() + + # run + ret = the_args.func(the_args) + + # return + sys.exit(ret) diff --git a/cmake/Tools/registration.py b/cmake/Tools/registration.py index c16a712859..292ec33d1f 100755 --- a/cmake/Tools/registration.py +++ b/cmake/Tools/registration.py @@ -11,6 +11,7 @@ """ This file contains all the code that has to do with registering engines, projects, gems and templates """ + import argparse import logging import os @@ -19,133 +20,611 @@ import json import pathlib import hashlib import shutil -import urllib +import zipfile +import urllib.parse +import urllib.request logger = logging.getLogger() logging.basicConfig() -def backup(file_name): +def backup_file(file_name: str or pathlib.Path) -> None: index = 0 renamed = False while not renamed: - backup_file_name = pathlib.Path(str(file_name) + '.bak' + str(index)) + backup_file_name = pathlib.Path(str(file_name) + '.bak' + str(index)).resolve() + index += 1 if not backup_file_name.is_file(): - file_name = pathlib.Path(file_name) + file_name = pathlib.Path(file_name).resolve() file_name.rename(backup_file_name) - renamed = True + if backup_file_name.is_file(): + renamed = True + + +def backup_folder(folder: str or pathlib.Path) -> None: + index = 0 + renamed = False + while not renamed: + backup_folder_name = pathlib.Path(str(folder) + '.bak' + str(index)).resolve() + index += 1 + if not backup_folder_name.is_dir(): + folder = pathlib.Path(folder).resolve() + folder.rename(backup_folder_name) + if backup_folder_name.is_dir(): + renamed = True + + +def get_this_engine_path() -> pathlib.Path: + return pathlib.Path(os.path.realpath(__file__)).parents[2].resolve() + + +override_home_folder = None + + +def get_home_folder() -> pathlib.Path: + if override_home_folder: + return pathlib.Path(override_home_folder).resolve() + else: + return pathlib.Path(os.path.expanduser("~")).resolve() + + +def get_o3de_folder() -> pathlib.Path: + o3de_folder = get_home_folder() / '.o3de' + o3de_folder.mkdir(parents=True, exist_ok=True) + return o3de_folder + + +def get_o3de_registry_folder() -> pathlib.Path: + registry_folder = get_o3de_folder() / 'Registry' + registry_folder.mkdir(parents=True, exist_ok=True) + return registry_folder + + +def get_o3de_cache_folder() -> pathlib.Path: + cache_folder = get_o3de_folder() / 'Cache' + cache_folder.mkdir(parents=True, exist_ok=True) + return cache_folder + + +def get_o3de_download_folder() -> pathlib.Path: + download_folder = get_o3de_folder() / 'Download' + download_folder.mkdir(parents=True, exist_ok=True) + return download_folder + + +def get_o3de_engines_folder() -> pathlib.Path: + engines_folder = get_o3de_folder() / 'Engines' + engines_folder.mkdir(parents=True, exist_ok=True) + return engines_folder + + +def get_o3de_projects_folder() -> pathlib.Path: + projects_folder = get_o3de_folder() / 'Projects' + projects_folder.mkdir(parents=True, exist_ok=True) + return projects_folder + + +def get_o3de_gems_folder() -> pathlib.Path: + gems_folder = get_o3de_folder() / 'Gems' + gems_folder.mkdir(parents=True, exist_ok=True) + return gems_folder -def register_shipped_engine_o3de_objects(): - # register this engines shipped projects, gems and templates + +def get_o3de_templates_folder() -> pathlib.Path: + templates_folder = get_o3de_folder() / 'Templates' + templates_folder.mkdir(parents=True, exist_ok=True) + return templates_folder + + +def get_o3de_restricted_folder() -> pathlib.Path: + restricted_folder = get_o3de_folder() / 'Restricted' + restricted_folder.mkdir(parents=True, exist_ok=True) + return restricted_folder + + +def get_o3de_logs_folder() -> pathlib.Path: + restricted_folder = get_o3de_folder() / 'Logs' + restricted_folder.mkdir(parents=True, exist_ok=True) + return restricted_folder + + +def register_shipped_engine_o3de_objects() -> int: engine_path = get_this_engine_path() - for root, dirs, files in os.walk(engine_path / 'AtomSampleViewer', topdown=False): + + ret_val = 0 + + # directories with engines + starting_engines_directories = [ + ] + for engines_directory in sorted(starting_engines_directories, reverse=True): + error_code = register_all_engines_in_folder(engines_path=engines_directory) + if error_code: + ret_val = error_code + + # specific engines + starting_engines = [ + ] + for engine_path in sorted(starting_engines): + error_code = register(engine_path=engine_path) + if error_code: + ret_val = error_code + + # directories with projects + starting_projects_directories = [ + ] + for projects_directory in sorted(starting_projects_directories, reverse=True): + error_code = register_all_projects_in_folder(engine_path=engine_path, projects_path=projects_directory) + if error_code: + ret_val = error_code + + # specific projects + starting_projects = [ + f'{engine_path}/AutomatedTesting' + ] + for project_path in sorted(starting_projects, reverse=True): + error_code = register(engine_path=engine_path, project_path=project_path) + if error_code: + ret_val = error_code + + # directories with gems + starting_gems_directories = [ + f'{engine_path}/Gems' + ] + for gems_directory in sorted(starting_gems_directories, reverse=True): + error_code = register_all_gems_in_folder(engine_path=engine_path, gems_path=gems_directory) + if error_code: + ret_val = error_code + + # specific gems + starting_gems = [ + ] + for gem_path in sorted(starting_gems, reverse=True): + error_code = register(engine_path=engine_path, gem_path=gem_path) + if error_code: + ret_val = error_code + + # directories with templates + starting_templates_directories = [ + f'{engine_path}/Templates' + ] + for templates_directory in sorted(starting_templates_directories, reverse=True): + error_code = register_all_templates_in_folder(engine_path=engine_path, templates_path=templates_directory) + if error_code: + ret_val = error_code + + # specific templates + starting_templates = [ + ] + for template_path in sorted(starting_templates, reverse=True): + error_code = register(engine_path=engine_path, template_path=template_path) + if error_code: + ret_val = error_code + + # directories with restricted + starting_restricted_directories = [ + ] + for restricted_directory in sorted(starting_restricted_directories, reverse=True): + error_code = register_all_restricted_in_folder(engine_path=engine_path, restricted_path=restricted_directory) + if error_code: + ret_val = error_code + + # specific restricted + starting_restricted = [ + ] + for restricted_path in sorted(starting_restricted, reverse=True): + error_code = register(engine_path=engine_path, restricted_path=restricted_path) + if error_code: + ret_val = error_code + + # directories with repos + starting_repo_directories = [ + ] + for repos_directory in sorted(starting_repo_directories, reverse=True): + error_code = register_all_repos_in_folder(engine_path=engine_path, repos_path=repos_directory) + if error_code: + ret_val = error_code + + # specific repos + starting_repos = [ + ] + for repo_uri in sorted(starting_repos, reverse=True): + error_code = register(repo_uri=repo_uri) + if error_code: + ret_val = error_code + + # register anything in the users default folders globally + error_code = register_all_engines_in_folder(get_registered(default_folder='engines')) + if error_code: + ret_val = error_code + error_code = register_all_projects_in_folder(get_registered(default_folder='projects')) + if error_code: + ret_val = error_code + error_code = register_all_gems_in_folder(get_registered(default_folder='gems')) + if error_code: + ret_val = error_code + error_code = register_all_templates_in_folder(get_registered(default_folder='templates')) + if error_code: + ret_val = error_code + error_code = register_all_restricted_in_folder(get_registered(default_folder='restricted')) + if error_code: + ret_val = error_code + error_code = register_all_restricted_in_folder(get_registered(default_folder='projects')) + if error_code: + ret_val = error_code + error_code = register_all_restricted_in_folder(get_registered(default_folder='gems')) + if error_code: + ret_val = error_code + error_code = register_all_restricted_in_folder(get_registered(default_folder='templates')) + if error_code: + ret_val = error_code + + starting_external_subdirectories = [ + f'{engine_path}/Gems/Atom', + f'{engine_path}/Gems/AtomLyIntegration' + ] + for external_subdir in sorted(starting_external_subdirectories, reverse=True): + error_code = add_external_subdirectory(engine_path=engine_path, external_subdir=external_subdir) + if error_code: + ret_val = error_code + + json_data = load_o3de_manifest() + engine_object = find_engine_data(json_data) + gems = json_data['gems'].copy() + gems.extend(engine_object['gems']) + for gem_path in sorted(gems, key=len): + gem_path = pathlib.Path(gem_path).resolve() + gem_cmake_lists_txt = gem_path / 'CMakeLists.txt' + if gem_cmake_lists_txt.is_file(): + add_gem_to_cmake(engine_path=engine_path, gem_path=gem_path, supress_errors=True) # don't care about errors + + return ret_val + + +def register_all_in_folder(folder_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None, + exclude: list = None) -> int: + if not folder_path: + logger.error(f'Folder path cannot be empty.') + return 1 + + folder_path = pathlib.Path(folder_path).resolve() + if not folder_path.is_dir(): + logger.error(f'Folder path is not dir.') + return 1 + + engines_set = set() + projects_set = set() + gems_set = set() + templates_set = set() + restricted_set = set() + repo_set = set() + + ret_val = 0 + for root, dirs, files in os.walk(folder_path): + if root in exclude: + continue + for name in files: - if name == 'project.json': - register(project_path=root) + if name == 'engine.json': + engines_set.add(root) + elif name == 'project.json': + projects_set.add(root) elif name == 'gem.json': - register(gem_path=root) + gems_set.add(root) elif name == 'template.json': - register(template_path=root) + templates_set.add(root) + elif name == 'restricted.json': + restricted_set.add(root) + elif name == 'repo.json': + repo_set.add(root) + + for engine in sorted(engines_set, reverse=True): + error_code = register(engine_path=engine, remove=remove) + if error_code: + ret_val = error_code + + for project in sorted(projects_set, reverse=True): + error_code = register(engine_path=engine_path, project_path=project, remove=remove) + if error_code: + ret_val = error_code + + for gem in sorted(gems_set, reverse=True): + error_code = register(engine_path=engine_path, gem_path=gem, remove=remove) + if error_code: + ret_val = error_code + + for template in sorted(templates_set, reverse=True): + error_code = register(engine_path=engine_path, template_path=template, remove=remove) + if error_code: + ret_val = error_code + + for restricted in sorted(restricted_set, reverse=True): + error_code = register(engine_path=engine_path, restricted_path=restricted, remove=remove) + if error_code: + ret_val = error_code + + for repo in sorted(repo_set, reverse=True): + error_code = register(engine_path=engine_path, repo_uri=repo, remove=remove) + if error_code: + ret_val = error_code + + return ret_val + + +def register_all_engines_in_folder(engines_path: str or pathlib.Path, + remove: bool = False) -> int: + if not engines_path: + logger.error(f'Engines path cannot be empty.') + return 1 - for root, dirs, files in os.walk(engine_path / 'AtomTest', topdown=False): + engines_path = pathlib.Path(engines_path).resolve() + if not engines_path.is_dir(): + logger.error(f'Engines path is not dir.') + return 1 + + engines_set = set() + + ret_val = 0 + for root, dirs, files in os.walk(engines_path): + for name in files: + if name == 'engine.json': + engines_set.add(name) + + for engine in sorted(engines_set, reverse=True): + error_code = register(engine_path=engine, remove=remove) + if error_code: + ret_val = error_code + + return ret_val + + +def register_all_projects_in_folder(projects_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: + if not projects_path: + logger.error(f'Projects path cannot be empty.') + return 1 + + projects_path = pathlib.Path(projects_path).resolve() + if not projects_path.is_dir(): + logger.error(f'Projects path is not dir.') + return 1 + + projects_set = set() + + ret_val = 0 + for root, dirs, files in os.walk(projects_path): for name in files: if name == 'project.json': - register(project_path=root) - elif name == 'gem.json': - register(gem_path=root) - elif name == 'template.json': - register(template_path=root) + projects_set.add(root) + + for project in sorted(projects_set, reverse=True): + error_code = register(engine_path=engine_path, project_path=project, remove=remove) + if error_code: + ret_val = error_code - for root, dirs, files in os.walk(engine_path / 'Gems', topdown=False): + return ret_val + + +def register_all_gems_in_folder(gems_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: + if not gems_path: + logger.error(f'Gems path cannot be empty.') + return 1 + + gems_path = pathlib.Path(gems_path).resolve() + if not gems_path.is_dir(): + logger.error(f'Gems path is not dir.') + return 1 + + gems_set = set() + + ret_val = 0 + for root, dirs, files in os.walk(gems_path): for name in files: if name == 'gem.json': - register(gem_path=root) + gems_set.add(root) + + for gem in sorted(gems_set, reverse=True): + error_code = register(engine_path=engine_path, gem_path=gem, remove=remove) + if error_code: + ret_val = error_code - engine_templates = os.listdir(engine_path / 'Templates') - for engine_template in engine_templates: - register(template_path=engine_path / 'Templates' / engine_template) + return ret_val - engine_projects = os.listdir(engine_path) - for engine_project in engine_projects: - engine_project_json = engine_path / engine_project / 'project.json' - if engine_project_json.is_file(): - register(project_path=engine_path / engine_project) +def register_all_templates_in_folder(templates_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: + if not templates_path: + logger.error(f'Templates path cannot be empty.') + return 1 -def get_this_engine_path(): - return pathlib.Path(os.path.realpath(__file__)).parent.parent.parent + templates_path = pathlib.Path(templates_path).resolve() + if not templates_path.is_dir(): + logger.error(f'Templates path is not dir.') + return 1 + templates_set = set() -def get_home_folder(): - return pathlib.Path(os.path.expanduser("~")) + ret_val = 0 + for root, dirs, files in os.walk(templates_path): + for name in files: + if name == 'template.json': + templates_set.add(root) + for template in sorted(templates_set, reverse=True): + error_code = register(engine_path=engine_path, template_path=template, remove=remove) + if error_code: + ret_val = error_code -def get_o3de_folder(): - o3de_folder = get_home_folder() / '.o3de' - o3de_folder.mkdir(parents=True, exist_ok=True) - return o3de_folder + return ret_val -def get_o3de_cache(): - cache_folder = get_o3de_folder() / 'cache' - cache_folder.mkdir(parents=True, exist_ok=True) - return cache_folder +def register_all_restricted_in_folder(restricted_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: + if not restricted_path: + logger.error(f'Restricted path cannot be empty.') + return 1 + + restricted_path = pathlib.Path(restricted_path).resolve() + if not restricted_path.is_dir(): + logger.error(f'Restricted path is not dir.') + return 1 + + restricted_set = set() + + ret_val = 0 + for root, dirs, files in os.walk(restricted_path): + for name in files: + if name == 'restricted.json': + restricted_set.add(root) + + for restricted in sorted(restricted_set, reverse=True): + error_code = register(engine_path=engine_path, restricted_path=restricted, remove=remove) + if error_code: + ret_val = error_code + + return ret_val + + +def register_all_repos_in_folder(repos_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: + if not repos_path: + logger.error(f'Repos path cannot be empty.') + return 1 + + repos_path = pathlib.Path(repos_path).resolve() + if not repos_path.is_dir(): + logger.error(f'Repos path is not dir.') + return 1 + + repo_set = set() + + ret_val = 0 + for root, dirs, files in os.walk(repos_path): + for name in files: + if name == 'repo.json': + repo_set.add(root) + for repo in sorted(repo_set, reverse=True): + error_code = register(engine_path=engine_path, repo_uri=repo, remove=remove) + if error_code: + ret_val = error_code -def get_o3de_registry(): - registry_path = get_o3de_folder() / 'o3de_manifest.json' - if not registry_path.is_file(): + return ret_val + +def get_o3de_manifest() -> pathlib.Path: + manifest_path = get_o3de_folder() / 'o3de_manifest.json' + if not manifest_path.is_file(): username = os.path.split(get_home_folder())[-1] + o3de_folder = get_o3de_folder() + default_registry_folder = get_o3de_registry_folder() + default_cache_folder = get_o3de_cache_folder() + default_downloads_folder = get_o3de_download_folder() + default_logs_folder = get_o3de_logs_folder() + default_engines_folder = get_o3de_engines_folder() + default_projects_folder = get_o3de_projects_folder() + default_gems_folder = get_o3de_gems_folder() + default_templates_folder = get_o3de_templates_folder() + default_restricted_folder = get_o3de_restricted_folder() + + default_projects_restricted_folder = default_projects_folder / 'Restricted' + default_projects_restricted_folder.mkdir(parents=True, exist_ok=True) + default_gems_restricted_folder = default_gems_folder / 'Restricted' + default_gems_restricted_folder.mkdir(parents=True, exist_ok=True) + default_templates_restricted_folder = default_templates_folder / 'Restricted' + default_templates_restricted_folder.mkdir(parents=True, exist_ok=True) + json_data = {} - json_data.update({'repo_name': f'{username}'}) - json_data.update({'origin': get_o3de_folder().as_posix()}) - json_data.update({'engines': []}) + json_data.update({'o3de_manifest_name': f'{username}'}) + json_data.update({'origin': o3de_folder.as_posix()}) + json_data.update({'default_engines_folder': default_engines_folder.as_posix()}) + json_data.update({'default_projects_folder': default_projects_folder.as_posix()}) + json_data.update({'default_gems_folder': default_gems_folder.as_posix()}) + json_data.update({'default_templates_folder': default_templates_folder.as_posix()}) + json_data.update({'default_restricted_folder': default_restricted_folder.as_posix()}) + json_data.update({'projects': []}) json_data.update({'gems': []}) json_data.update({'templates': []}) + json_data.update({'restricted': []}) json_data.update({'repos': []}) - default_projects_folder = get_home_folder() / 'my_o3de/projects' - default_projects_folder.mkdir(parents=True, exist_ok=True) - json_data.update({'default_projects_folder': default_projects_folder.as_posix()}) - default_gems_folder = get_home_folder() / 'my_o3de/gems' - default_gems_folder.mkdir(parents=True, exist_ok=True) - json_data.update({'default_gems_folder': default_gems_folder.as_posix()}) - default_templates_folder = get_home_folder() / 'my_o3de/templates' - default_templates_folder.mkdir(parents=True, exist_ok=True) - json_data.update({'default_templates_folder': default_templates_folder.as_posix()}) - with registry_path.open('w') as s: + json_data.update({'engines': []}) + + default_restricted_folder_json = default_restricted_folder / 'restricted.json' + if not default_restricted_folder_json.is_file(): + with default_restricted_folder_json.open('w') as s: + restricted_json_data = {} + restricted_json_data.update({'restricted_name': 'o3de'}) + s.write(json.dumps(restricted_json_data, indent=4)) + json_data.update({'default_restricted_folder': default_restricted_folder.as_posix()}) + + default_projects_restricted_folder_json = default_projects_restricted_folder / 'restricted.json' + if not default_projects_restricted_folder_json.is_file(): + with default_projects_restricted_folder_json.open('w') as s: + restricted_json_data = {} + restricted_json_data.update({'restricted_name': 'projects'}) + s.write(json.dumps(restricted_json_data, indent=4)) + + default_gems_restricted_folder_json = default_gems_restricted_folder / 'restricted.json' + if not default_gems_restricted_folder_json.is_file(): + with default_gems_restricted_folder_json.open('w') as s: + restricted_json_data = {} + restricted_json_data.update({'restricted_name': 'gems'}) + s.write(json.dumps(restricted_json_data, indent=4)) + + default_templates_restricted_folder_json = default_templates_restricted_folder / 'restricted.json' + if not default_templates_restricted_folder_json.is_file(): + with default_templates_restricted_folder_json.open('w') as s: + restricted_json_data = {} + restricted_json_data.update({'restricted_name': 'templates'}) + s.write(json.dumps(restricted_json_data, indent=4)) + + with manifest_path.open('w') as s: s.write(json.dumps(json_data, indent=4)) - return registry_path + return manifest_path -def load_o3de_registry(): - with get_o3de_registry().open('r') as f: - return json.load(f) +def load_o3de_manifest() -> dict: + with get_o3de_manifest().open('r') as f: + try: + json_data = json.load(f) + except Exception as e: + logger.error(f'Manifest json failed to load: {str(e)}') + else: + return json_data -def save_o3de_registry(json_data): - with get_o3de_registry().open('w') as s: - s.write(json.dumps(json_data, indent=4)) +def save_o3de_manifest(json_data: dict) -> None: + with get_o3de_manifest().open('w') as s: + try: + s.write(json.dumps(json_data, indent=4)) + except Exception as e: + logger.error(f'Manifest json failed to save: {str(e)}') -def register_engine_path(json_data, - engine_path: str, +def register_engine_path(json_data: dict, + engine_path: str or pathlib.Path, remove: bool = False) -> int: if not engine_path: logger.error(f'Engine path cannot be empty.') return 1 + engine_path = pathlib.Path(engine_path).resolve() - while engine_path in json_data['engines']: - json_data['engines'].remove(engine_path) - engine_path = pathlib.Path(engine_path) - while engine_path.as_posix() in json_data['engines']: - json_data['engines'].remove(engine_path.as_posix()) + for engine_object in json_data['engines']: + engine_object_path = pathlib.Path(engine_object['path']).resolve() + if engine_object_path == engine_path: + json_data['engines'].remove(engine_object) if remove: - logger.warn(f'Removing Engine path {engine_path}.') return 0 if not engine_path.is_dir(): @@ -157,27 +636,53 @@ def register_engine_path(json_data, logger.error(f'Engine json {engine_json} is not valid.') return 1 - json_data['engines'].insert(0, engine_path.as_posix()) + engine_object = {} + engine_object.update({'path': engine_path.as_posix()}) + engine_object.update({'projects': []}) + engine_object.update({'gems': []}) + engine_object.update({'templates': []}) + engine_object.update({'restricted': []}) + engine_object.update({'external_subdirectories': []}) + + json_data['engines'].insert(0, engine_object) return 0 -def register_gem_path(json_data, - gem_path: str, - remove: bool = False) -> int: +def register_gem_path(json_data: dict, + gem_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: if not gem_path: logger.error(f'Gem path cannot be empty.') return 1 + gem_path = pathlib.Path(gem_path).resolve() - while gem_path in json_data['gems']: - json_data['gems'].remove(gem_path) - gem_path = pathlib.Path(gem_path) - while gem_path.as_posix() in json_data['gems']: - json_data['gems'].remove(gem_path.as_posix()) + if engine_path: + engine_data = find_engine_data(json_data, engine_path) + if not engine_data: + logger.error(f'Engine path {engine_path} is not registered.') + return 1 - if remove: - logger.warn(f'Removing Gem path {gem_path}.') - return 0 + while gem_path in engine_data['gems']: + engine_data['gems'].remove(gem_path) + + while gem_path.as_posix() in engine_data['gems']: + engine_data['gems'].remove(gem_path.as_posix()) + + if remove: + logger.warn(f'Removing Gem path {gem_path}.') + return 0 + else: + while gem_path in json_data['gems']: + json_data['gems'].remove(gem_path) + + while gem_path.as_posix() in json_data['gems']: + json_data['gems'].remove(gem_path.as_posix()) + + if remove: + logger.warn(f'Removing Gem path {gem_path}.') + return 0 if not gem_path.is_dir(): logger.error(f'Gem path {gem_path} does not exist.') @@ -188,27 +693,48 @@ def register_gem_path(json_data, logger.error(f'Gem json {gem_json} is not valid.') return 1 - json_data['gems'].insert(0, gem_path.as_posix()) + if engine_path: + engine_data['gems'].insert(0, gem_path.as_posix()) + else: + json_data['gems'].insert(0, gem_path.as_posix()) return 0 -def register_project_path(json_data, - project_path: str, - remove: bool = False) -> int: +def register_project_path(json_data: dict, + project_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: if not project_path: logger.error(f'Project path cannot be empty.') return 1 + project_path = pathlib.Path(project_path).resolve() - while project_path in json_data['projects']: - json_data['projects'].remove(project_path) - project_path = pathlib.Path(project_path) - while project_path.as_posix() in json_data['projects']: - json_data['projects'].remove(project_path.as_posix()) + if engine_path: + engine_data = find_engine_data(json_data, engine_path) + if not engine_data: + logger.error(f'Engine path {engine_path} is not registered.') + return 1 - if remove: - logger.warn(f'Removing Project path {project_path}.') - return 0 + while project_path in engine_data['projects']: + engine_data['projects'].remove(project_path) + + while project_path.as_posix() in engine_data['projects']: + engine_data['projects'].remove(project_path.as_posix()) + + if remove: + logger.warn(f'Engine {engine_path} removing Project path {project_path}.') + return 0 + else: + while project_path in json_data['projects']: + json_data['projects'].remove(project_path) + + while project_path.as_posix() in json_data['projects']: + json_data['projects'].remove(project_path.as_posix()) + + if remove: + logger.warn(f'Removing Project path {project_path}.') + return 0 if not project_path.is_dir(): logger.error(f'Project path {project_path} does not exist.') @@ -219,14 +745,25 @@ def register_project_path(json_data, logger.error(f'Project json {project_json} is not valid.') return 1 - json_data['projects'].insert(0, project_path.as_posix()) + if engine_path: + engine_data['projects'].insert(0, project_path.as_posix()) + else: + json_data['projects'].insert(0, project_path.as_posix()) # registering a project has the additional step of setting the project.json 'engine' field this_engine_json = get_this_engine_path() / 'engine.json' with this_engine_json.open('r') as f: - this_engine_json = json.load(f) + try: + this_engine_json = json.load(f) + except Exception as e: + logger.error(f'Engine json failed to load: {str(e)}') + return 1 with project_json.open('r') as f: - project_json_data = json.load(f) + try: + project_json_data = json.load(f) + except Exception as e: + logger.error(f'Project json failed to load: {str(e)}') + return 1 update_project_json = False try: @@ -236,29 +773,51 @@ def register_project_path(json_data, if update_project_json: project_json_data['engine'] = this_engine_json['engine_name'] - backup(project_json) + backup_file(project_json) with project_json.open('w') as s: - s.write(json.dumps(project_json_data, indent=4)) + try: + s.write(json.dumps(project_json_data, indent=4)) + except Exception as e: + logger.error(f'Project json failed to save: {str(e)}') + return 1 return 0 -def register_template_path(json_data, - template_path: str, - remove: bool = False) -> int: +def register_template_path(json_data: dict, + template_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: if not template_path: logger.error(f'Template path cannot be empty.') return 1 + template_path = pathlib.Path(template_path).resolve() - while template_path in json_data['templates']: - json_data['templates'].remove(template_path) - template_path = pathlib.Path(template_path) - while template_path.as_posix() in json_data['templates']: - json_data['templates'].remove(template_path.as_posix()) + if engine_path: + engine_data = find_engine_data(json_data, engine_path) + if not engine_data: + logger.error(f'Engine path {engine_path} is not registered.') + return 1 - if remove: - logger.warn(f'Removing Template path {template_path}.') - return 0 + while template_path in engine_data['templates']: + engine_data['templates'].remove(template_path) + + while template_path.as_posix() in engine_data['templates']: + engine_data['templates'].remove(template_path.as_posix()) + + if remove: + logger.warn(f'Engine {engine_path} removing Template path {template_path}.') + return 0 + else: + while template_path in json_data['templates']: + json_data['templates'].remove(template_path) + + while template_path.as_posix() in json_data['templates']: + json_data['templates'].remove(template_path.as_posix()) + + if remove: + logger.warn(f'Removing Template path {template_path}.') + return 0 if not template_path.is_dir(): logger.error(f'Template path {template_path} does not exist.') @@ -269,223 +828,470 @@ def register_template_path(json_data, logger.error(f'Template json {template_json} is not valid.') return 1 - json_data['templates'].insert(0, template_path.as_posix()) + if engine_path: + engine_data['templates'].insert(0, template_path.as_posix()) + else: + json_data['templates'].insert(0, template_path.as_posix()) return 0 -def register_repo(json_data, - repo_uri: str, - remove: bool = False) -> int: - if not repo_uri: - logger.error(f'Repo URI cannot be empty.') +def register_restricted_path(json_data: dict, + restricted_path: str or pathlib.Path, + remove: bool = False, + engine_path: str or pathlib.Path = None) -> int: + if not restricted_path: + logger.error(f'Restricted path cannot be empty.') return 1 + restricted_path = pathlib.Path(restricted_path).resolve() - while repo_uri in json_data['repos']: - json_data['repos'].remove(repo_uri) + if engine_path: + engine_data = find_engine_data(json_data, engine_path) + if not engine_data: + logger.error(f'Engine path {engine_path} is not registered.') + return 1 - if remove: - logger.warn(f'Removing repo uri {repo_uri}.') + while restricted_path in engine_data['restricted']: + engine_data['restricted'].remove(restricted_path) - if remove: - result = refresh_repos() + while restricted_path.as_posix() in engine_data['restricted']: + engine_data['restricted'].remove(restricted_path.as_posix()) + + if remove: + logger.warn(f'Engine {engine_path} removing Restricted path {restricted_path}.') + return 0 else: - repo_hash = hashlib.md5(repo_uri.encode()) - cache_folder = get_o3de_cache() - cache_file = cache_folder / str(repo_hash.hexdigest() + '.json') - parsed_uri = urllib.parse.urlparse(repo_uri) - - if parsed_uri.scheme == 'http' or parsed_uri.scheme == 'https' or parsed_uri.scheme == 'ftp' or parsed_uri.scheme == 'ftps': - result = 0 # a function that processes the uri and returns result - if not result: - json_data['repos'].insert(0, repo_uri) - else: - repo_uri = pathlib.Path(repo_uri) - json_data['repos'].insert(0, repo_uri.as_posix()) - result = 0 + while restricted_path in json_data['restricted']: + json_data['restricted'].remove(restricted_path) - return result + while restricted_path.as_posix() in json_data['restricted']: + json_data['restricted'].remove(restricted_path.as_posix()) + if remove: + logger.warn(f'Removing Restricted path {restricted_path}.') + return 0 -def valid_o3de_manifest_json(file_name: str) -> bool: - try: - file_name = pathlib.Path(file_name) - if not file_name.is_file(): - return False - with file_name.open('r') as f: + if not restricted_path.is_dir(): + logger.error(f'Restricted path {restricted_path} does not exist.') + return 1 + + restricted_json = restricted_path / 'restricted.json' + if not valid_o3de_restricted_json(restricted_json): + logger.error(f'Restricted json {restricted_json} is not valid.') + return 1 + + if engine_path: + engine_data['restricted'].insert(0, restricted_path.as_posix()) + else: + json_data['restricted'].insert(0, restricted_path.as_posix()) + + return 0 + + +def valid_o3de_repo_json(file_name: str or pathlib.Path) -> bool: + file_name = pathlib.Path(file_name).resolve() + if not file_name.is_file(): + return False + + with file_name.open('r') as f: + try: json_data = json.load(f) test = json_data['repo_name'] test = json_data['origin'] - test = json_data['engines'] - test = json_data['projects'] - test = json_data['gems'] - test = json_data['templates'] - test = json_data['repos'] - except Exception as e: - return False + except Exception as e: + return False return True -def valid_o3de_engine_json(file_name: str) -> bool: - try: - file_name = pathlib.Path(file_name) - if not file_name.is_file(): - return False - with file_name.open('r') as f: - json_data = json.load(f) - test = json_data['engine_name'] - except Exception as e: +def valid_o3de_engine_json(file_name: str or pathlib.Path) -> bool: + file_name = pathlib.Path(file_name).resolve() + if not file_name.is_file(): return False + with file_name.open('r') as f: + try: + json_data = json.load(f) + test = json_data['engine_name'] + # test = json_data['origin'] # will be required soon + except Exception as e: + return False return True -def valid_o3de_project_json(file_name: str) -> bool: - try: - file_name = pathlib.Path(file_name) - if not file_name.is_file(): - return False - with file_name.open('r') as f: - json_data = json.load(f) - test = json_data['project_name'] - except Exception as e: +def valid_o3de_project_json(file_name: str or pathlib.Path) -> bool: + file_name = pathlib.Path(file_name).resolve() + if not file_name.is_file(): return False + with file_name.open('r') as f: + try: + json_data = json.load(f) + test = json_data['project_name'] + # test = json_data['origin'] # will be required soon + except Exception as e: + return False return True -def valid_o3de_gem_json(file_name: str) -> bool: - try: - file_name = pathlib.Path(file_name) - if not file_name.is_file(): - return False - with file_name.open('r') as f: - json_data = json.load(f) - test = json_data['gem_name'] - except Exception as e: +def valid_o3de_gem_json(file_name: str or pathlib.Path) -> bool: + file_name = pathlib.Path(file_name).resolve() + if not file_name.is_file(): return False + with file_name.open('r') as f: + try: + json_data = json.load(f) + test = json_data['gem_name'] + # test = json_data['origin'] # will be required soon + except Exception as e: + return False return True -def valid_o3de_template_json(file_name: str) -> bool: - try: - file_name = pathlib.Path(file_name) - if not file_name.is_file(): - return False - with file_name.open('r') as f: +def valid_o3de_template_json(file_name: str or pathlib.Path) -> bool: + file_name = pathlib.Path(file_name).resolve() + if not file_name.is_file(): + return False + with file_name.open('r') as f: + try: json_data = json.load(f) test = json_data['template_name'] - except Exception as e: - return False + # test = json_data['origin'] # will be required soon + except Exception as e: + return False + return True + +def valid_o3de_restricted_json(file_name: str or pathlib.Path) -> bool: + file_name = pathlib.Path(file_name).resolve() + if not file_name.is_file(): + return False + with file_name.open('r') as f: + try: + json_data = json.load(f) + test = json_data['restricted_name'] + # test = json_data['origin'] # will be required soon + except Exception as e: + return False return True -def register_default_projects_folder(json_data, - default_projects_folder: str, - remove: bool = False) -> int: - if remove: - json_data['default_projects_folder'] = '' - else: - # make sure the path exists - default_projects_folder = pathlib.Path(default_projects_folder) - if not default_projects_folder.is_dir(): - logger.error(f'Default projects folder {default_projects_folder} does not exist.') - return 1 +def process_add_o3de_repo(file_name: str or pathlib.Path, + repo_set: set) -> int: + file_name = pathlib.Path(file_name).resolve() + if not valid_o3de_repo_json(file_name): + return 1 + + cache_folder = get_o3de_cache_folder() - default_projects_folder = default_projects_folder.as_posix() - json_data['default_projects_folder'] = default_projects_folder + with file_name.open('r') as f: + try: + repo_data = json.load(f) + except Exception as e: + logger.error(f'{file_name} failed to load: {str(e)}') + return 1 + for engine_uri in repo_data['engines']: + engine_uri = f'{engine_uri}/engine.json' + engine_sha256 = hashlib.sha256(engine_uri.encode()) + cache_file = cache_folder / str(engine_sha256.hexdigest() + '.json') + if not cache_file.is_file(): + parsed_uri = urllib.parse.urlparse(engine_uri) + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + with urllib.request.urlopen(engine_uri) as s: + with cache_file.open('wb') as f: + shutil.copyfileobj(s, f) + else: + engine_json = pathlib.Path(engine_uri).resolve() + if not engine_json.is_file(): + return 1 + shutil.copy(engine_json, cache_file) + + for project_uri in repo_data['projects']: + project_uri = f'{project_uri}/project.json' + project_sha256 = hashlib.sha256(project_uri.encode()) + cache_file = cache_folder / str(project_sha256.hexdigest() + '.json') + if not cache_file.is_file(): + parsed_uri = urllib.parse.urlparse(project_uri) + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + with urllib.request.urlopen(project_uri) as s: + with cache_file.open('wb') as f: + shutil.copyfileobj(s, f) + else: + project_json = pathlib.Path(project_uri).resolve() + if not project_json.is_file(): + return 1 + shutil.copy(project_json, cache_file) + + for gem_uri in repo_data['gems']: + gem_uri = f'{gem_uri}/gem.json' + gem_sha256 = hashlib.sha256(gem_uri.encode()) + cache_file = cache_folder / str(gem_sha256.hexdigest() + '.json') + if not cache_file.is_file(): + parsed_uri = urllib.parse.urlparse(gem_uri) + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + with urllib.request.urlopen(gem_uri) as s: + with cache_file.open('wb') as f: + shutil.copyfileobj(s, f) + else: + gem_json = pathlib.Path(gem_uri).resolve() + if not gem_json.is_file(): + return 1 + shutil.copy(gem_json, cache_file) + + for template_uri in repo_data['templates']: + template_uri = f'{template_uri}/template.json' + template_sha256 = hashlib.sha256(template_uri.encode()) + cache_file = cache_folder / str(template_sha256.hexdigest() + '.json') + if not cache_file.is_file(): + parsed_uri = urllib.parse.urlparse(template_uri) + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + with urllib.request.urlopen(template_uri) as s: + with cache_file.open('wb') as f: + shutil.copyfileobj(s, f) + else: + template_json = pathlib.Path(template_uri).resolve() + if not template_json.is_file(): + return 1 + shutil.copy(template_json, cache_file) + + for repo_uri in repo_data['repos']: + if repo_uri not in repo_set: + repo_set.add(repo_uri) + repo_uri = f'{repo_uri}/repo.json' + repo_sha256 = hashlib.sha256(repo_uri.encode()) + cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') + if not cache_file.is_file(): + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + with urllib.request.urlopen(repo_uri) as s: + with cache_file.open('wb') as f: + shutil.copyfileobj(s, f) + else: + repo_json = pathlib.Path(repo_uri).resolve() + if not repo_json.is_file(): + return 1 + shutil.copy(repo_json, cache_file) return 0 -def register_default_gems_folder(json_data, - default_gems_folder: str, - remove: bool = False) -> int: +def register_repo(json_data: dict, + repo_uri: str or pathlib.Path, + remove: bool = False) -> int: + if not repo_uri: + logger.error(f'Repo URI cannot be empty.') + return 1 + + url = f'{repo_uri}/repo.json' + parsed_uri = urllib.parse.urlparse(url) + + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + while repo_uri in json_data['repos']: + json_data['repos'].remove(repo_uri) + else: + repo_uri = pathlib.Path(repo_uri).resolve() + while repo_uri.as_posix() in json_data['repos']: + json_data['repos'].remove(repo_uri.as_posix()) + if remove: - json_data['default_gems_folder'] = '' + logger.warn(f'Removing repo uri {repo_uri}.') + return 0 + + repo_sha256 = hashlib.sha256(url.encode()) + cache_file = get_o3de_cache_folder() / str(repo_sha256.hexdigest() + '.json') + + result = 0 + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + if not cache_file.is_file(): + with urllib.request.urlopen(url) as s: + with cache_file.open('wb') as f: + shutil.copyfileobj(s, f) + json_data['repos'].insert(0, repo_uri) else: - # make sure the path exists - default_gems_folder = pathlib.Path(default_gems_folder) - if not default_gems_folder.is_dir(): - logger.error(f'Default gems folder {default_gems_folder} does not exist.') - return 1 + if not cache_file.is_file(): + origin_file = pathlib.Path(url).resolve() + if not origin_file.is_file(): + return 1 + shutil.copy(origin_file, origin_file) + json_data['repos'].insert(0, repo_uri.as_posix()) - default_gems_folder = default_gems_folder.as_posix() - json_data['default_gems_folder'] = default_gems_folder + repo_set = set() + result = process_add_o3de_repo(cache_file, repo_set) - return 0 + return result -def register_default_templates_folder(json_data, - default_templates_folder: str, - remove: bool = False) -> int: +def register_default_engines_folder(json_data: dict, + default_engines_folder: str or pathlib.Path, + remove: bool = False) -> int: if remove: - json_data['default_templates_folder'] = '' - else: - # make sure the path exists - default_templates_folder = pathlib.Path(default_templates_folder) - if not default_templates_folder.is_dir(): - logger.error(f'Default templates folder {default_templates_folder} does not exist.') - return 1 + default_engines_folder = get_o3de_engines_folder() + + # make sure the path exists + default_engines_folder = pathlib.Path(default_engines_folder).resolve() + if not default_engines_folder.is_dir(): + logger.error(f'Default engines folder {default_engines_folder} does not exist.') + return 1 - default_templates_folder = default_templates_folder.as_posix() - json_data['default_templates_folder'] = default_templates_folder + default_engines_folder = default_engines_folder.as_posix() + json_data['default_engines_folder'] = default_engines_folder return 0 -def register(engine_path: str = None, - project_path: str = None, - gem_path: str = None, - template_path: str = None, - default_projects_folder: str = None, - default_gems_folder: str = None, - default_templates_folder: str = None, - repo_uri: str = None, - remove: bool = False) -> int: - """ - Adds/Updates entries to the .o3de/o3de_manifest.json +def register_default_projects_folder(json_data: dict, + default_projects_folder: str or pathlib.Path, + remove: bool = False) -> int: + if remove: + default_projects_folder = get_o3de_projects_folder() - :param engine_path: engine folder - :param project_path: project folder - :param gem_path: gem folder - :param template_path: template folder - :param default_projects_folder: default projects folder - :param default_gems_folder: default gems folder - :param default_templates_folder: default templates folder - :param repo_uri: repo uri - :param remove: add/remove the entries - :return: 0 for success or non 0 failure code - """ + # make sure the path exists + default_projects_folder = pathlib.Path(default_projects_folder).resolve() + if not default_projects_folder.is_dir(): + logger.error(f'Default projects folder {default_projects_folder} does not exist.') + return 1 - json_data = load_o3de_registry() + default_projects_folder = default_projects_folder.as_posix() + json_data['default_projects_folder'] = default_projects_folder - if isinstance(engine_path, str) or isinstance(engine_path, pathlib.PurePath): - if not engine_path: - logger.error(f'Engine path cannot be empty.') - return 1 - result = register_engine_path(json_data, engine_path, remove) + return 0 - elif isinstance(project_path, str) or isinstance(project_path, pathlib.PurePath): - if not project_path: - logger.error(f'Project path cannot be empty.') - return 1 - result = register_project_path(json_data, project_path, remove) - elif isinstance(gem_path, str) or isinstance(gem_path, pathlib.PurePath): +def register_default_gems_folder(json_data: dict, + default_gems_folder: str or pathlib.Path, + remove: bool = False) -> int: + if remove: + default_gems_folder = get_o3de_gems_folder() + + # make sure the path exists + default_gems_folder = pathlib.Path(default_gems_folder).resolve() + if not default_gems_folder.is_dir(): + logger.error(f'Default gems folder {default_gems_folder} does not exist.') + return 1 + + default_gems_folder = default_gems_folder.as_posix() + json_data['default_gems_folder'] = default_gems_folder + + return 0 + + +def register_default_templates_folder(json_data: dict, + default_templates_folder: str or pathlib.Path, + remove: bool = False) -> int: + if remove: + default_templates_folder = get_o3de_templates_folder() + + # make sure the path exists + default_templates_folder = pathlib.Path(default_templates_folder).resolve() + if not default_templates_folder.is_dir(): + logger.error(f'Default templates folder {default_templates_folder} does not exist.') + return 1 + + default_templates_folder = default_templates_folder.as_posix() + json_data['default_templates_folder'] = default_templates_folder + + return 0 + + +def register_default_restricted_folder(json_data: dict, + default_restricted_folder: str or pathlib.Path, + remove: bool = False) -> int: + if remove: + default_restricted_folder = get_o3de_restricted_folder() + + # make sure the path exists + default_restricted_folder = pathlib.Path(default_restricted_folder).resolve() + if not default_restricted_folder.is_dir(): + logger.error(f'Default restricted folder {default_restricted_folder} does not exist.') + return 1 + + default_restricted_folder = default_restricted_folder.as_posix() + json_data['default_restricted_folder'] = default_restricted_folder + + return 0 + + +def register(engine_path: str or pathlib.Path = None, + project_path: str or pathlib.Path = None, + gem_path: str or pathlib.Path = None, + template_path: str or pathlib.Path = None, + restricted_path: str or pathlib.Path = None, + repo_uri: str or pathlib.Path = None, + default_engines_folder: str or pathlib.Path = None, + default_projects_folder: str or pathlib.Path = None, + default_gems_folder: str or pathlib.Path = None, + default_templates_folder: str or pathlib.Path = None, + default_restricted_folder: str or pathlib.Path = None, + remove: bool = False + ) -> int: + """ + Adds/Updates entries to the .o3de/o3de_manifest.json + + :param engine_path: if engine folder is supplied the path will be added to the engine if it can, if not global + :param project_path: project folder + :param gem_path: gem folder + :param template_path: template folder + :param restricted_path: restricted folder + :param repo_uri: repo uri + :param default_engines_folder: default engines folder + :param default_projects_folder: default projects folder + :param default_gems_folder: default gems folder + :param default_templates_folder: default templates folder + :param default_restricted_folder: default restricted code folder + :param remove: add/remove the entries + + :return: 0 for success or non 0 failure code + """ + + json_data = load_o3de_manifest() + + result = 0 + + # do anything that could require a engine context first + if isinstance(project_path, str) or isinstance(project_path, pathlib.PurePath): + if not project_path: + logger.error(f'Project path cannot be empty.') + return 1 + result = register_project_path(json_data, project_path, remove, engine_path) + + elif isinstance(gem_path, str) or isinstance(gem_path, pathlib.PurePath): if not gem_path: logger.error(f'Gem path cannot be empty.') return 1 - result = register_gem_path(json_data, gem_path, remove) + result = register_gem_path(json_data, gem_path, remove, engine_path) elif isinstance(template_path, str) or isinstance(template_path, pathlib.PurePath): if not template_path: logger.error(f'Template path cannot be empty.') return 1 - result = register_template_path(json_data, template_path, remove) + result = register_template_path(json_data, template_path, remove, engine_path) + + elif isinstance(restricted_path, str) or isinstance(restricted_path, pathlib.PurePath): + if not restricted_path: + logger.error(f'Restricted path cannot be empty.') + return 1 + result = register_restricted_path(json_data, restricted_path, remove, engine_path) elif isinstance(repo_uri, str) or isinstance(repo_uri, pathlib.PurePath): if not repo_uri: @@ -493,6 +1299,9 @@ def register(engine_path: str = None, return 1 result = register_repo(json_data, repo_uri, remove) + elif isinstance(default_engines_folder, str) or isinstance(default_engines_folder, pathlib.PurePath): + result = register_default_engines_folder(json_data, default_engines_folder, remove) + elif isinstance(default_projects_folder, str) or isinstance(default_projects_folder, pathlib.PurePath): result = register_default_projects_folder(json_data, default_projects_folder, remove) @@ -502,578 +1311,2746 @@ def register(engine_path: str = None, elif isinstance(default_templates_folder, str) or isinstance(default_templates_folder, pathlib.PurePath): result = register_default_templates_folder(json_data, default_templates_folder, remove) + elif isinstance(default_restricted_folder, str) or isinstance(default_restricted_folder, pathlib.PurePath): + result = register_default_restricted_folder(json_data, default_restricted_folder, remove) + + # engine is done LAST + # Now that everything that could have an engine context is done, if the engine is supplied that means this is + # registering the engine itself + elif isinstance(engine_path, str) or isinstance(engine_path, pathlib.PurePath): + if not engine_path: + logger.error(f'Engine path cannot be empty.') + return 1 + result = register_engine_path(json_data, engine_path, remove) + if not result: - save_o3de_registry(json_data) + save_o3de_manifest(json_data) - return 0 + return result -def remove_invalid_o3de_objects(): - json_data = load_o3de_registry() +def remove_invalid_o3de_objects() -> None: + json_data = load_o3de_manifest() - for engine in json_data['engines']: - if not valid_o3de_engine_json(pathlib.Path(engine) / 'engine.json'): - logger.warn(f"Engine path {engine} is invalid.") - register(engine_path=engine, remove=True) + for engine_object in json_data['engines']: + engine_path = engine_object['path'] + if not valid_o3de_engine_json(pathlib.Path(engine_path).resolve() / 'engine.json'): + logger.warn(f"Engine path {engine_path} is invalid.") + register(engine_path=engine_path, remove=True) + else: + for project in engine_object['projects']: + if not valid_o3de_project_json(pathlib.Path(project).resolve() / 'project.json'): + logger.warn(f"Project path {project} is invalid.") + register(engine_path=engine_path, project_path=project, remove=True) + + for gem_path in engine_object['gems']: + if not valid_o3de_gem_json(pathlib.Path(gem_path).resolve() / 'gem.json'): + logger.warn(f"Gem path {gem_path} is invalid.") + register(engine_path=engine_path, gem_path=gem_path, remove=True) + + for template_path in engine_object['templates']: + if not valid_o3de_template_json(pathlib.Path(template_path).resolve() / 'template.json'): + logger.warn(f"Template path {template_path} is invalid.") + register(engine_path=engine_path, template_path=template_path, remove=True) + + for restricted in engine_object['restricted']: + if not valid_o3de_restricted_json(pathlib.Path(restricted).resolve() / 'restricted.json'): + logger.warn(f"Restricted path {restricted} is invalid.") + register(engine_path=engine_path, restricted_path=restricted, remove=True) + + for external in engine_object['external_subdirectories']: + external = pathlib.Path(external).resolve() + if not external.is_dir(): + logger.warn(f"External subdirectory {external} is invalid.") + remove_external_subdirectory(external) for project in json_data['projects']: - if not valid_o3de_project_json(pathlib.Path(project) / 'project.json'): + if not valid_o3de_project_json(pathlib.Path(project).resolve() / 'project.json'): logger.warn(f"Project path {project} is invalid.") register(project_path=project, remove=True) for gem in json_data['gems']: - if not valid_o3de_gem_json(pathlib.Path(gem) / 'gem.json'): + if not valid_o3de_gem_json(pathlib.Path(gem).resolve() / 'gem.json'): logger.warn(f"Gem path {gem} is invalid.") register(gem_path=gem, remove=True) for template in json_data['templates']: - if not valid_o3de_template_json(pathlib.Path(template) / 'template.json'): + if not valid_o3de_template_json(pathlib.Path(template).resolve() / 'template.json'): logger.warn(f"Template path {template} is invalid.") register(template_path=template, remove=True) - default_projects_folder = pathlib.Path(json_data['default_projects_folder']) + for restricted in json_data['restricted']: + if not valid_o3de_restricted_json(pathlib.Path(restricted).resolve() / 'restricted.json'): + logger.warn(f"Restricted path {restricted} is invalid.") + register(restricted_path=restricted, remove=True) + + default_engines_folder = pathlib.Path(json_data['default_engines_folder']).resolve() + if not default_engines_folder.is_dir(): + new_default_engines_folder = get_o3de_folder() / 'Engines' + new_default_engines_folder.mkdir(parents=True, exist_ok=True) + logger.warn( + f"Default engines folder {default_engines_folder} is invalid. Set default {new_default_engines_folder}") + register(default_engines_folder=new_default_engines_folder.as_posix()) + + default_projects_folder = pathlib.Path(json_data['default_projects_folder']).resolve() if not default_projects_folder.is_dir(): - new_default_projects_folder = get_home_folder() / 'my_o3de/projects' + new_default_projects_folder = get_o3de_folder() / 'Projects' new_default_projects_folder.mkdir(parents=True, exist_ok=True) - logger.warn(f"Default projects folder {default_projects_folder} is invalid. Set default {new_default_projects_folder}") + logger.warn( + f"Default projects folder {default_projects_folder} is invalid. Set default {new_default_projects_folder}") register(default_projects_folder=new_default_projects_folder.as_posix()) - default_gems_folder = pathlib.Path(json_data['default_gems_folder']) + default_gems_folder = pathlib.Path(json_data['default_gems_folder']).resolve() if not default_gems_folder.is_dir(): - new_default_gems_folder = get_home_folder() / 'my_o3de/gems' + new_default_gems_folder = get_o3de_folder() / 'Gems' new_default_gems_folder.mkdir(parents=True, exist_ok=True) - logger.warn(f"Default gems folder {default_gems_folder} is invalid. Set default {new_default_gems_folder}") + logger.warn(f"Default gems folder {default_gems_folder} is invalid." + f" Set default {new_default_gems_folder}") register(default_gems_folder=new_default_gems_folder.as_posix()) - default_templates_folder = pathlib.Path(json_data['default_templates_folder']) + default_templates_folder = pathlib.Path(json_data['default_templates_folder']).resolve() if not default_templates_folder.is_dir(): - new_default_templates_folder = get_home_folder() / 'my_o3de/templates' + new_default_templates_folder = get_o3de_folder() / 'Templates' new_default_templates_folder.mkdir(parents=True, exist_ok=True) - logger.warn(f"Default templates folder {default_templates_folder} is invalid. Set default {new_default_templates_folder}") + logger.warn( + f"Default templates folder {default_templates_folder} is invalid." + f" Set default {new_default_templates_folder}") register(default_templates_folder=new_default_templates_folder.as_posix()) + default_restricted_folder = pathlib.Path(json_data['default_restricted_folder']).resolve() + if not default_restricted_folder.is_dir(): + default_restricted_folder = get_o3de_folder() / 'Restricted' + default_restricted_folder.mkdir(parents=True, exist_ok=True) + logger.warn( + f"Default restricted folder {default_restricted_folder} is invalid." + f" Set default {default_restricted_folder}") + register(default_restricted_folder=default_restricted_folder.as_posix()) + + +def refresh_repos() -> int: + json_data = load_o3de_manifest() + + # clear the cache + cache_folder = get_o3de_cache_folder() + shutil.rmtree(cache_folder) + cache_folder = get_o3de_cache_folder() # will recreate it + + result = 0 + + # set will stop circular references + repo_set = set() + + for repo_uri in json_data['repos']: + if repo_uri not in repo_set: + repo_set.add(repo_uri) + + repo_uri = f'{repo_uri}/repo.json' + repo_sha256 = hashlib.sha256(repo_uri.encode()) + cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') + if not cache_file.is_file(): + parsed_uri = urllib.parse.urlparse(repo_uri) + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + with urllib.request.urlopen(repo_uri) as s: + with cache_file.open('wb') as f: + shutil.copyfileobj(s, f) + else: + origin_file = pathlib.Path(repo_uri).resolve() + if not origin_file.is_file(): + return 1 + shutil.copy(origin_file, cache_file) + + if not valid_o3de_repo_json(cache_file): + logger.error(f'Repo json {repo_uri} is not valid.') + cache_file.unlink() + return 1 + + last_failure = process_add_o3de_repo(cache_file, repo_set) + if last_failure: + result = last_failure + + return result + + +def search_repo(repo_set: set, + repo_json_data: dict, + engine_name: str = None, + project_name: str = None, + gem_name: str = None, + template_name: str = None, + restricted_name: str = None) -> dict or None: + cache_folder = get_o3de_cache_folder() + + if isinstance(engine_name, str) or isinstance(engine_name, pathlib.PurePath): + for engine_uri in repo_json_data['engines']: + engine_uri = f'{engine_uri}/engine.json' + engine_sha256 = hashlib.sha256(engine_uri.encode()) + engine_cache_file = cache_folder / str(engine_sha256.hexdigest() + '.json') + if engine_cache_file.is_file(): + with engine_cache_file.open('r') as f: + try: + engine_json_data = json.load(f) + except Exception as e: + logger.warn(f'{engine_cache_file} failed to load: {str(e)}') + else: + if engine_json_data['engine_name'] == engine_name: + return engine_json_data + + elif isinstance(project_name, str) or isinstance(project_name, pathlib.PurePath): + for project_uri in repo_json_data['projects']: + project_uri = f'{project_uri}/project.json' + project_sha256 = hashlib.sha256(project_uri.encode()) + project_cache_file = cache_folder / str(project_sha256.hexdigest() + '.json') + if project_cache_file.is_file(): + with project_cache_file.open('r') as f: + try: + project_json_data = json.load(f) + except Exception as e: + logger.warn(f'{project_cache_file} failed to load: {str(e)}') + else: + if project_json_data['project_name'] == project_name: + return project_json_data + + elif isinstance(gem_name, str) or isinstance(gem_name, pathlib.PurePath): + for gem_uri in repo_json_data['gems']: + gem_uri = f'{gem_uri}/gem.json' + gem_sha256 = hashlib.sha256(gem_uri.encode()) + gem_cache_file = cache_folder / str(gem_sha256.hexdigest() + '.json') + if gem_cache_file.is_file(): + with gem_cache_file.open('r') as f: + try: + gem_json_data = json.load(f) + except Exception as e: + logger.warn(f'{gem_cache_file} failed to load: {str(e)}') + else: + if gem_json_data['gem_name'] == gem_name: + return gem_json_data + + elif isinstance(template_name, str) or isinstance(template_name, pathlib.PurePath): + for template_uri in repo_json_data['templates']: + template_uri = f'{template_uri}/template.json' + template_sha256 = hashlib.sha256(template_uri.encode()) + template_cache_file = cache_folder / str(template_sha256.hexdigest() + '.json') + if template_cache_file.is_file(): + with template_cache_file.open('r') as f: + try: + template_json_data = json.load(f) + except Exception as e: + logger.warn(f'{template_cache_file} failed to load: {str(e)}') + else: + if template_json_data['template_name'] == template_name: + return template_json_data + + elif isinstance(restricted_name, str) or isinstance(restricted_name, pathlib.PurePath): + for restricted_uri in repo_json_data['restricted']: + restricted_uri = f'{restricted_uri}/restricted.json' + restricted_sha256 = hashlib.sha256(restricted_uri.encode()) + restricted_cache_file = cache_folder / str(restricted_sha256.hexdigest() + '.json') + if restricted_cache_file.is_file(): + with restricted_cache_file.open('r') as f: + try: + restricted_json_data = json.load(f) + except Exception as e: + logger.warn(f'{restricted_cache_file} failed to load: {str(e)}') + else: + if restricted_json_data['restricted_name'] == restricted_name: + return restricted_json_data + # recurse + else: + for repo_repo_uri in repo_json_data['repos']: + if repo_repo_uri not in repo_set: + repo_set.add(repo_repo_uri) + repo_repo_uri = f'{repo_repo_uri}/repo.json' + repo_repo_sha256 = hashlib.sha256(repo_repo_uri.encode()) + repo_repo_cache_file = cache_folder / str(repo_repo_sha256.hexdigest() + '.json') + if repo_repo_cache_file.is_file(): + with repo_repo_cache_file.open('r') as f: + try: + repo_repo_json_data = json.load(f) + except Exception as e: + logger.warn(f'{repo_repo_cache_file} failed to load: {str(e)}') + else: + item = search_repo(repo_set, + repo_repo_json_data, + engine_name, + project_name, + gem_name, + template_name) + if item: + return item + return None + + +def get_downloadable(engine_name: str = None, + project_name: str = None, + gem_name: str = None, + template_name: str = None, + restricted_name: str = None) -> dict or None: + json_data = load_o3de_manifest() + cache_folder = get_o3de_cache_folder() + repo_set = set() + for repo_uri in json_data['repos']: + if repo_uri not in repo_set: + repo_set.add(repo_uri) + repo_uri = f'{repo_uri}/repo.json' + repo_sha256 = hashlib.sha256(repo_uri.encode()) + repo_cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') + if repo_cache_file.is_file(): + with repo_cache_file.open('r') as f: + try: + repo_json_data = json.load(f) + except Exception as e: + logger.warn(f'{repo_cache_file} failed to load: {str(e)}') + else: + item = search_repo(repo_set, + repo_json_data, + engine_name, + project_name, + gem_name, + template_name, + restricted_name) + if item: + return item + return None + + +def get_registered(engine_name: str = None, + project_name: str = None, + gem_name: str = None, + template_name: str = None, + default_folder: str = None, + repo_name: str = None, + restricted_name: str = None) -> pathlib.Path or None: + json_data = load_o3de_manifest() + + # check global first then this engine + if isinstance(engine_name, str): + for engine in json_data['engines']: + engine_path = pathlib.Path(engine['path']).resolve() + engine_json = engine_path / 'engine.json' + with engine_json.open('r') as f: + try: + engine_json_data = json.load(f) + except Exception as e: + logger.warn(f'{engine_json} failed to load: {str(e)}') + else: + this_engines_name = engine_json_data['engine_name'] + if this_engines_name == engine_name: + return engine_path + + elif isinstance(project_name, str): + engine_object = find_engine_data(json_data) + projects = json_data['projects'].copy() + projects.extend(engine_object['projects']) + for project_path in projects: + project_path = pathlib.Path(project_path).resolve() + project_json = project_path / 'project.json' + with project_json.open('r') as f: + try: + project_json_data = json.load(f) + except Exception as e: + logger.warn(f'{project_json} failed to load: {str(e)}') + else: + this_projects_name = project_json_data['project_name'] + if this_projects_name == project_name: + return project_path + + elif isinstance(gem_name, str): + engine_object = find_engine_data(json_data) + gems = json_data['gems'].copy() + gems.extend(engine_object['gems']) + for gem_path in gems: + gem_path = pathlib.Path(gem_path).resolve() + gem_json = gem_path / 'gem.json' + with gem_json.open('r') as f: + try: + gem_json_data = json.load(f) + except Exception as e: + logger.warn(f'{gem_json} failed to load: {str(e)}') + else: + this_gems_name = gem_json_data['gem_name'] + if this_gems_name == gem_name: + return gem_path + + elif isinstance(template_name, str): + engine_object = find_engine_data(json_data) + templates = json_data['templates'].copy() + templates.extend(engine_object['templates']) + for template_path in templates: + template_path = pathlib.Path(template_path).resolve() + template_json = template_path / 'template.json' + with template_json.open('r') as f: + try: + template_json_data = json.load(f) + except Exception as e: + logger.warn(f'{template_path} failed to load: {str(e)}') + else: + this_templates_name = template_json_data['template_name'] + if this_templates_name == template_name: + return template_path + + elif isinstance(restricted_name, str): + engine_object = find_engine_data(json_data) + restricted = json_data['restricted'].copy() + restricted.extend(engine_object['restricted']) + for restricted_path in restricted: + restricted_path = pathlib.Path(restricted_path).resolve() + restricted_json = restricted_path / 'restricted.json' + with restricted_json.open('r') as f: + try: + restricted_json_data = json.load(f) + except Exception as e: + logger.warn(f'{restricted_json} failed to load: {str(e)}') + else: + this_restricted_name = restricted_json_data['restricted_name'] + if this_restricted_name == restricted_name: + return restricted_path + + elif isinstance(default_folder, str): + if default_folder == 'engines': + default_engines_folder = pathlib.Path(json_data['default_engines_folder']).resolve() + return default_engines_folder + elif default_folder == 'projects': + default_projects_folder = pathlib.Path(json_data['default_projects_folder']).resolve() + return default_projects_folder + elif default_folder == 'gems': + default_gems_folder = pathlib.Path(json_data['default_gems_folder']).resolve() + return default_gems_folder + elif default_folder == 'templates': + default_templates_folder = pathlib.Path(json_data['default_templates_folder']).resolve() + return default_templates_folder + elif default_folder == 'restricted': + default_restricted_folder = pathlib.Path(json_data['default_restricted_folder']).resolve() + return default_restricted_folder + + elif isinstance(repo_name, str): + cache_folder = get_o3de_cache_folder() + for repo_uri in json_data['repos']: + repo_uri = pathlib.Path(repo_uri).resolve() + repo_sha256 = hashlib.sha256(repo_uri.encode()) + cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') + if cache_file.is_file(): + repo = pathlib.Path(cache_file).resolve() + with repo.open('r') as f: + try: + repo_json_data = json.load(f) + except Exception as e: + logger.warn(f'{cache_file} failed to load: {str(e)}') + else: + this_repos_name = repo_json_data['repo_name'] + if this_repos_name == repo_name: + return repo_uri + return None + + +def print_engines_data(engines_data: dict) -> None: + print('\n') + print("Engines================================================") + for engine_object in engines_data: + # if it's not local it should be in the cache + engine_uri = engine_object['path'] + parsed_uri = urllib.parse.urlparse(engine_uri) + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + repo_sha256 = hashlib.sha256(engine_uri.encode()) + cache_folder = get_o3de_cache_folder() + engine = cache_folder / str(repo_sha256.hexdigest() + '.json') + print(f'{engine_uri}/engine.json cached as:') + else: + engine_json = pathlib.Path(engine_uri).resolve() / 'engine.json' + + with engine_json.open('r') as f: + try: + engine_json_data = json.load(f) + except Exception as e: + logger.warn(f'{engine_json} failed to load: {str(e)}') + else: + print(engine_json) + print(json.dumps(engine_json_data, indent=4)) + print('\n') + + +def print_projects_data(projects_data: dict) -> None: + print('\n') + print("Projects================================================") + for project_uri in projects_data: + # if it's not local it should be in the cache + parsed_uri = urllib.parse.urlparse(project_uri) + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + repo_sha256 = hashlib.sha256(project_uri.encode()) + cache_folder = get_o3de_cache_folder() + project_json = cache_folder / str(repo_sha256.hexdigest() + '.json') + else: + project_json = pathlib.Path(project_uri).resolve() / 'project.json' + + with project_json.open('r') as f: + try: + project_json_data = json.load(f) + except Exception as e: + logger.warn(f'{project_json} failed to load: {str(e)}') + else: + print(project_json) + print(json.dumps(project_json_data, indent=4)) + print('\n') + + +def print_gems_data(gems_data: dict) -> None: + print('\n') + print("Gems================================================") + for gem_uri in gems_data: + # if it's not local it should be in the cache + parsed_uri = urllib.parse.urlparse(gem_uri) + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + repo_sha256 = hashlib.sha256(gem_uri.encode()) + cache_folder = get_o3de_cache_folder() + gem_json = cache_folder / str(repo_sha256.hexdigest() + '.json') + else: + gem_json = pathlib.Path(gem_uri).resolve() / 'gem.json' + + with gem_json.open('r') as f: + try: + gem_json_data = json.load(f) + except Exception as e: + logger.warn(f'{gem_json} failed to load: {str(e)}') + else: + print(gem_json) + print(json.dumps(gem_json_data, indent=4)) + print('\n') + + +def print_templates_data(templates_data: dict) -> None: + print('\n') + print("Templates================================================") + for template_uri in templates_data: + # if it's not local it should be in the cache + parsed_uri = urllib.parse.urlparse(template_uri) + if parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + repo_sha256 = hashlib.sha256(template_uri.encode()) + cache_folder = get_o3de_cache_folder() + template_json = cache_folder / str(repo_sha256.hexdigest() + '.json') + else: + template_json = pathlib.Path(template_uri).resolve() / 'template.json' + + with template_json.open('r') as f: + try: + template_json_data = json.load(f) + except Exception as e: + logger.warn(f'{template_json} failed to load: {str(e)}') + else: + print(template_json) + print(json.dumps(template_json_data, indent=4)) + print('\n') + + +def print_repos_data(repos_data: dict) -> None: + print('\n') + print("Repos================================================") + cache_folder = get_o3de_cache_folder() + for repo_uri in repos_data: + repo_sha256 = hashlib.sha256(repo_uri.encode()) + cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') + if valid_o3de_repo_json(cache_file): + with cache_file.open('r') as s: + try: + repo_json_data = json.load(s) + except Exception as e: + logger.warn(f'{cache_file} failed to load: {str(e)}') + else: + print(f'{repo_uri}/repo.json cached as:') + print(cache_file) + print(json.dumps(repo_json_data, indent=4)) + print('\n') + + +def print_restricted_data(restricted_data: dict) -> None: + print('\n') + print("Restricted================================================") + for restricted_path in restricted_data: + restricted_json = pathlib.Path(restricted_path).resolve() / 'restricted.json' + with restricted_json.open('r') as f: + try: + restricted_json_data = json.load(f) + except Exception as e: + logger.warn(f'{restricted_json} failed to load: {str(e)}') + else: + print(restricted_json) + print(json.dumps(restricted_json_data, indent=4)) + print('\n') + + +def get_this_engine() -> dict: + json_data = load_o3de_manifest() + engine_data = find_engine_data(json_data) + return engine_data + + +def get_engines() -> dict: + json_data = load_o3de_manifest() + return json_data['engines'] + + +def get_projects() -> dict: + json_data = load_o3de_manifest() + return json_data['projects'] + + +def get_gems() -> dict: + json_data = load_o3de_manifest() + return json_data['gems'] + + +def get_templates() -> dict: + json_data = load_o3de_manifest() + return json_data['templates'] + + +def get_restricted() -> dict: + json_data = load_o3de_manifest() + return json_data['restricted'] + + +def get_repos() -> dict: + json_data = load_o3de_manifest() + return json_data['repos'] + + +def get_engine_projects() -> dict: + json_data = load_o3de_manifest() + engine_object = find_engine_data(json_data) + return engine_object['projects'] + + +def get_engine_gems() -> dict: + json_data = load_o3de_manifest() + engine_object = find_engine_data(json_data) + return engine_object['gems'] + + +def get_engine_templates() -> dict: + json_data = load_o3de_manifest() + engine_object = find_engine_data(json_data) + return engine_object['templates'] + + +def get_engine_restricted() -> dict: + json_data = load_o3de_manifest() + engine_object = find_engine_data(json_data) + return engine_object['restricted'] + + +def get_external_subdirectories() -> dict: + json_data = load_o3de_manifest() + engine_object = find_engine_data(json_data) + return engine_object['external_subdirectories'] + + +def get_all_projects() -> dict: + json_data = load_o3de_manifest() + engine_object = find_engine_data(json_data) + projects_data = json_data['projects'].copy() + projects_data.extend(engine_object['projects']) + return projects_data + + +def get_all_gems() -> dict: + json_data = load_o3de_manifest() + engine_object = find_engine_data(json_data) + gems_data = json_data['gems'].copy() + gems_data.extend(engine_object['gems']) + return gems_data + + +def get_all_templates() -> dict: + json_data = load_o3de_manifest() + engine_object = find_engine_data(json_data) + templates_data = json_data['templates'].copy() + templates_data.extend(engine_object['templates']) + return templates_data + + +def get_all_restricted() -> dict: + json_data = load_o3de_manifest() + engine_object = find_engine_data(json_data) + restricted_data = json_data['restricted'].copy() + restricted_data.extend(engine_object['restricted']) + return restricted_data + + +def print_this_engine(verbose: int) -> None: + engine_data = get_this_engine() + print(json.dumps(engine_data, indent=4)) + if verbose > 0: + print_engines_data(engine_data) + + +def print_engines(verbose: int) -> None: + engines_data = get_engines() + print(json.dumps(engines_data, indent=4)) + if verbose > 0: + print_engines_data(engines_data) + + +def print_projects(verbose: int) -> None: + projects_data = get_projects() + print(json.dumps(projects_data, indent=4)) + if verbose > 0: + print_projects_data(projects_data) + + +def print_gems(verbose: int) -> None: + gems_data = get_gems() + print(json.dumps(gems_data, indent=4)) + if verbose > 0: + print_gems_data(gems_data) + + +def print_templates(verbose: int) -> None: + templates_data = get_templates() + print(json.dumps(templates_data, indent=4)) + if verbose > 0: + print_templates_data(templates_data) + + +def print_restricted(verbose: int) -> None: + restricted_data = get_restricted() + print(json.dumps(restricted_data, indent=4)) + if verbose > 0: + print_restricted_data(restricted_data) + + +def register_show_repos(verbose: int) -> None: + repos_data = get_repos() + print(json.dumps(repos_data, indent=4)) + if verbose > 0: + print_repos_data(repos_data) + + +def print_engine_projects(verbose: int) -> None: + engine_projects_data = get_engine_projects() + print(json.dumps(engine_projects_data, indent=4)) + if verbose > 0: + print_projects_data(engine_projects_data) + + +def print_engine_gems(verbose: int) -> None: + engine_gems_data = get_engine_gems() + print(json.dumps(engine_gems_data, indent=4)) + if verbose > 0: + print_gems_data(engine_gems_data) + + +def print_engine_templates(verbose: int) -> None: + engine_templates_data = get_engine_templates() + print(json.dumps(engine_templates_data, indent=4)) + if verbose > 0: + print_templates_data(engine_templates_data) + + +def print_engine_restricted(verbose: int) -> None: + engine_restricted_data = get_engine_restricted() + print(json.dumps(engine_restricted_data, indent=4)) + if verbose > 0: + print_restricted_data(engine_restricted_data) + + +def print_external_subdirectories(verbose: int) -> None: + external_subdirs_data = get_external_subdirectories() + print(json.dumps(external_subdirs_data, indent=4)) + + +def print_all_projects(verbose: int) -> None: + all_projects_data = get_all_projects() + print(json.dumps(all_projects_data, indent=4)) + if verbose > 0: + print_projects_data(all_projects_data) + + +def print_all_gems(verbose: int) -> None: + all_gems_data = get_all_gems() + print(json.dumps(all_gems_data, indent=4)) + if verbose > 0: + print_gems_data(all_gems_data) + + +def print_all_templates(verbose: int) -> None: + all_templates_data = get_all_templates() + print(json.dumps(all_templates_data, indent=4)) + if verbose > 0: + print_templates_data(all_templates_data) + + +def print_all_restricted(verbose: int) -> None: + all_restricted_data = get_all_restricted() + print(json.dumps(all_restricted_data, indent=4)) + if verbose > 0: + print_restricted_data(all_restricted_data) + + +def register_show(verbose: int) -> None: + json_data = load_o3de_manifest() + print(f"{get_o3de_manifest()}:") + print(json.dumps(json_data, indent=4)) + + if verbose > 0: + print_engines_data(get_engines()) + print_projects_data(get_all_projects()) + print_gems_data(get_gems()) + print_templates_data(get_all_templates()) + print_restricted_data(get_all_restricted()) + print_repos_data(get_repos()) + + +def find_engine_data(json_data: dict, + engine_path: str or pathlib.Path = None) -> dict or None: + if not engine_path: + engine_path = get_this_engine_path() + engine_path = pathlib.Path(engine_path).resolve() + + for engine_object in json_data['engines']: + engine_object_path = pathlib.Path(engine_object['path']).resolve() + if engine_path == engine_object_path: + return engine_object + + return None + + +def get_engine_data(engine_name: str = None, + engine_path: str or pathlib.Path = None, ) -> dict or None: + if not engine_name and not engine_path: + logger.error('Must specify either a Engine name or Engine Path.') + return 1 + + if engine_name and not engine_path: + engine_path = get_registered(engine_name=engine_name) + + if not engine_path: + logger.error(f'Engine Path {engine_path} has not been registered.') + return 1 + + engine_path = pathlib.Path(engine_path).resolve() + engine_json = engine_path / 'engine.json' + if not engine_json.is_file(): + logger.error(f'Engine json {engine_json} is not present.') + return 1 + if not valid_o3de_engine_json(engine_json): + logger.error(f'Engine json {engine_json} is not valid.') + return 1 + + with engine_json.open('r') as f: + try: + engine_json_data = json.load(f) + except Exception as e: + logger.warn(f'{engine_json} failed to load: {str(e)}') + else: + return engine_json_data + + return None + + +def get_project_data(project_name: str = None, + project_path: str or pathlib.Path = None, ) -> dict or None: + if not project_name and not project_path: + logger.error('Must specify either a Project name or Project Path.') + return 1 + + if project_name and not project_path: + project_path = get_registered(project_name=project_name) + + if not project_path: + logger.error(f'Project Path {project_path} has not been registered.') + return 1 + + project_path = pathlib.Path(project_path).resolve() + project_json = project_path / 'project.json' + if not project_json.is_file(): + logger.error(f'Project json {project_json} is not present.') + return 1 + if not valid_o3de_project_json(project_json): + logger.error(f'Project json {project_json} is not valid.') + return 1 + + with project_json.open('r') as f: + try: + project_json_data = json.load(f) + except Exception as e: + logger.warn(f'{project_json} failed to load: {str(e)}') + else: + return project_json_data + + return None + + +def get_gem_data(gem_name: str = None, + gem_path: str or pathlib.Path = None, ) -> dict or None: + if not gem_name and not gem_path: + logger.error('Must specify either a Gem name or Gem Path.') + return 1 + + if gem_name and not gem_path: + gem_path = get_registered(gem_name=gem_name) + + if not gem_path: + logger.error(f'Gem Path {gem_path} has not been registered.') + return 1 + + gem_path = pathlib.Path(gem_path).resolve() + gem_json = gem_path / 'gem.json' + if not gem_json.is_file(): + logger.error(f'Gem json {gem_json} is not present.') + return 1 + if not valid_o3de_gem_json(gem_json): + logger.error(f'Gem json {gem_json} is not valid.') + return 1 + + with gem_json.open('r') as f: + try: + gem_json_data = json.load(f) + except Exception as e: + logger.warn(f'{gem_json} failed to load: {str(e)}') + else: + return gem_json_data + + return None + + +def get_template_data(template_name: str = None, + template_path: str or pathlib.Path = None, ) -> dict or None: + if not template_name and not template_path: + logger.error('Must specify either a Template name or Template Path.') + return 1 + + if template_name and not template_path: + template_path = get_registered(template_name=template_name) + + if not template_path: + logger.error(f'Template Path {template_path} has not been registered.') + return 1 + + template_path = pathlib.Path(template_path).resolve() + template_json = template_path / 'template.json' + if not template_json.is_file(): + logger.error(f'Template json {template_json} is not present.') + return 1 + if not valid_o3de_template_json(template_json): + logger.error(f'Template json {template_json} is not valid.') + return 1 + + with template_json.open('r') as f: + try: + template_json_data = json.load(f) + except Exception as e: + logger.warn(f'{template_json} failed to load: {str(e)}') + else: + return template_json_data + + return None + + +def get_restricted_data(restricted_name: str = None, + restricted_path: str or pathlib.Path = None, ) -> dict or None: + if not restricted_name and not restricted_path: + logger.error('Must specify either a Restricted name or Restricted Path.') + return 1 + + if restricted_name and not restricted_path: + restricted_path = get_registered(restricted_name=restricted_name) + + if not restricted_path: + logger.error(f'Restricted Path {restricted_path} has not been registered.') + return 1 + + restricted_path = pathlib.Path(restricted_path).resolve() + restricted_json = restricted_path / 'restricted.json' + if not restricted_json.is_file(): + logger.error(f'Restricted json {restricted_json} is not present.') + return 1 + if not valid_o3de_restricted_json(restricted_json): + logger.error(f'Restricted json {restricted_json} is not valid.') + return 1 + + with restricted_json.open('r') as f: + try: + restricted_json_data = json.load(f) + except Exception as e: + logger.warn(f'{restricted_json} failed to load: {str(e)}') + else: + return restricted_json_data + + return None + + +def get_downloadables() -> dict: + json_data = load_o3de_manifest() + downloadable_data = {} + downloadable_data.update({'engines': []}) + downloadable_data.update({'projects': []}) + downloadable_data.update({'gems': []}) + downloadable_data.update({'templates': []}) + downloadable_data.update({'restricted': []}) + + def recurse_downloadables(repo_uri: str or pathlib.Path) -> None: + cache_folder = get_o3de_cache_folder() + repo_sha256 = hashlib.sha256(repo_uri.encode()) + cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') + if valid_o3de_repo_json(cache_file): + with cache_file.open('r') as s: + try: + repo_json_data = json.load(s) + except Exception as e: + logger.warn(f'{cache_file} failed to load: {str(e)}') + else: + for engine in repo_json_data['engines']: + if engine not in downloadable_data['engines']: + downloadable_data['engines'].append(engine) + + for project in repo_json_data['projects']: + if project not in downloadable_data['projects']: + downloadable_data['projects'].append(project) + + for gem in repo_json_data['gems']: + if gem not in downloadable_data['gems']: + downloadable_data['gems'].append(gem) + + for template in repo_json_data['templates']: + if template not in downloadable_data['templates']: + downloadable_data['templates'].append(template) + + for restricted in repo_json_data['restricted']: + if restricted not in downloadable_data['restricted']: + downloadable_data['restricted'].append(restricted) + + for repo in repo_json_data['repos']: + if repo not in downloadable_data['repos']: + downloadable_data['repos'].append(repo) + + for repo in downloadable_data['repos']: + recurse_downloadables(repo) + + for repo_entry in json_data['repos']: + recurse_downloadables(repo_entry) + return downloadable_data + + +def get_downloadable_engines() -> dict: + downloadable_data = get_downloadables() + return downloadable_data['engines'] + + +def get_downloadable_projects() -> dict: + downloadable_data = get_downloadables() + return downloadable_data['projects'] + + +def get_downloadable_gems() -> dict: + downloadable_data = get_downloadables() + return downloadable_data['gems'] + + +def get_downloadable_templates() -> dict: + downloadable_data = get_downloadables() + return downloadable_data['templates'] + + +def get_downloadable_restricted() -> dict: + downloadable_data = get_downloadables() + return downloadable_data['restricted'] + + +def print_downloadable_engines(verbose: int) -> None: + downloadable_engines = get_downloadable_engines() + for engine_data in downloadable_engines: + print(json.dumps(engine_data, indent=4)) + if verbose > 0: + print_engines_data(downloadable_engines) + + +def print_downloadable_projects(verbose: int) -> None: + downloadable_projects = get_downloadable_projects() + for projects_data in downloadable_projects: + print(json.dumps(projects_data, indent=4)) + if verbose > 0: + print_projects_data(downloadable_projects) + + +def print_downloadable_gems(verbose: int) -> None: + downloadable_gems = get_downloadable_gems() + for gem_data in downloadable_gems: + print(json.dumps(gem_data, indent=4)) + if verbose > 0: + print_gems_data(downloadable_gems) + + +def print_downloadable_templates(verbose: int) -> None: + downloadable_templates = get_downloadable_templates() + for template_data in downloadable_templates: + print(json.dumps(template_data, indent=4)) + if verbose > 0: + print_engines_data(downloadable_templates) + + +def print_downloadable_restricted(verbose: int) -> None: + downloadable_restricted = get_downloadable_restricted() + for restricted_data in downloadable_restricted: + print(json.dumps(restricted_data, indent=4)) + if verbose > 0: + print_engines_data(downloadable_restricted) + + +def print_downloadables(verbose: int) -> None: + downloadable_data = get_downloadables() + print(json.dumps(downloadable_data, indent=4)) + if verbose > 0: + print_engines_data(downloadable_data['engines']) + print_projects_data(downloadable_data['projects']) + print_gems_data(downloadable_data['gems']) + print_templates_data(downloadable_data['templates']) + print_restricted_data(downloadable_data['templates']) + + +def download_engine(engine_name: str, + dest_path: str) -> int: + if not dest_path: + dest_path = get_registered(default_folder='engines') + if not dest_path: + logger.error(f'Destination path not cannot be empty.') + return 1 + + dest_path = pathlib.Path(dest_path).resolve() + dest_path.mkdir(exist_ok=True) + + download_path = get_o3de_download_folder() / 'engines' / engine_name + download_path.mkdir(exist_ok=True) + download_zip_path = download_path / 'engine.zip' + + downloadable_engine_data = get_downloadable(engine_name=engine_name) + if not downloadable_engine_data: + logger.error(f'Downloadable engine {engine_name} not found.') + return 1 + + origin = downloadable_engine_data['origin'] + url = f'{origin}/project.zip' + parsed_uri = urllib.parse.urlparse(url) + + if download_zip_path.is_file(): + logger.warn(f'Project already downloaded to {download_zip_path}.') + elif parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + with urllib.request.urlopen(url) as s: + with download_zip_path.open('wb') as f: + shutil.copyfileobj(s, f) + else: + origin_file = pathlib.Path(url).resolve() + if not origin_file.is_file(): + return 1 + shutil.copy(origin_file, download_zip_path) + + if not zipfile.is_zipfile(download_zip_path): + logger.error(f"Engine zip {download_zip_path} is invalid.") + download_zip_path.unlink() + return 1 + + # if the engine.json has a sha256 check it against a sha256 of the zip + try: + sha256A = downloadable_engine_data['sha256'] + except Exception as e: + logger.warn(f'SECURITY WARNING: The advertised engine you downloaded has no "sha256"!!! Be VERY careful!!!' + f' We cannot verify this is the actually the advertised engine!!!') + else: + sha256B = hashlib.sha256(download_zip_path.open('rb').read()).hexdigest() + if sha256A != sha256B: + logger.error(f'SECURITY VIOLATION: Downloaded engine.zip sha256 {sha256B} does not match' + f' the advertised "sha256":{sha256A} in the engine.json. Deleting unzipped files!!!') + shutil.rmtree(dest_path) + return 1 + + dest_engine_folder = dest_path / engine_name + if dest_engine_folder.is_dir(): + backup_folder(dest_engine_folder) + with zipfile.ZipFile(download_zip_path, 'r') as project_zip: + try: + project_zip.extractall(dest_path) + except Exception as e: + logger.error(f'UnZip exception:{str(e)}') + shutil.rmtree(dest_path) + return 1 + + unzipped_engine_json = dest_engine_folder / 'engine.json' + if not unzipped_engine_json.is_file(): + logger.error(f'Engine json {unzipped_engine_json} is missing.') + return 1 + + if not valid_o3de_engine_json(unzipped_engine_json): + logger.error(f'Engine json {unzipped_engine_json} is invalid.') + return 1 + + # remove the sha256 if present in the advertised downloadable engine.json + # then compare it to the engine.json in the zip, they should now be identical + try: + del downloadable_engine_data['sha256'] + except Exception as e: + pass + + sha256A = hashlib.sha256(json.dumps(downloadable_engine_data, indent=4).encode('utf8')).hexdigest() + with unzipped_engine_json.open('r') as s: + try: + unzipped_engine_json_data = json.load(s) + except Exception as e: + logger.error(f'Failed to read engine json {unzipped_engine_json}. Unable to confirm this' + f' is the same template that was advertised.') + return 1 + sha256B = hashlib.sha256(json.dumps(unzipped_engine_json_data, indent=4).encode('utf8')).hexdigest() + if sha256A != sha256B: + logger.error(f'SECURITY VIOLATION: Downloaded engine.json does not match' + f' the advertised engine.json. Deleting unzipped files!!!') + shutil.rmtree(dest_path) + return 1 + + return 0 + + +def download_project(project_name: str, + dest_path: str or pathlib.Path) -> int: + if not dest_path: + dest_path = get_registered(default_folder='projects') + if not dest_path: + logger.error(f'Destination path not specified and not default projects path.') + return 1 + + dest_path = pathlib.Path(dest_path).resolve() + dest_path.mkdir(exist_ok=True, parents=True) + + download_path = get_o3de_download_folder() / 'projects' / project_name + download_path.mkdir(exist_ok=True, parents=True) + download_zip_path = download_path / 'project.zip' + + downloadable_project_data = get_downloadable(project_name=project_name) + if not downloadable_project_data: + logger.error(f'Downloadable project {project_name} not found.') + return 1 + + origin = downloadable_project_data['origin'] + url = f'{origin}/project.zip' + parsed_uri = urllib.parse.urlparse(url) + + if download_zip_path.is_file(): + logger.warn(f'Project already downloaded to {download_zip_path}.') + elif parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + with urllib.request.urlopen(url) as s: + with download_zip_path.open('wb') as f: + shutil.copyfileobj(s, f) + else: + origin_file = pathlib.Path(url).resolve() + if not origin_file.is_file(): + return 1 + shutil.copy(origin_file, download_zip_path) + + if not zipfile.is_zipfile(download_zip_path): + logger.error(f"Project zip {download_zip_path} is invalid.") + download_zip_path.unlink() + return 1 + + # if the project.json has a sha256 check it against a sha256 of the zip + try: + sha256A = downloadable_project_data['sha256'] + except Exception as e: + logger.warn(f'SECURITY WARNING: The advertised project you downloaded has no "sha256"!!! Be VERY careful!!!' + f' We cannot verify this is the actually the advertised project!!!') + else: + sha256B = hashlib.sha256(download_zip_path.open('rb').read()).hexdigest() + if sha256A != sha256B: + logger.error(f'SECURITY VIOLATION: Downloaded project.zip sha256 {sha256B} does not match' + f' the advertised "sha256":{sha256A} in the project.json. Deleting unzipped files!!!') + shutil.rmtree(dest_path) + return 1 + + dest_project_folder = dest_path / project_name + if dest_project_folder.is_dir(): + backup_folder(dest_project_folder) + with zipfile.ZipFile(download_zip_path, 'r') as project_zip: + try: + project_zip.extractall(dest_project_folder) + except Exception as e: + logger.error(f'UnZip exception:{str(e)}') + shutil.rmtree(dest_path) + return 1 + + unzipped_project_json = dest_project_folder / 'project.json' + if not unzipped_project_json.is_file(): + logger.error(f'Project json {unzipped_project_json} is missing.') + return 1 + + if not valid_o3de_project_json(unzipped_project_json): + logger.error(f'Project json {unzipped_project_json} is invalid.') + return 1 + + # remove the sha256 if present in the advertised downloadable project.json + # then compare it to the project.json in the zip, they should now be identical + try: + del downloadable_project_data['sha256'] + except Exception as e: + pass + + sha256A = hashlib.sha256(json.dumps(downloadable_project_data, indent=4).encode('utf8')).hexdigest() + with unzipped_project_json.open('r') as s: + try: + unzipped_project_json_data = json.load(s) + except Exception as e: + logger.error(f'Failed to read Project json {unzipped_project_json}. Unable to confirm this' + f' is the same project that was advertised.') + return 1 + sha256B = hashlib.sha256(json.dumps(unzipped_project_json_data, indent=4).encode('utf8')).hexdigest() + if sha256A != sha256B: + logger.error(f'SECURITY VIOLATION: Downloaded project.json does not match' + f' the advertised project.json. Deleting unzipped files!!!') + shutil.rmtree(dest_path) + return 1 + + return 0 + + +def download_gem(gem_name: str, + dest_path: str or pathlib.Path) -> int: + if not dest_path: + dest_path = get_registered(default_folder='gems') + if not dest_path: + logger.error(f'Destination path not cannot be empty.') + return 1 + + dest_path = pathlib.Path(dest_path).resolve() + dest_path.mkdir(exist_ok=True, parents=True) + + download_path = get_o3de_download_folder() / 'gems' / gem_name + download_path.mkdir(exist_ok=True, parents=True) + download_zip_path = download_path / 'gem.zip' + + downloadable_gem_data = get_downloadable(gem_name=gem_name) + if not downloadable_gem_data: + logger.error(f'Downloadable gem {gem_name} not found.') + return 1 + + origin = downloadable_gem_data['origin'] + url = f'{origin}/gem.zip' + parsed_uri = urllib.parse.urlparse(url) + + if download_zip_path.is_file(): + logger.warn(f'Project already downloaded to {download_zip_path}.') + elif parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + with urllib.request.urlopen(url) as s: + with download_zip_path.open('wb') as f: + shutil.copyfileobj(s, f) + else: + origin_file = pathlib.Path(url).resolve() + if not origin_file.is_file(): + return 1 + shutil.copy(origin_file, download_zip_path) + + if not zipfile.is_zipfile(download_zip_path): + logger.error(f"Gem zip {download_zip_path} is invalid.") + download_zip_path.unlink() + return 1 + + # if the gem.json has a sha256 check it against a sha256 of the zip + try: + sha256A = downloadable_gem_data['sha256'] + except Exception as e: + logger.warn(f'SECURITY WARNING: The advertised gem you downloaded has no "sha256"!!! Be VERY careful!!!' + f' We cannot verify this is the actually the advertised gem!!!') + else: + sha256B = hashlib.sha256(download_zip_path.open('rb').read()).hexdigest() + if sha256A != sha256B: + logger.error(f'SECURITY VIOLATION: Downloaded gem.zip sha256 {sha256B} does not match' + f' the advertised "sha256":{sha256A} in the gem.json. Deleting unzipped files!!!') + shutil.rmtree(dest_path) + return 1 + + dest_gem_folder = dest_path / gem_name + if dest_gem_folder.is_dir(): + backup_folder(dest_gem_folder) + with zipfile.ZipFile(download_zip_path, 'r') as gem_zip: + try: + gem_zip.extractall(dest_path) + except Exception as e: + logger.error(f'UnZip exception:{str(e)}') + shutil.rmtree(dest_path) + return 1 + + unzipped_gem_json = dest_gem_folder / 'gem.json' + if not unzipped_gem_json.is_file(): + logger.error(f'Engine json {unzipped_gem_json} is missing.') + return 1 + + if not valid_o3de_engine_json(unzipped_gem_json): + logger.error(f'Engine json {unzipped_gem_json} is invalid.') + return 1 + + # remove the sha256 if present in the advertised downloadable gem.json + # then compare it to the gem.json in the zip, they should now be identical + try: + del downloadable_gem_data['sha256'] + except Exception as e: + pass + + sha256A = hashlib.sha256(json.dumps(downloadable_gem_data, indent=4).encode('utf8')).hexdigest() + with unzipped_gem_json.open('r') as s: + try: + unzipped_gem_json_data = json.load(s) + except Exception as e: + logger.error(f'Failed to read gem json {unzipped_gem_json}. Unable to confirm this' + f' is the same gem that was advertised.') + return 1 + sha256B = hashlib.sha256(json.dumps(unzipped_gem_json_data, indent=4).encode('utf8')).hexdigest() + if sha256A != sha256B: + logger.error(f'SECURITY VIOLATION: Downloaded gem.json does not match' + f' the advertised gem.json. Deleting unzipped files!!!') + shutil.rmtree(dest_path) + return 1 + + return 0 + + +def download_template(template_name: str, + dest_path: str or pathlib.Path) -> int: + if not dest_path: + dest_path = get_registered(default_folder='templates') + if not dest_path: + logger.error(f'Destination path not cannot be empty.') + return 1 + + dest_path = pathlib.Path(dest_path).resolve() + dest_path.mkdir(exist_ok=True, parents=True) + + download_path = get_o3de_download_folder() / 'templates' / template_name + download_path.mkdir(exist_ok=True, parents=True) + download_zip_path = download_path / 'template.zip' + + downloadable_template_data = get_downloadable(template_name=template_name) + if not downloadable_template_data: + logger.error(f'Downloadable template {template_name} not found.') + return 1 + + origin = downloadable_template_data['origin'] + url = f'{origin}/project.zip' + parsed_uri = urllib.parse.urlparse(url) + + result = 0 + + if download_zip_path.is_file(): + logger.warn(f'Project already downloaded to {download_zip_path}.') + elif parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + with urllib.request.urlopen(url) as s: + with download_zip_path.open('wb') as f: + shutil.copyfileobj(s, f) + else: + origin_file = pathlib.Path(url).resolve() + if not origin_file.is_file(): + return 1 + shutil.copy(origin_file, download_zip_path) + + if not zipfile.is_zipfile(download_zip_path): + logger.error(f"Template zip {download_zip_path} is invalid.") + download_zip_path.unlink() + return 1 + + # if the template.json has a sha256 check it against a sha256 of the zip + try: + sha256A = downloadable_template_data['sha256'] + except Exception as e: + logger.warn(f'SECURITY WARNING: The advertised template you downloaded has no "sha256"!!! Be VERY careful!!!' + f' We cannot verify this is the actually the advertised template!!!') + else: + sha256B = hashlib.sha256(download_zip_path.open('rb').read()).hexdigest() + if sha256A != sha256B: + logger.error(f'SECURITY VIOLATION: Downloaded template.zip sha256 {sha256B} does not match' + f' the advertised "sha256":{sha256A} in the template.json. Deleting unzipped files!!!') + shutil.rmtree(dest_path) + return 1 + + dest_template_folder = dest_path / template_name + if dest_template_folder.is_dir(): + backup_folder(dest_template_folder) + with zipfile.ZipFile(download_zip_path, 'r') as project_zip: + try: + project_zip.extractall(dest_path) + except Exception as e: + logger.error(f'UnZip exception:{str(e)}') + shutil.rmtree(dest_path) + return 1 + + unzipped_template_json = dest_template_folder / 'template.json' + if not unzipped_template_json.is_file(): + logger.error(f'Template json {unzipped_template_json} is missing.') + return 1 + + if not valid_o3de_engine_json(unzipped_template_json): + logger.error(f'Template json {unzipped_template_json} is invalid.') + return 1 + + # remove the sha256 if present in the advertised downloadable template.json + # then compare it to the template.json in the zip, they should now be identical + try: + del downloadable_template_data['sha256'] + except Exception as e: + pass + + sha256A = hashlib.sha256(json.dumps(downloadable_template_data, indent=4).encode('utf8')).hexdigest() + with unzipped_template_json.open('r') as s: + try: + unzipped_template_json_data = json.load(s) + except Exception as e: + logger.error(f'Failed to read Template json {unzipped_template_json}. Unable to confirm this' + f' is the same template that was advertised.') + return 1 + sha256B = hashlib.sha256(json.dumps(unzipped_template_json_data, indent=4).encode('utf8')).hexdigest() + if sha256A != sha256B: + logger.error(f'SECURITY VIOLATION: Downloaded template.json does not match' + f' the advertised template.json. Deleting unzipped files!!!') + shutil.rmtree(dest_path) + return 1 + + return 0 + + +def download_restricted(restricted_name: str, + dest_path: str or pathlib.Path) -> int: + if not dest_path: + dest_path = get_registered(default_folder='restricted') + if not dest_path: + logger.error(f'Destination path not cannot be empty.') + return 1 + + dest_path = pathlib.Path(dest_path).resolve() + dest_path.mkdir(exist_ok=True, parents=True) + + download_path = get_o3de_download_folder() / 'restricted' / restricted_name + download_path.mkdir(exist_ok=True, parents=True) + download_zip_path = download_path / 'restricted.zip' + + downloadable_restricted_data = get_downloadable(restricted_name=restricted_name) + if not downloadable_restricted_data: + logger.error(f'Downloadable Restricted {restricted_name} not found.') + return 1 + + origin = downloadable_restricted_data['origin'] + url = f'{origin}/restricted.zip' + parsed_uri = urllib.parse.urlparse(url) + + if download_zip_path.is_file(): + logger.warn(f'Restricted already downloaded to {download_zip_path}.') + elif parsed_uri.scheme == 'http' or \ + parsed_uri.scheme == 'https' or \ + parsed_uri.scheme == 'ftp' or \ + parsed_uri.scheme == 'ftps': + with urllib.request.urlopen(url) as s: + with download_zip_path.open('wb') as f: + shutil.copyfileobj(s, f) + else: + origin_file = pathlib.Path(url).resolve() + if not origin_file.is_file(): + return 1 + shutil.copy(origin_file, download_zip_path) + + if not zipfile.is_zipfile(download_zip_path): + logger.error(f"Restricted zip {download_zip_path} is invalid.") + download_zip_path.unlink() + return 1 + + # if the restricted.json has a sha256 check it against a sha256 of the zip + try: + sha256A = downloadable_restricted_data['sha256'] + except Exception as e: + logger.warn(f'SECURITY WARNING: The advertised restricted you downloaded has no "sha256"!!! Be VERY careful!!!' + f' We cannot verify this is the actually the advertised restricted!!!') + else: + sha256B = hashlib.sha256(download_zip_path.open('rb').read()).hexdigest() + if sha256A != sha256B: + logger.error(f'SECURITY VIOLATION: Downloaded restricted.zip sha256 {sha256B} does not match' + f' the advertised "sha256":{sha256A} in the restricted.json. Deleting unzipped files!!!') + shutil.rmtree(dest_path) + return 1 + + dest_restricted_folder = dest_path / restricted_name + if dest_restricted_folder.is_dir(): + backup_folder(dest_restricted_folder) + with zipfile.ZipFile(download_zip_path, 'r') as project_zip: + try: + project_zip.extractall(dest_path) + except Exception as e: + logger.error(f'UnZip exception:{str(e)}') + shutil.rmtree(dest_path) + return 1 + + unzipped_restricted_json = dest_restricted_folder / 'restricted.json' + if not unzipped_restricted_json.is_file(): + logger.error(f'Restricted json {unzipped_restricted_json} is missing.') + return 1 + + if not valid_o3de_engine_json(unzipped_restricted_json): + logger.error(f'Restricted json {unzipped_restricted_json} is invalid.') + return 1 + + # remove the sha256 if present in the advertised downloadable restricted.json + # then compare it to the restricted.json in the zip, they should now be identical + try: + del downloadable_restricted_data['sha256'] + except Exception as e: + pass + + sha256A = hashlib.sha256(json.dumps(downloadable_restricted_data, indent=4).encode('utf8')).hexdigest() + with unzipped_restricted_json.open('r') as s: + try: + unzipped_restricted_json_data = json.load(s) + except Exception as e: + logger.error( + f'Failed to read Restricted json {unzipped_restricted_json}. Unable to confirm this' + f' is the same restricted that was advertised.') + return 1 + sha256B = hashlib.sha256( + json.dumps(unzipped_restricted_json_data, indent=4).encode('utf8')).hexdigest() + if sha256A != sha256B: + logger.error(f'SECURITY VIOLATION: Downloaded restricted.json does not match' + f' the advertised restricted.json. Deleting unzipped files!!!') + shutil.rmtree(dest_path) + return 1 + + return 0 + + +def add_gem_dependency(cmake_file: str or pathlib.Path, + gem_target: str) -> int: + """ + adds a gem dependency to a cmake file + :param cmake_file: path to the cmake file + :param gem_target: name of the cmake target + :return: 0 for success or non 0 failure code + """ + if not os.path.isfile(cmake_file): + logger.error(f'Failed to locate cmake file {cmake_file}') + return 1 + + # on a line by basis, see if there already is Gem::{gem_name} + # find the first occurrence of a gem, copy its formatting and replace + # the gem name with the new one and append it + # if the gem is already present fail + t_data = [] + added = False + with open(cmake_file, 'r') as s: + for line in s: + if f'Gem::{gem_target}' in line: + logger.warning(f'{gem_target} is already a gem dependency.') + return 0 + if not added and r'Gem::' in line: + new_gem = ' ' * line.find(r'Gem::') + f'Gem::{gem_target}\n' + t_data.append(new_gem) + added = True + t_data.append(line) + + # if we didn't add it the set gem dependencies could be empty so + # add a new gem, if empty the correct format is 1 tab=4spaces + if not added: + index = 0 + for line in t_data: + index = index + 1 + if r'set(GEM_DEPENDENCIES' in line: + t_data.insert(index, f' Gem::{gem_target}\n') + added = True + break + + # if we didn't add it then it's not here, add a whole new one + if not added: + t_data.append('\n') + t_data.append('set(GEM_DEPENDENCIES\n') + t_data.append(f' Gem::{gem_target}\n') + t_data.append(')\n') + + # write the cmake + os.unlink(cmake_file) + with open(cmake_file, 'w') as s: + s.writelines(t_data) + + return 0 + + +def get_project_runtime_gem_targets(project_path: str or pathlib.Path, + platform: str = 'Common') -> set: + return get_gem_targets_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='runtime', platform=platform)) + + +def get_project_tool_gem_targets(project_path: str or pathlib.Path, + platform: str = 'Common') -> set: + return get_gem_targets_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='tool', platform=platform)) + + +def get_project_server_gem_targets(project_path: str or pathlib.Path, + platform: str = 'Common') -> set: + return get_gem_targets_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='server', platform=platform)) + + +def get_project_gem_targets(project_path: str or pathlib.Path, + platform: str = 'Common') -> set: + runtime_gems = get_gem_targets_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='runtime', platform=platform)) + tool_gems = get_gem_targets_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='tool', platform=platform)) + server_gems = get_gem_targets_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='server', platform=platform)) + return runtime_gems.union(tool_gems.union(server_gems)) + + +def get_gem_targets_from_cmake_file(cmake_file: str or pathlib.Path) -> set: + """ + Gets a list of declared gem targets dependencies of a cmake file + :param cmake_file: path to the cmake file + :return: set of gem targets found + """ + cmake_file = pathlib.Path(cmake_file).resolve() + + if not cmake_file.is_file(): + logger.error(f'Failed to locate cmake file {cmake_file}') + return set() + + gem_target_set = set() + with cmake_file.open('r') as s: + for line in s: + gem_name = line.split('Gem::') + if len(gem_name) > 1: + # Only take the name as everything leading up to the first '.' if found + # Gem naming conventions will have GemName.Editor, GemName.Server, and GemName + # as different targets of the GemName Gem + gem_target_set.add(gem_name[1].replace('\n', '')) + return gem_target_set + + +def get_project_runtime_gem_names(project_path: str or pathlib.Path, + platform: str = 'Common') -> set: + return get_gem_names_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='runtime', platform=platform)) + + +def get_project_tool_gem_names(project_path: str or pathlib.Path, + platform: str = 'Common') -> set: + return get_gem_names_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='tool', platform=platform)) + + +def get_project_server_gem_names(project_path: str or pathlib.Path, + platform: str = 'Common') -> set: + return get_gem_names_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='server', platform=platform)) + + +def get_project_gem_names(project_path: str or pathlib.Path, + platform: str = 'Common') -> set: + runtime_gem_names = get_gem_names_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='runtime', platform=platform)) + tool_gem_names = get_gem_names_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='tool', platform=platform)) + server_gem_names = get_gem_names_from_cmake_file(get_dependencies_cmake_file(project_path=project_path, dependency_type='server', platform=platform)) + return runtime_gem_names.union(tool_gem_names.union(server_gem_names)) + + +def get_gem_names_from_cmake_file(cmake_file: str or pathlib.Path) -> set: + """ + Gets a list of declared gem dependencies of a cmake file + :param cmake_file: path to the cmake file + :return: set of gems found + """ + cmake_file = pathlib.Path(cmake_file).resolve() + + if not cmake_file.is_file(): + logger.error(f'Failed to locate cmake file {cmake_file}') + return set() + + gem_set = set() + with cmake_file.open('r') as s: + for line in s: + gem_name = line.split('Gem::') + if len(gem_name) > 1: + # Only take the name as everything leading up to the first '.' if found + # Gem naming conventions will have GemName.Editor, GemName.Server, and GemName + # as different targets of the GemName Gem + gem_set.add(gem_name[1].split('.')[0].replace('\n', '')) + return gem_set + + +def get_project_runtime_gem_paths(project_path: str or pathlib.Path, + platform: str = 'Common') -> set: + gem_names = get_project_runtime_gem_names(project_path, platform) + gem_paths = set() + for gem_name in gem_names: + gem_paths.add(get_registered(gem_name=gem_name)) + return gem_paths + + +def get_project_tool_gem_paths(project_path: str or pathlib.Path, + platform: str = 'Common') -> set: + gem_names = get_project_tool_gem_names(project_path, platform) + gem_paths = set() + for gem_name in gem_names: + gem_paths.add(get_registered(gem_name=gem_name)) + return gem_paths + + +def get_project_server_gem_paths(project_path: str or pathlib.Path, + platform: str = 'Common') -> set: + gem_names = get_project_server_gem_names(project_path, platform) + gem_paths = set() + for gem_name in gem_names: + gem_paths.add(get_registered(gem_name=gem_name)) + return gem_paths + + +def get_project_gem_paths(project_path: str or pathlib.Path, + platform: str = 'Common') -> set: + gem_names = get_project_gem_names(project_path, platform) + gem_paths = set() + for gem_name in gem_names: + gem_paths.add(get_registered(gem_name=gem_name)) + return gem_paths + + +def remove_gem_dependency(cmake_file: str or pathlib.Path, + gem_target: str) -> int: + """ + removes a gem dependency from a cmake file + :param cmake_file: path to the cmake file + :param gem_target: cmake target name + :return: 0 for success or non 0 failure code + """ + if not os.path.isfile(cmake_file): + logger.error(f'Failed to locate cmake file {cmake_file}') + return 1 + + # on a line by basis, remove any line with Gem::{gem_name} + t_data = [] + # Remove the gem from the cmake_dependencies file by skipping the gem name entry + removed = False + with open(cmake_file, 'r') as s: + for line in s: + if f'Gem::{gem_target}' in line: + removed = True + else: + t_data.append(line) + + if not removed: + logger.error(f'Failed to remove Gem::{gem_target} from cmake file {cmake_file}') + return 1 + + # write the cmake + os.unlink(cmake_file) + with open(cmake_file, 'w') as s: + s.writelines(t_data) + + return 0 + + +def get_project_templates(): # temporary until we have a better way to do this... maybe template_type element + project_templates = [] + for template in get_all_templates(): + if 'Project' in template: + project_templates.append(template) + return project_templates -def refresh_repos() -> int: - json_data = load_o3de_registry() - cache_folder = get_o3de_cache() - shutil.rmtree(cache_folder) - cache_folder.mkdir(parents=True, exist_ok=True) - if len(json_data['repos']) == 0: - return 0 - result = 0 - last_failure = 0 - for repo_uri in json_data['repos']: - repo_hash = hashlib.md5(repo_uri.encode()) - cache_file = cache_folder / str(repo_hash.hexdigest() + '.json') - parsed_uri = urllib.parse.urlparse(repo_uri) +def get_gem_templates(): # temporary until we have a better way to do this... maybe template_type element + gem_templates = [] + for template in get_all_templates(): + if 'Gem' in template: + gem_templates.append(template) + return gem_templates - last_failure = 0 # download and validate the repo_uri - if not last_failure: - result = 1 - return result +def get_generic_templates(): # temporary until we have a better way to do this... maybe template_type element + generic_templates = [] + for template in get_all_templates(): + if 'Project' not in template and 'Gem' not in template: + generic_templates.append(template) + return generic_templates -def get_registered(engine_name: str = None, - project_name: str = None, - gem_name: str = None, - template_name: str = None, - default_folder: str = None, - repo_name: str = None): - json_data = load_o3de_registry() +def get_dependencies_cmake_file(project_name: str = None, + project_path: str or pathlib.Path = None, + dependency_type: str = 'runtime', + platform: str = 'Common') -> str or None: + """ + get the standard cmake file name for a particular type of dependency + :param gem_name: name of the gem, resolves gem_path + :param gem_path: path of the gem + :return: list of gem targets + """ + if not project_name and not project_path: + logger.error(f'Must supply either a Project Name or Project Path.') + return None - if type(engine_name) == str: - for engine in json_data['engines']: - engine = pathlib.Path(engine) / 'engine.json' - with engine.open('r') as f: - engine_json_data = json.load(f) - this_engines_name = engine_json_data['engine_name'] - if this_engines_name == engine_name: - engine = engine.parent.as_posix() - return engine - - elif type(project_name) == str: - for project in json_data['projects']: - project = pathlib.Path(project) / 'project.json' - with project.open('r') as f: - project_json_data = json.load(f) - this_projects_name = project_json_data['project_name'] - if this_projects_name == project_name: - project = project.parent.as_posix() - return project - - elif type(gem_name) == str: - for gem in json_data['gems']: - gem = pathlib.Path(gem) / 'gem.json' - with gem.open('r') as f: - gem_json_data = json.load(f) - this_gems_name = gem_json_data['gem_name'] - if this_gems_name == gem_name: - gem = gem.parent.as_posix() - return gem - - elif type(template_name) == str: - for template in json_data['templates']: - template = pathlib.Path(template) / 'template.json' - with template.open('r') as f: - template_json_data = json.load(f) - this_templates_name = template_json_data['template_name'] - if this_templates_name == template_name: - template = template.parent.as_posix() - return template - - elif type(default_folder) == str: - if default_folder == 'project': - return json_data['default_projects_folder'] - elif default_folder == 'gem': - return json_data['default_gems_folder'] - elif default_folder == 'template': - return json_data['default_templates_folder'] - - elif type(repo_name) == str: - cache_folder = get_o3de_cache() - cache_folder.mkdir(parents=True, exist_ok=True) - for repo_uri in json_data['repos']: - repo_hash = hashlib.md5(repo_uri.encode()) - cache_file = cache_folder / str(repo_hash.hexdigest() + '.json') - if cache_file.is_file(): - repo = pathlib.Path(cache_file) - with repo.open('r') as f: - repo_json_data = json.load(f) - this_repos_name = repo_json_data['repo_name'] - if this_repos_name == repo_name: - repo = repo.parent.as_posix() - return repo - return None + if project_name and not project_path: + project_path = get_registered(project_name=project_name) + project_path = pathlib.Path(project_path).resolve() -def print_engines(json_data, - verbose: int): - if verbose > 0: - print('\n') - print("Engines================================================") - for engine in json_data['engines']: - engine = pathlib.Path(engine) / 'engine.json' - with engine.open('r') as f: - engine_json_data = json.load(f) - print(engine) - print(json.dumps(engine_json_data, indent=4)) - print('\n') + if platform == 'Common': + dependencies_file = f'{dependency_type}_dependencies.cmake' + dependencies_file_path = project_path / 'Gem/Code' / dependencies_file + if dependencies_file_path.is_file(): + return dependencies_file_path + return project_path / 'Code' / dependencies_file + else: + dependencies_file = f'{platform.lower()}_{dependency_type}_dependencies.cmake' + dependencies_file_path = project_path / 'Gem/Code/Platform' / platform / dependencies_file + if dependencies_file_path.is_file(): + return dependencies_file_path + return project_path / 'Code/Platform' / platform / dependencies_file -def print_projects(json_data, - verbose: int): - if verbose > 0: - print('\n') - print("Projects================================================") - for project in json_data['projects']: - project = pathlib.Path(project) / 'project.json' - with project.open('r') as f: - project_json_data = json.load(f) - print(project) - print(json.dumps(project_json_data, indent=4)) - print('\n') +def get_all_gem_targets() -> list: + modules = [] + for gem_path in get_all_gems(): + this_gems_targets = get_gem_targets(gem_path=gem_path) + modules.extend(this_gems_targets) + return modules -def print_gems(json_data, - verbose: int): - if verbose > 0: - print('\n') - print("Gems================================================") - for gem in json_data['gems']: - gem = pathlib.Path(gem) / 'gem.json' - with gem.open('r') as f: - gem_json_data = json.load(f) - print(gem) - print(json.dumps(gem_json_data, indent=4)) - print('\n') +def get_gem_targets(gem_name: str = None, + gem_path: str or pathlib.Path = None) -> list: + """ + Finds gem targets in a gem + :param gem_name: name of the gem, resolves gem_path + :param gem_path: path of the gem + :return: list of gem targets + """ + if not gem_name and not gem_path: + return [] + if gem_name and not gem_path: + gem_path = get_registered(gem_name=gem_name) -def print_templates(json_data, - verbose: int): - if verbose > 0: - print("Templates================================================") - for template in json_data['templates']: - template = pathlib.Path(template) / 'template.json' - with template.open('r') as f: - template_json_data = json.load(f) - print(template) - print(json.dumps(template_json_data, indent=4)) - print('\n') + if not gem_path: + return [] + gem_path = pathlib.Path(gem_path).resolve() + gem_json = gem_path / 'gem.json' + if not valid_o3de_gem_json(gem_json): + return [] + + module_identifiers = [ + 'MODULE', + 'GEM_MODULE', + '${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}' + ] + modules = [] + for root, dirs, files in os.walk(gem_path): + for file in files: + if file == 'CMakeLists.txt': + with open(os.path.join(root, file), 'r') as s: + for line in s: + trimmed = line.lstrip() + if trimmed.startswith('NAME '): + trimmed = trimmed.rstrip(' \n') + split_trimmed = trimmed.split(' ') + if len(split_trimmed) == 3 and split_trimmed[2] in module_identifiers: + modules.append(split_trimmed[1]) + return modules + + +def add_external_subdirectory(external_subdir: str or pathlib.Path, + engine_path: str or pathlib.Path = None, + supress_errors: bool = False) -> int: + """ + add external subdirectory to a cmake + :param external_subdir: external subdirectory to add to cmake + :param engine_path: optional engine path, defaults to this engine + :param supress_errors: optional silence errors + :return: 0 for success or non 0 failure code + """ + external_subdir = pathlib.Path(external_subdir).resolve() + if not external_subdir.is_dir(): + if not supress_errors: + logger.error(f'Add External Subdirectory Failed: {external_subdir} does not exist.') + return 1 -def print_repos(json_data: str, - verbose: int): - if verbose > 0: - print("Repos================================================") - cache_folder = get_o3de_cache() - cache_folder.mkdir(parents=True, exist_ok=True) - for repo in json_data['repos']: - repo_hash = hashlib.md5(repo.encode()) - cache_file = cache_folder / str(repo_hash.hexdigest() + '.json') - if valid_o3de_manifest_json(cache_file): - with cache_file.open('r') as s: - repo_json_data = json.load(s) - print(repo) - print(json.dumps(repo_json_data, indent=4)) - print('\n') + external_subdir_cmake = external_subdir / 'CMakeLists.txt' + if not external_subdir_cmake.is_file(): + if not supress_errors: + logger.error(f'Add External Subdirectory Failed: {external_subdir} does not contain a CMakeLists.txt.') + return 1 + json_data = load_o3de_manifest() + engine_object = find_engine_data(json_data, engine_path) + if not engine_object: + if not supress_errors: + logger.error(f'Add External Subdirectory Failed: {engine_path} not registered.') + return 1 -def register_show_engines(verbose: int): - json_data = load_o3de_registry() + while external_subdir.as_posix() in engine_object['external_subdirectories']: + engine_object['external_subdirectories'].remove(external_subdir.as_posix()) + + def parse_cmake_file(cmake: str or pathlib.Path, + files: set()): + cmake_path = pathlib.Path(cmake).resolve() + cmake_file = cmake_path + if cmake_path.is_dir(): + files.add(cmake_path) + cmake_file = cmake_path / 'CMakeLists.txt' + elif cmake_path.is_file(): + cmake_path = cmake_path.parent + else: + return + + with cmake_file.open('r') as s: + lines = s.readlines() + for line in lines: + line = line.strip() + start = line.find('include(') + if start == 0: + end = line.find(')', start) + if end > start + len('include('): + try: + include_cmake_file = pathlib.Path(engine_path / line[start + len('include('): end]).resolve() + except Exception as e: + pass + else: + parse_cmake_file(include_cmake_file, files) + else: + start = line.find('add_subdirectory(') + if start == 0: + end = line.find(')', start) + if end > start + len('add_subdirectory('): + try: + include_cmake_file = pathlib.Path( + cmake_path / line[start + len('add_subdirectory('): end]).resolve() + except Exception as e: + pass + else: + parse_cmake_file(include_cmake_file, files) + + cmake_files = set() + parse_cmake_file(engine_path, cmake_files) + for external in engine_object["external_subdirectories"]: + parse_cmake_file(external, cmake_files) + + if external_subdir in cmake_files: + save_o3de_manifest(json_data) + if not supress_errors: + logger.error(f'External subdirectory {external_subdir.as_posix()} already included by add_subdirectory().') + return 1 - del json_data['repo_name'] - del json_data['origin'] - del json_data['projects'] - del json_data['gems'] - del json_data['templates'] - del json_data['repos'] - del json_data['default_projects_folder'] - del json_data['default_gems_folder'] - del json_data['default_templates_folder'] + engine_object['external_subdirectories'].insert(0, external_subdir.as_posix()) + engine_object['external_subdirectories'] = sorted(engine_object['external_subdirectories']) - print(json.dumps(json_data, indent=4)) - print_engines(json_data, - verbose) + save_o3de_manifest(json_data) + return 0 -def register_show_projects(verbose: int): - json_data = load_o3de_registry() - del json_data['repo_name'] - del json_data['origin'] - del json_data['engines'] - del json_data['gems'] - del json_data['templates'] - del json_data['repos'] - del json_data['default_projects_folder'] - del json_data['default_gems_folder'] - del json_data['default_templates_folder'] +def remove_external_subdirectory(external_subdir: str or pathlib.Path, + engine_path: str or pathlib.Path = None) -> int: + """ + remove external subdirectory from cmake + :param external_subdir: external subdirectory to add to cmake + :param engine_path: optional engine path, defaults to this engine + :return: 0 for success or non 0 failure code + """ + json_data = load_o3de_manifest() + engine_object = find_engine_data(json_data, engine_path) + if not engine_object: + logger.error(f'Remove External Subdirectory Failed: {engine_path} not registered.') + return 1 - print(json.dumps(json_data, indent=4)) - print_projects(json_data, - verbose) + external_subdir = pathlib.Path(external_subdir).resolve() + while external_subdir.as_posix() in engine_object['external_subdirectories']: + engine_object['external_subdirectories'].remove(external_subdir.as_posix()) + save_o3de_manifest(json_data) -def register_show_gems(verbose: int): - json_data = load_o3de_registry() + return 0 - del json_data['repo_name'] - del json_data['origin'] - del json_data['engines'] - del json_data['projects'] - del json_data['templates'] - del json_data['repos'] - del json_data['default_projects_folder'] - del json_data['default_gems_folder'] - del json_data['default_templates_folder'] - print(json.dumps(json_data, indent=4)) - print_gems(json_data, - verbose) +def add_gem_to_cmake(gem_name: str = None, + gem_path: str or pathlib.Path = None, + engine_name: str = None, + engine_path: str or pathlib.Path = None, + supress_errors: bool = False) -> int: + """ + add a gem to a cmake as an external subdirectory for an engine + :param gem_name: name of the gem to add to cmake + :param gem_path: the path of the gem to add to cmake + :param engine_name: name of the engine to add to cmake + :param engine_path: the path of the engine to add external subdirectory to, default to this engine + :param supress_errors: optional silence errors + :return: 0 for success or non 0 failure code + """ + if not gem_name and not gem_path: + if not supress_errors: + logger.error('Must specify either a Gem name or Gem Path.') + return 1 + if gem_name and not gem_path: + gem_path = get_registered(gem_name=gem_name) -def register_show_templates(verbose: int): - json_data = load_o3de_registry() + if not gem_path: + if not supress_errors: + logger.error(f'Gem Path {gem_path} has not been registered.') + return 1 - del json_data['repo_name'] - del json_data['origin'] - del json_data['engines'] - del json_data['projects'] - del json_data['gems'] - del json_data['repos'] - del json_data['default_projects_folder'] - del json_data['default_gems_folder'] - del json_data['default_templates_folder'] + gem_path = pathlib.Path(gem_path).resolve() + gem_json = gem_path / 'gem.json' + if not gem_json.is_file(): + if not supress_errors: + logger.error(f'Gem json {gem_json} is not present.') + return 1 + if not valid_o3de_gem_json(gem_json): + if not supress_errors: + logger.error(f'Gem json {gem_json} is not valid.') + return 1 - print(json.dumps(json_data, indent=4)) - print_templates(json_data, - verbose) + if not engine_name and not engine_path: + engine_path = get_this_engine_path() + if engine_name and not engine_path: + engine_path = get_registered(engine_name=engine_name) -def register_show_repos(verbose: int): - json_data = load_o3de_registry() + if not engine_path: + if not supress_errors: + logger.error(f'Engine Path {engine_path} has not been registered.') + return 1 - del json_data['repo_name'] - del json_data['origin'] - del json_data['engines'] - del json_data['projects'] - del json_data['gems'] - del json_data['templates'] - del json_data['default_projects_folder'] - del json_data['default_gems_folder'] - del json_data['default_templates_folder'] + engine_json = engine_path / 'engine.json' + if not engine_json.is_file(): + if not supress_errors: + logger.error(f'Engine json {engine_json} is not present.') + return 1 + if not valid_o3de_engine_json(engine_json): + if not supress_errors: + logger.error(f'Engine json {engine_json} is not valid.') + return 1 - print(json.dumps(json_data, indent=4)) - print_repos(json_data, - verbose) + return add_external_subdirectory(external_subdir=gem_path, engine_path=engine_path, supress_errors=supress_errors) -def register_show(verbose: int): - json_data = load_o3de_registry() - if verbose > 0: - print(f"{get_o3de_registry()}:") +def remove_gem_from_cmake(gem_name: str = None, + gem_path: str or pathlib.Path = None, + engine_name: str = None, + engine_path: str or pathlib.Path = None) -> int: + """ + remove a gem to cmake as an external subdirectory + :param gem_name: name of the gem to remove from cmake + :param gem_path: the path of the gem to add to cmake + :param engine_name: optional name of the engine to remove from cmake + :param engine_path: the path of the engine to remove external subdirectory from, defaults to this engine + :return: 0 for success or non 0 failure code + """ + if not gem_name and not gem_path: + logger.error('Must specify either a Gem name or Gem Path.') + return 1 - print(json.dumps(json_data, indent=4)) + if gem_name and not gem_path: + gem_path = get_registered(gem_name=gem_name) - print_engines(json_data, - verbose) - print_projects(json_data, - verbose) - print_gems(json_data, - verbose) - print_templates(json_data, - verbose) - print_repos(json_data, - verbose) + if not gem_path: + logger.error(f'Gem Path {gem_path} has not been registered.') + return 1 - if verbose > 0: - print("Default Folders================================================") - print(f"Default projects folder: {json_data['default_projects_folder']}") - print(os.listdir(json_data['default_projects_folder'])) - print('\n') - print(f"Default gems folder: {json_data['default_gems_folder']}") - print(os.listdir(json_data['default_gems_folder'])) - print('\n') - print(f"Default templates folder: {json_data['default_templates_folder']}") - print(os.listdir(json_data['default_templates_folder'])) + if not engine_name and not engine_path: + engine_path = get_this_engine_path() + if engine_name and not engine_path: + engine_path = get_registered(engine_name=engine_name) -def aggregate_repo(json_data, repo_uri: str): - cache_folder = get_o3de_cache() - cache_folder.mkdir(parents=True, exist_ok=True) - repo_hash = hashlib.md5(repo_uri.encode()) - cache_file = cache_folder / str(repo_hash.hexdigest() + '.json') - if valid_o3de_manifest_json(cache_file): - with cache_file.open('r') as s: - repo_json_data = json.load(s) - for engine in repo_json_data['engines']: - if engine not in json_data['engines']: - json_data['engines'].append(engine) - - for project in repo_json_data['projects']: - if project not in json_data['projects']: - json_data['projects'].append(project) - - for gem in repo_json_data['gems']: - if gem not in json_data['gems']: - json_data['gems'].append(gem) - - for template in repo_json_data['templates']: - if template not in json_data['templates']: - json_data['templates'].append(template) - - for repo in repo_json_data['repos']: - if repo not in json_data['repos']: - json_data['repos'].append(repo) - - for repo_uri in repo_json_data['repos']: - aggregate_repo(json_data, repo_uri) - - -def register_show_aggregate_engines(verbose: int): - json_data = load_o3de_registry() - repos = json_data['repos'].copy() - for repo_uri in repos: - aggregate_repo(json_data, repo_uri) - - del json_data['projects'] - del json_data['gems'] - del json_data['templates'] - del json_data['repos'] - del json_data['default_projects_folder'] - del json_data['default_gems_folder'] - del json_data['default_templates_folder'] + if not engine_path: + logger.error(f'Engine Path {engine_path} is not registered.') + return 1 - print(json.dumps(json_data, indent=4)) - print_engines(json_data, - verbose) + return remove_external_subdirectory(external_subdir=gem_path, engine_path=engine_path) -def register_show_aggregate_projects(verbose: int): - json_data = load_o3de_registry() - repos = json_data['repos'].copy() - for repo_uri in repos: - aggregate_repo(json_data, repo_uri) +def add_gem_to_project(gem_name: str = None, + gem_path: str or pathlib.Path = None, + gem_target: str = None, + project_name: str = None, + project_path: str or pathlib.Path = None, + dependencies_file: str or pathlib.Path = None, + runtime_dependency: bool = False, + tool_dependency: bool = False, + server_dependency: bool = False, + platforms: str = 'Common', + add_to_cmake: bool = True) -> int: + """ + add a gem to a project + :param gem_name: name of the gem to add + :param gem_path: path to the gem to add + :param gem_target: the name of the cmake gem module + :param project_name: name of to the project to add the gem to + :param project_path: path to the project to add the gem to + :param dependencies_file: if this dependency goes/is in a specific file + :param runtime_dependency: bool to specify this is a runtime gem for the game + :param tool_dependency: bool to specify this is a tool gem for the editor + :param server_dependency: bool to specify this is a server gem for the server + :param platforms: str to specify common or which specific platforms + :param add_to_cmake: bool to specify that this gem should be added to cmake + :return: 0 for success or non 0 failure code + """ + # we need either a project name or path + if not project_name and not project_path: + logger.error(f'Must either specify a Project path or Project Name.') + return 1 - del json_data['engines'] - del json_data['gems'] - del json_data['templates'] - del json_data['repos'] - del json_data['default_projects_folder'] - del json_data['default_gems_folder'] - del json_data['default_templates_folder'] + # if project name resolve it into a path + if project_name and not project_path: + project_path = get_registered(project_name=project_name) + project_path = pathlib.Path(project_path).resolve() + if not project_path.is_dir(): + logger.error(f'Project path {project_path} is not a folder.') + return 1 - print(json.dumps(json_data, indent=4)) - print_projects(json_data, - verbose) + # get the engine name this project is associated with + # and resolve that engines path + project_json = project_path / 'project.json' + if not valid_o3de_project_json(project_json): + logger.error(f'Project json {project_json} is not valid.') + return 1 + with project_json.open('r') as s: + try: + project_json_data = json.load(s) + except Exception as e: + logger.error(f'Error loading Project json {project_json}: {str(e)}') + return 1 + else: + try: + engine_name = project_json_data['engine'] + except Exception as e: + logger.error(f'Project json {project_json} "engine" not found: {str(e)}') + return 1 + else: + engine_path = get_registered(engine_name=engine_name) + if not engine_path: + logger.error(f'Engine {engine_name} is not registered.') + return 1 + + # we need either a gem name or path + if not gem_name and not gem_path: + logger.error(f'Must either specify a Gem path or Gem Name.') + return 1 + # if gem name resolve it into a path + if gem_name and not gem_path: + gem_path = get_registered(gem_name=gem_name) + gem_path = pathlib.Path(gem_path).resolve() + # make sure this gem already exists if we're adding. We can always remove a gem. + if not gem_path.is_dir(): + logger.error(f'Gem Path {gem_path} does not exist.') + return 1 -def register_show_aggregate_gems(verbose: int): - json_data = load_o3de_registry() - repos = json_data['repos'].copy() - for repo_uri in repos: - aggregate_repo(json_data, repo_uri) + # if add to cmake, make sure the gem.json exists and valid before we proceed + if add_to_cmake: + gem_json = gem_path / 'gem.json' + if not gem_json.is_file(): + logger.error(f'Gem json {gem_json} is not present.') + return 1 + if not valid_o3de_gem_json(gem_json): + logger.error(f'Gem json {gem_json} is not valid.') + return 1 - del json_data['engines'] - del json_data['projects'] - del json_data['templates'] - del json_data['repos'] - del json_data['default_projects_folder'] - del json_data['default_gems_folder'] - del json_data['default_templates_folder'] + # find all available modules in this gem_path + modules = get_gem_targets(gem_path=gem_path) + if len(modules) == 0: + logger.error(f'No gem modules found under {gem_path}.') + return 1 - print(json.dumps(json_data, indent=4)) - print_gems(json_data, - verbose) + # if the gem has no modules and the user has specified a target fail + if gem_target and not modules: + logger.error(f'Gem has no targets, but gem target {gem_target} was specified.') + return 1 + # if the gem target is not in the modules + if gem_target not in modules: + logger.error(f'Gem target not in gem modules: {modules}') + return 1 -def register_show_aggregate_templates(verbose: int): - json_data = load_o3de_registry() - repos = json_data['repos'].copy() - for repo_uri in repos: - aggregate_repo(json_data, repo_uri) + if gem_target: + # if the user has not specified either we will assume they meant the most common which is runtime + if not runtime_dependency and not tool_dependency and not server_dependency and not dependencies_file: + logger.warning("Dependency type not specified: Assuming '--runtime-dependency'") + runtime_dependency = True - del json_data['engines'] - del json_data['projects'] - del json_data['gems'] - del json_data['repos'] - del json_data['default_projects_folder'] - del json_data['default_gems_folder'] - del json_data['default_templates_folder'] + ret_val = 0 - print(json.dumps(json_data, indent=4)) - print_templates(json_data, - verbose) + # if the user has specified the dependencies file then ignore the runtime_dependency and tool_dependency flags + if dependencies_file: + dependencies_file = pathlib.Path(dependencies_file).resolve() + # make sure this is a project has a dependencies_file + if not dependencies_file.is_file(): + logger.error(f'Dependencies file {dependencies_file} is not present.') + return 1 + # add the dependency + ret_val = add_gem_dependency(dependencies_file, gem_target) + else: + if ',' in platforms: + platforms = platforms.split(',') + else: + platforms = [platforms] + for platform in platforms: + if runtime_dependency: + # make sure this is a project has a runtime_dependencies.cmake file + project_runtime_dependencies_file = pathlib.Path( + get_dependencies_cmake_file(project_path=project_path, dependency_type='runtime', platform=platform)).resolve() + if not project_runtime_dependencies_file.is_file(): + logger.error(f'Runtime dependencies file {project_runtime_dependencies_file} is not present.') + return 1 + # add the dependency + ret_val = add_gem_dependency(project_runtime_dependencies_file, gem_target) + + if (ret_val == 0) and tool_dependency: + # make sure this is a project has a tool_dependencies.cmake file + project_tool_dependencies_file = pathlib.Path( + get_dependencies_cmake_file(project_path=project_path, dependency_type='tool', platform=platform)).resolve() + if not project_tool_dependencies_file.is_file(): + logger.error(f'Tool dependencies file {project_tool_dependencies_file} is not present.') + return 1 + # add the dependency + ret_val = add_gem_dependency(project_tool_dependencies_file, gem_target) + + if (ret_val == 0) and server_dependency: + # make sure this is a project has a tool_dependencies.cmake file + project_server_dependencies_file = pathlib.Path( + get_dependencies_cmake_file(project_path=project_path, dependency_type='server', platform=platform)).resolve() + if not project_server_dependencies_file.is_file(): + logger.error(f'Server dependencies file {project_server_dependencies_file} is not present.') + return 1 + # add the dependency + ret_val = add_gem_dependency(project_server_dependencies_file, gem_target) + + if not ret_val and add_to_cmake: + ret_val = add_gem_to_cmake(gem_path=gem_path, engine_path=engine_path) + + return ret_val + + +def remove_gem_from_project(gem_name: str = None, + gem_path: str or pathlib.Path = None, + gem_target: str = None, + project_name: str = None, + project_path: str or pathlib.Path = None, + dependencies_file: str or pathlib.Path = None, + runtime_dependency: bool = False, + tool_dependency: bool = False, + server_dependency: bool = False, + platforms: str = 'Common', + remove_from_cmake: bool = False) -> int: + """ + remove a gem from a project + :param gem_name: name of the gem to add + :param gem_path: path to the gem to add + :param gem_target: the name of teh cmake gem module + :param project_name: name of the project to add the gem to + :param project_path: path to the project to add the gem to + :param dependencies_file: if this dependency goes/is in a specific file + :param runtime_dependency: bool to specify this is a runtime gem for the game + :param tool_dependency: bool to specify this is a tool gem for the editor + :param server_dependency: bool to specify this is a server gem for the server + :param platforms: str to specify common or which specific platforms + :param remove_from_cmake: bool to specify that this gem should be removed from cmake + :return: 0 for success or non 0 failure code + """ -def register_show_aggregate_repos(verbose: int): - json_data = load_o3de_registry() - repos = json_data['repos'].copy() - for repo_uri in repos: - aggregate_repo(json_data, repo_uri) + # we need either a project name or path + if not project_name and not project_path: + logger.error(f'Must either specify a Project path or Project Name.') + return 1 - del json_data['engines'] - del json_data['projects'] - del json_data['gems'] - del json_data['templates'] - del json_data['default_projects_folder'] - del json_data['default_gems_folder'] - del json_data['default_templates_folder'] + # if project name resolve it into a path + if project_name and not project_path: + project_path = get_registered(project_name=project_name) + project_path = pathlib.Path(project_path).resolve() + if not project_path.is_dir(): + logger.error(f'Project path {project_path} is not a folder.') + return 1 - print(json.dumps(json_data, indent=4)) - print_repos(json_data, - verbose) + # We need either a gem name or path + if not gem_name and not gem_path: + logger.error(f'Must either specify a Gem path or Gem Name.') + return 1 + # if gem name resolve it into a path + if gem_name and not gem_path: + gem_path = get_registered(gem_name=gem_name) + gem_path = pathlib.Path(gem_path).resolve() + # make sure this gem already exists if we're adding. We can always remove a gem. + if not gem_path.is_dir(): + logger.error(f'Gem Path {gem_path} does not exist.') + return 1 -def register_show_aggregate(verbose: int): - json_data = load_o3de_registry() - repos = json_data['repos'].copy() - for repo_uri in repos: - aggregate_repo(json_data, repo_uri) + # find all available modules in this gem_path + modules = get_gem_targets(gem_path=gem_path) + if len(modules) == 0: + logger.error(f'No gem modules found.') + return 1 - print(json.dumps(json_data, indent=4)) + # if the user has not set a specific gem target remove all of them - print_engines(json_data, - verbose) - print_projects(json_data, - verbose) - print_gems(json_data, - verbose) - print_templates(json_data, - verbose) - print_repos(json_data, - verbose) + # if gem target not specified, see if there is only 1 module + if not gem_target: + if len(modules) == 1: + gem_target = modules[0] + else: + logger.error(f'Gem target not specified: {modules}') + return 1 + elif gem_target not in modules: + logger.error(f'Gem target not in gem modules: {modules}') + return 1 - if verbose > 0: - print("Default Folders================================================") - print(f"Default projects folder: {json_data['default_projects_folder']}") - print(os.listdir(json_data['default_projects_folder'])) - print('\n') - print(f"Default gems folder: {json_data['default_gems_folder']}") - print(os.listdir(json_data['default_gems_folder'])) - print('\n') - print(f"Default templates folder: {json_data['default_templates_folder']}") - print(os.listdir(json_data['default_templates_folder'])) + # if the user has not specified either we will assume they meant the most common which is runtime + if not runtime_dependency and not tool_dependency and not server_dependency and not dependencies_file: + logger.warning("Dependency type not specified: Assuming '--runtime-dependency'") + runtime_dependency = True + # when removing we will try to do as much as possible even with failures so ret_val will be the last error code + ret_val = 0 -def _run_register(args: argparse) -> int: - if args.update: - remove_invalid_o3de_objects() - return refresh_repos() + # if the user has specified the dependencies file then ignore the runtime_dependency and tool_dependency flags + if dependencies_file: + dependencies_file = pathlib.Path(dependencies_file).resolve() + # make sure this is a project has a dependencies_file + if not dependencies_file.is_file(): + logger.error(f'Dependencies file {dependencies_file} is not present.') + return 1 + # remove the dependency + error_code = remove_gem_dependency(dependencies_file, gem_target) + if error_code: + ret_val = error_code else: - if args.this_engine: - register(engine_path=get_this_engine_path()) - register_shipped_engine_o3de_objects() + if ',' in platforms: + platforms = platforms.split(',') else: - return register(args.engine_path, - args.project_path, - args.gem_path, - args.template_path, - args.default_projects_folder, - args.default_gems_folder, - args.default_templates_folder, - args.repo_uri, - args.remove) - - -def _run_get_registered(args: argparse) -> int: + platforms = [platforms] + for platform in platforms: + if runtime_dependency: + # make sure this is a project has a runtime_dependencies.cmake file + project_runtime_dependencies_file = pathlib.Path( + get_dependencies_cmake_file(project_path=project_path, dependency_type='runtime', platform=platform)).resolve() + if not project_runtime_dependencies_file.is_file(): + logger.error(f'Runtime dependencies file {project_runtime_dependencies_file} is not present.') + else: + # remove the dependency + error_code = remove_gem_dependency(project_runtime_dependencies_file, gem_target) + if error_code: + ret_val = error_code + + if tool_dependency: + # make sure this is a project has a tool_dependencies.cmake file + project_tool_dependencies_file = pathlib.Path( + get_dependencies_cmake_file(project_path=project_path, dependency_type='tool', platform=platform)).resolve() + if not project_tool_dependencies_file.is_file(): + logger.error(f'Tool dependencies file {project_tool_dependencies_file} is not present.') + else: + # remove the dependency + error_code = remove_gem_dependency(project_tool_dependencies_file, gem_target) + if error_code: + ret_val = error_code + + if server_dependency: + # make sure this is a project has a tool_dependencies.cmake file + project_server_dependencies_file = pathlib.Path( + get_dependencies_cmake_file(project_path=project_path, dependency_type='server', platform=platform)).resolve() + if not project_server_dependencies_file.is_file(): + logger.error(f'Server dependencies file {project_server_dependencies_file} is not present.') + else: + # remove the dependency + error_code = remove_gem_dependency(project_server_dependencies_file, gem_target) + if error_code: + ret_val = error_code + + if remove_from_cmake: + error_code = remove_gem_from_cmake(gem_path=gem_path) + if error_code: + ret_val = error_code + + return ret_val + + +def sha256(file_path: str or pathlib.Path, + json_path: str or pathlib.Path = None) -> int: + if not file_path: + logger.error(f'File path cannot be empty.') + return 1 + file_path = pathlib.Path(file_path).resolve() + if not file_path.is_file(): + logger.error(f'File path {file_path} does not exist.') + return 1 + + if json_path: + json_path = pathlib.Path(json_path).resolve() + if not json_path.is_file(): + logger.error(f'Json path {json_path} does not exist.') + return 1 + + sha256 = hashlib.sha256(file_path.open('rb').read()).hexdigest() + + if json_path: + with json_path.open('r') as s: + try: + json_data = json.load(s) + except Exception as e: + logger.error(f'Failed to read Json path {json_path}: {str(e)}') + return 1 + json_data.update({"sha256": sha256}) + backup_file(json_path) + with json_path.open('w') as s: + try: + s.write(json.dumps(json_data, indent=4)) + except Exception as e: + logger.error(f'Failed to write Json path {json_path}: {str(e)}') + return 1 + else: + print(sha256) + return 0 + + +def _run_get_registered(args: argparse) -> str or pathlib.Path: + if args.override_home_folder: + global override_home_folder + override_home_folder = args.override_home_folder + return get_registered(args.engine_name, args.project_name, args.gem_name, args.template_name, args.default_folder, - args.repo_name) + args.repo_name, + args.restricted_name) def _run_register_show(args: argparse) -> int: + if args.override_home_folder: + global override_home_folder + override_home_folder = args.override_home_folder - if args.aggregate: - register_show_aggregate(args.verbose) - return 0 - if args.aggregate_engines: - register_show_aggregate_engines(args.verbose) - return 0 - elif args.aggregate_projects: - register_show_aggregate_projects(args.verbose) - return 0 - elif args.aggregate_gems: - register_show_aggregate_gems(args.verbose) - return 0 - elif args.aggregate_templates: - register_show_aggregate_templates(args.verbose) - return 0 - elif args.aggregate_repos: - register_show_aggregate_repos(args.verbose) + if args.this_engine: + print_this_engine(args.verbose) return 0 + elif args.engines: - register_show_engines(args.verbose) + print_engines(args.verbose) return 0 elif args.projects: - register_show_projects(args.verbose) + print_projects(args.verbose) return 0 elif args.gems: - register_show_gems(args.verbose) + print_gems(args.verbose) return 0 elif args.templates: - register_show_templates(args.verbose) + print_templates(args.verbose) return 0 elif args.repos: register_show_repos(args.verbose) return 0 + elif args.restricted: + print_restricted(args.verbose) + return 0 + + elif args.engine_projects: + print_engine_projects(args.verbose) + return 0 + elif args.engine_gems: + print_engine_gems(args.verbose) + return 0 + elif args.engine_templates: + print_engine_templates(args.verbose) + return 0 + elif args.engine_restricted: + print_engine_restricted(args.verbose) + return 0 + elif args.external_subdirectories: + print_external_subdirectories(args.verbose) + return 0 + + elif args.all_projects: + print_all_projects(args.verbose) + return 0 + elif args.all_gems: + print_all_gems(args.verbose) + return 0 + elif args.all_templates: + print_all_templates(args.verbose) + return 0 + elif args.all_restricted: + print_all_restricted(args.verbose) + return 0 + + elif args.downloadables: + print_downloadables(args.verbose) + return 0 + if args.downloadable_engines: + print_downloadable_engines(args.verbose) + return 0 + elif args.downloadable_projects: + print_downloadable_projects(args.verbose) + return 0 + elif args.downloadable_gems: + print_downloadable_gems(args.verbose) + return 0 + elif args.downloadable_templates: + print_downloadable_templates(args.verbose) + return 0 else: register_show(args.verbose) return 0 +def _run_download(args: argparse) -> int: + if args.override_home_folder: + global override_home_folder + override_home_folder = args.override_home_folder + + if args.engine_name: + return download_engine(args.engine_name, + args.dest_path) + elif args.project_name: + return download_project(args.project_name, + args.dest_path) + elif args.gem_nanme: + return download_gem(args.gem_name, + args.dest_path) + elif args.template_name: + return download_template(args.template_name, + args.dest_path) + + +def _run_register(args: argparse) -> int: + if args.override_home_folder: + global override_home_folder + override_home_folder = args.override_home_folder + + if args.update: + remove_invalid_o3de_objects() + return refresh_repos() + elif args.this_engine: + ret_val = register(engine_path=get_this_engine_path()) + error_code = register_shipped_engine_o3de_objects() + if error_code: + ret_val = error_code + return ret_val + elif args.all_engines_path: + return register_all_engines_in_folder(args.all_engines_path, args.remove) + elif args.all_projects_path: + return register_all_projects_in_folder(args.all_projects_path, args.remove) + elif args.all_gems_path: + return register_all_gems_in_folder(args.all_gems_path, args.remove) + elif args.all_templates_path: + return register_all_templates_in_folder(args.all_templates_path, args.remove) + elif args.all_restricted_path: + return register_all_restricted_in_folder(args.all_restricted_path, args.remove) + elif args.all_repo_uri: + return register_all_repos_in_folder(args.all_restricted_path, args.remove) + else: + return register(engine_path=args.engine_path, + project_path=args.project_path, + gem_path=args.gem_path, + template_path=args.template_path, + restricted_path=args.restricted_path, + repo_uri=args.repo_uri, + default_engines_folder=args.default_engines_folder, + default_projects_folder=args.default_projects_folder, + default_gems_folder=args.default_gems_folder, + default_templates_folder=args.default_templates_folder, + default_restricted_folder=args.default_restricted_folder, + remove=args.remove) + + +def _run_add_external_subdirectory(args: argparse) -> int: + if args.override_home_folder: + global override_home_folder + override_home_folder = args.override_home_folder + + return add_external_subdirectory(args.external_subdirectory) + + +def _run_remove_external_subdirectory(args: argparse) -> int: + if args.override_home_folder: + global override_home_folder + override_home_folder = args.override_home_folder + + return remove_external_subdirectory(args.external_subdirectory) + + +def _run_add_gem_to_cmake(args: argparse) -> int: + if args.override_home_folder: + global override_home_folder + override_home_folder = args.override_home_folder + + return add_gem_to_cmake(gem_name=args.gem_name, gem_path=args.gem_path) + + +def _run_remove_gem_from_cmake(args: argparse) -> int: + if args.override_home_folder: + global override_home_folder + override_home_folder = args.override_home_folder + + return remove_gem_from_cmake(args.gem_name, args.gem_path) + + +def _run_add_gem_to_project(args: argparse) -> int: + if args.override_home_folder: + global override_home_folder + override_home_folder = args.override_home_folder + + return add_gem_to_project(args.gem_name, + args.gem_path, + args.gem_target, + args.project_name, + args.project_path, + args.dependencies_file, + args.runtime_dependency, + args.tool_dependency, + args.server_dependency, + args.platforms, + args.add_to_cmake) + + +def _run_remove_gem_from_project(args: argparse) -> int: + if args.override_home_folder: + global override_home_folder + override_home_folder = args.override_home_folder + + return remove_gem_from_project(args.gem_name, + args.gem_path, + args.gem_target, + args.project_path, + args.project_name, + args.dependencies_file, + args.runtime_dependency, + args.tool_dependency, + args.server_dependency, + args.platforms, + args.remove_from_cmake) + + +def _run_sha256(args: argparse) -> int: + return sha256(args.file_path, + args.json_path) + + def add_args(parser, subparsers) -> None: """ add_args is called to add expected parser arguments and subparsers arguments to each command such that it can be - invoked locally or aggregated by a central python file. + invoked locally or added by a central python file. Ex. Directly run from this file alone with: python register.py register --gem-path "C:/TestGem" OR - o3de.py can aggregate commands by importing engine_template, + o3de.py can downloadable commands by importing engine_template, call add_args and execute: python o3de.py register --gem-path "C:/TestGem" :param parser: the caller instantiates a parser and passes it in here :param subparsers: the caller instantiates subparsers and passes it in here @@ -1081,98 +4058,317 @@ def add_args(parser, subparsers) -> None: # register register_subparser = subparsers.add_parser('register') group = register_subparser.add_mutually_exclusive_group(required=True) - group.add_argument('--this-engine', action = 'store_true', required=False, - default = False, + group.add_argument('--this-engine', action='store_true', required=False, + default=False, help='Registers the engine this script is running from.') - group.add_argument('-e', '--engine-path', type=str, required=False, + group.add_argument('-ep', '--engine-path', type=str, required=False, help='Engine path to register/remove.') - group.add_argument('-p', '--project-path', type=str, required=False, + group.add_argument('-pp', '--project-path', type=str, required=False, help='Project path to register/remove.') - group.add_argument('-g', '--gem-path', type=str, required=False, + group.add_argument('-gp', '--gem-path', type=str, required=False, help='Gem path to register/remove.') - group.add_argument('-t', '--template-path', type=str, required=False, + group.add_argument('-tp', '--template-path', type=str, required=False, help='Template path to register/remove.') - group.add_argument('-dp', '--default-projects-folder', type=str, required=False, + group.add_argument('-rp', '--restricted-path', type=str, required=False, + help='A restricted folder to register/remove.') + group.add_argument('-ru', '--repo-uri', type=str, required=False, + help='A repo uri to register/remove.') + group.add_argument('-aep', '--all-engines-path', type=str, required=False, + help='All engines under this folder to register/remove.') + group.add_argument('-app', '--all-projects-path', type=str, required=False, + help='All projects under this folder to register/remove.') + group.add_argument('-agp', '--all-gems-path', type=str, required=False, + help='All gems under this folder to register/remove.') + group.add_argument('-atp', '--all-templates-path', type=str, required=False, + help='All templates under this folder to register/remove.') + group.add_argument('-arp', '--all-restricted-path', type=str, required=False, + help='All templates under this folder to register/remove.') + group.add_argument('-aru', '--all-repo-uri', type=str, required=False, + help='All repos under this folder to register/remove.') + group.add_argument('-def', '--default-engines-folder', type=str, required=False, + help='The default engines folder to register/remove.') + group.add_argument('-dpf', '--default-projects-folder', type=str, required=False, help='The default projects folder to register/remove.') - group.add_argument('-dg', '--default-gems-folder', type=str, required=False, + group.add_argument('-dgf', '--default-gems-folder', type=str, required=False, help='The default gems folder to register/remove.') - group.add_argument('-dt', '--default-templates-folder', type=str, required=False, + group.add_argument('-dtf', '--default-templates-folder', type=str, required=False, help='The default templates folder to register/remove.') - group.add_argument('-ru', '--repo-uri', type=str, required=False, - help='A repo uri to register/remove.') + group.add_argument('-drf', '--default-restricted-folder', type=str, required=False, + help='The default restricted folder to register/remove.') group.add_argument('-u', '--update', action='store_true', required=False, default=False, help='Refresh the repo cache.') + + register_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') + register_subparser.add_argument('-r', '--remove', action='store_true', required=False, default=False, help='Remove entry.') - register_subparser.set_defaults(func=_run_register) # show register_show_subparser = subparsers.add_parser('register-show') group = register_show_subparser.add_mutually_exclusive_group(required=False) + group.add_argument('-te', '--this-engine', action='store_true', required=False, + default=False, + help='Just the local engines.') group.add_argument('-e', '--engines', action='store_true', required=False, default=False, - help='Just the local engines. Ignores repos') + help='Just the local engines.') group.add_argument('-p', '--projects', action='store_true', required=False, default=False, - help='Just the local projects. Ignores repos.') + help='Just the local projects.') group.add_argument('-g', '--gems', action='store_true', required=False, default=False, - help='Just the local gems. Ignores repos') + help='Just the local gems.') group.add_argument('-t', '--templates', action='store_true', required=False, default=False, - help='Just the local templates. Ignores repos.') + help='Just the local templates.') group.add_argument('-r', '--repos', action='store_true', required=False, default=False, help='Just the local repos. Ignores repos.') - group.add_argument('-a', '--aggregate', action='store_true', required=False, + group.add_argument('-rs', '--restricted', action='store_true', required=False, + default=False, + help='The local restricted folders.') + + group.add_argument('-ep', '--engine-projects', action='store_true', required=False, + default=False, + help='Just the local projects. Ignores repos.') + group.add_argument('-eg', '--engine-gems', action='store_true', required=False, + default=False, + help='Just the local gems. Ignores repos') + group.add_argument('-et', '--engine-templates', action='store_true', required=False, + default=False, + help='Just the local templates. Ignores repos.') + group.add_argument('-ers', '--engine-restricted', action='store_true', required=False, + default=False, + help='The restricted folders.') + group.add_argument('-x', '--external-subdirectories', action='store_true', required=False, + default=False, + help='The external subdirectories.') + + group.add_argument('-ap', '--all-projects', action='store_true', required=False, + default=False, + help='Just the local projects. Ignores repos.') + group.add_argument('-ag', '--all-gems', action='store_true', required=False, + default=False, + help='Just the local gems. Ignores repos') + group.add_argument('-at', '--all-templates', action='store_true', required=False, + default=False, + help='Just the local templates. Ignores repos.') + group.add_argument('-ars', '--all-restricted', action='store_true', required=False, + default=False, + help='The restricted folders.') + + group.add_argument('-d', '--downloadables', action='store_true', required=False, default=False, help='Combine all repos into a single list of resources.') - group.add_argument('-ae', '--aggregate-engines', action='store_true', required=False, + group.add_argument('-de', '--downloadable-engines', action='store_true', required=False, default=False, help='Combine all repos engines into a single list of resources.') - group.add_argument('-ap', '--aggregate-projects', action='store_true', required=False, + group.add_argument('-dp', '--downloadable-projects', action='store_true', required=False, default=False, help='Combine all repos projects into a single list of resources.') - group.add_argument('-ag', '--aggregate-gems', action='store_true', required=False, + group.add_argument('-dg', '--downloadable-gems', action='store_true', required=False, default=False, help='Combine all repos gems into a single list of resources.') - group.add_argument('-at', '--aggregate-templates', action='store_true', required=False, + group.add_argument('-dt', '--downloadable-templates', action='store_true', required=False, default=False, help='Combine all repos templates into a single list of resources.') - group.add_argument('-ar', '--aggregate-repos', action='store_true', required=False, - default=False, - help='Combine all repos into a single list of resources.') - group = register_show_subparser.add_mutually_exclusive_group(required=False) - group.add_argument('-v', '--verbose', action='count', required=False, - default=0, - help='How verbose do you want the output to be.') + register_show_subparser.add_argument('-v', '--verbose', action='count', required=False, + default=0, + help='How verbose do you want the output to be.') + + register_show_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') register_show_subparser.set_defaults(func=_run_register_show) # get-registered get_registered_subparser = subparsers.add_parser('get-registered') group = get_registered_subparser.add_mutually_exclusive_group(required=True) - group.add_argument('-e', '--engine-name', type=str, required=False, + group.add_argument('-en', '--engine-name', type=str, required=False, help='Engine name.') - group.add_argument('-p', '--project-name', type=str, required=False, + group.add_argument('-pn', '--project-name', type=str, required=False, help='Project name.') - group.add_argument('-g', '--gem-name', type=str, required=False, + group.add_argument('-gn', '--gem-name', type=str, required=False, help='Gem name.') - group.add_argument('-t', '--template-name', type=str, required=False, + group.add_argument('-tn', '--template-name', type=str, required=False, help='Template name.') - group.add_argument('-f', '--default-folder', type=str, required=False, - choices=['projects', 'gems', 'templates'], - help='The default folder.') - group.add_argument('-r', '--repo-name', type=str, required=False, - help='A repo uri to register/remove.') + group.add_argument('-df', '--default-folder', type=str, required=False, + choices=['engines', 'projects', 'gems', 'templates', 'restricted'], + help='The default folders for o3de.') + group.add_argument('-rn', '--repo-name', type=str, required=False, + help='Repo name.') + group.add_argument('-rsn', '--restricted-name', type=str, required=False, + help='Restricted name.') + + get_registered_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') get_registered_subparser.set_defaults(func=_run_get_registered) + # download + download_subparser = subparsers.add_parser('download') + group = download_subparser.add_mutually_exclusive_group(required=True) + group.add_argument('-e', '--engine-name', type=str, required=False, + help='Downloadable engine name.') + group.add_argument('-p', '--project-name', type=str, required=False, + help='Downloadable project name.') + group.add_argument('-g', '--gem-name', type=str, required=False, + help='Downloadable gem name.') + group.add_argument('-t', '--template-name', type=str, required=False, + help='Downloadable template name.') + download_subparser.add_argument('-dp', '--dest-path', type=str, required=False, + default=None, + help='Optional destination folder to download into.' + ' i.e. download --project-name "StarterGame" --dest-path "C:/projects"' + ' will result in C:/projects/StarterGame' + ' If blank will download to default object type folder') + + download_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') + + download_subparser.set_defaults(func=_run_download) + + # add external subdirectories + add_external_subdirectory_subparser = subparsers.add_parser('add-external-subdirectory') + add_external_subdirectory_subparser.add_argument('external_subdirectory', metavar='external_subdirectory', type=str, + help='add an external subdirectory to cmake') + + add_external_subdirectory_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') + + add_external_subdirectory_subparser.set_defaults(func=_run_add_external_subdirectory) + + # remove external subdirectories + remove_external_subdirectory_subparser = subparsers.add_parser('remove-external-subdirectory') + remove_external_subdirectory_subparser.add_argument('external_subdirectory', metavar='external_subdirectory', + type=str, + help='remove external subdirectory from cmake') + + remove_external_subdirectory_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') + + remove_external_subdirectory_subparser.set_defaults(func=_run_remove_external_subdirectory) + + # add gems to cmake + # convenience functions to disambiguate the gem name -> gem_path and call add-external-subdirectory on gem_path + add_gem_to_cmake_subparser = subparsers.add_parser('add-gem-to-cmake') + group = add_gem_to_cmake_subparser.add_mutually_exclusive_group(required=True) + group.add_argument('-gp', '--gem-path', type=str, required=False, + help='The path to the gem.') + group.add_argument('-gn', '--gem-name', type=str, required=False, + help='The name of the gem.') + + add_gem_to_cmake_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') + + add_gem_to_cmake_subparser.set_defaults(func=_run_add_gem_to_cmake) + + # remove gems from cmake + # convenience functions to disambiguate the gem name -> gem_path and call remove-external-subdirectory on gem_path + remove_gem_from_cmake_subparser = subparsers.add_parser('remove-gem-from-cmake') + group = remove_gem_from_cmake_subparser.add_mutually_exclusive_group(required=True) + group.add_argument('-gp', '--gem-path', type=str, required=False, + help='The path to the gem.') + group.add_argument('-gn', '--gem-name', type=str, required=False, + help='The name of the gem.') + + remove_gem_from_cmake_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') + + remove_gem_from_cmake_subparser.set_defaults(func=_run_remove_gem_from_cmake) + + # add a gem to a project + add_gem_subparser = subparsers.add_parser('add-gem-to-project') + group = add_gem_subparser.add_mutually_exclusive_group(required=True) + group.add_argument('-pp', '--project-path', type=str, required=False, + help='The path to the project.') + group.add_argument('-pn', '--project-name', type=str, required=False, + help='The name of the project.') + group = add_gem_subparser.add_mutually_exclusive_group(required=True) + group.add_argument('-gp', '--gem-path', type=str, required=False, + help='The path to the gem.') + group.add_argument('-gn', '--gem-name', type=str, required=False, + help='The name of the gem.') + add_gem_subparser.add_argument('-gt', '--gem-target', type=str, required=False, + help='The cmake target name to add. If not specified it will assume gem_name') + add_gem_subparser.add_argument('-df', '--dependencies-file', type=str, required=False, + help='The cmake dependencies file in which the gem dependencies are specified.' + 'If not specified it will assume ') + add_gem_subparser.add_argument('-rd', '--runtime-dependency', action='store_true', required=False, + default=False, + help='Optional toggle if this gem should be added as a runtime dependency') + add_gem_subparser.add_argument('-td', '--tool-dependency', action='store_true', required=False, + default=False, + help='Optional toggle if this gem should be added as a tool dependency') + add_gem_subparser.add_argument('-sd', '--server-dependency', action='store_true', required=False, + default=False, + help='Optional toggle if this gem should be added as a server dependency') + add_gem_subparser.add_argument('-pl', '--platforms', type=str, required=False, + default='Common', + help='Optional list of platforms this gem should be added to.' + ' Ex. --platforms Mac,Windows,Linux') + add_gem_subparser.add_argument('-a', '--add-to-cmake', type=bool, required=False, + default=True, + help='Automatically call add-gem-to-cmake.') + + add_gem_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') + + add_gem_subparser.set_defaults(func=_run_add_gem_to_project) + + # remove a gem from a project + remove_gem_subparser = subparsers.add_parser('remove-gem-from-project') + group = remove_gem_subparser.add_mutually_exclusive_group(required=True) + group.add_argument('-pp', '--project-path', type=str, required=False, + help='The path to the project.') + group.add_argument('-pn', '--project-name', type=str, required=False, + help='The name of the project.') + group = remove_gem_subparser.add_mutually_exclusive_group(required=True) + group.add_argument('-gp', '--gem-path', type=str, required=False, + help='The path to the gem.') + group.add_argument('-gn', '--gem-name', type=str, required=False, + help='The name of the gem.') + remove_gem_subparser.add_argument('-gt', '--gem-target', type=str, required=False, + help='The cmake target name to add. If not specified it will assume gem_name') + remove_gem_subparser.add_argument('-df', '--dependencies-file', type=str, required=False, + help='The cmake dependencies file in which the gem dependencies are specified.' + 'If not specified it will assume ') + remove_gem_subparser.add_argument('-rd', '--runtime-dependency', action='store_true', required=False, + default=False, + help='Optional toggle if this gem should be removed as a runtime dependency') + remove_gem_subparser.add_argument('-td', '--tool-dependency', action='store_true', required=False, + default=False, + help='Optional toggle if this gem should be removed as a server dependency') + remove_gem_subparser.add_argument('-sd', '--server-dependency', action='store_true', required=False, + default=False, + help='Optional toggle if this gem should be removed as a server dependency') + remove_gem_subparser.add_argument('-pl', '--platforms', type=str, required=False, + default='Common', + help='Optional list of platforms this gem should be removed from' + ' Ex. --platforms Mac,Windows,Linux') + remove_gem_subparser.add_argument('-r', '--remove-from-cmake', type=bool, required=False, + default=False, + help='Automatically call remove-from-cmake.') + + remove_gem_subparser.add_argument('-ohf', '--override-home-folder', type=str, required=False, + help='By default the home folder is the user folder, override it to this folder.') + + remove_gem_subparser.set_defaults(func=_run_remove_gem_from_project) + + # sha256 + sha256_subparser = subparsers.add_parser('sha256') + sha256_subparser.add_argument('-f', '--file-path', type=str, required=True, + help='The path to the file you want to sha256.') + sha256_subparser.add_argument('-j', '--json-path', type=str, required=False, + help='optional path to an o3de json file to add the "sha256" element to.') + sha256_subparser.set_defaults(func=_run_sha256) + if __name__ == "__main__": # parse the command line args diff --git a/cmake/o3de_manifest.cmake b/cmake/o3de_manifest.cmake new file mode 100644 index 0000000000..1585ec2d2f --- /dev/null +++ b/cmake/o3de_manifest.cmake @@ -0,0 +1,986 @@ +# +# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +# its licensors. +# +# For complete copyright and license terms please see the LICENSE at the root of this +# distribution (the "License"). All use of this software is governed by the License, +# or, if provided, by the license below or the license accompanying this file. Do not +# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# + +# Set the user home directory +set(O3DE_HOME_PATH "" CACHE PATH "Override the user home to this path") +if(O3DE_HOME_PATH) + set(home_directory ${O3DE_HOME_PATH}) +elseif(CMAKE_HOST_WIN32) + file(TO_CMAKE_PATH "$ENV{USERPROFILE}" home_directory) +else() + file(TO_CMAKE_PATH "$ENV{HOME}" home_directory) +endif() +if (NOT home_directory) + message(FATAL_ERROR "Cannot find user home directory, without the user home directory the o3de manifest cannot be found") +endif() + +# Optionally delete the home directory +if(O3DE_DELETE_HOME_PATH) + message(STATUS "O3DE_DELETE_HOME_PATH=${O3DE_DELETE_HOME_PATH}") + if(EXISTS ${home_directory}/.o3de) + message(STATUS "Deleting ${home_directory}/.o3de") + file(REMOVE_RECURSE ${home_directory}/.o3de) + else() + message(STATUS "Home path ${home_directory}/.o3de doesnt exist.") + endif() +endif() + +######################################################################################################################## +# If O3DE_REGISTER_ENGINE_PATH variable is set on the commandline this will allow registration of anything using +# O3DE_REGISTER_ENGINE_PATH o3de script. This is handy for situations like build servers which download the code and +# are expected to build without the need for someone to register o3de objects like this engine by manually typing it in. +# If O3DE_REGISTER_THIS_ENGINE=TRUE is set on the commandline when O3DE_REGISTER_ENGINE_PATH is also set this will call: +# O3DE_REGISTER_ENGINE_PATH/scripts>o3de register --this-engine --override-home-folder +# Note: register --this-engine will automatically register anything it finds in known folders, so if you put your +# o3de objects like projects/gems/templates/restricted/etc... in known folders for those types they will get registered +# automatically. Known folders for types are your .o3de/Projects and .o3de/Gems etc. So if I wanted my project to be +# registered and built by this build server I could simply put them in those known folders on the build server and they +# would get registered automatically by this call. +# OR +# I could put them on the commandline as well. This would be the way if the o3de objects we need to regiater are NOT +# in known folders or you do not intend to call with O3DE_REGISTER_THIS_ENGINE=TRUE Ex. +# -DO3DE_REGISTER_ENGINE_PATH=C:\this\engine +# -DO3DE_REGISTER_PROJECT_PATHS=C:\ThisGame;C:\ThatGame +# -DO3DE_REGISTER_GEM_PATHS=C:\ThisGem;C:\ThatGem;C:\And\Some\Other\Gem +# -DO3DE_REGISTER_RESTRICTED_PATHS=C:\this\engine\Restricted;C:\ThisGame\Restricted;C:\ThisGem\Restricted +######################################################################################################################## +if(O3DE_REGISTER_ENGINE_PATH) + message(STATUS "O3DE_REGISTER_ENGINE_PATH=${O3DE_REGISTER_ENGINE_PATH}") + + if(O3DE_REGISTER_THIS_ENGINE) + message(STATUS "O3DE_REGISTER_THIS_ENGINE=${O3DE_REGISTER_THIS_ENGINE}") + message(STATUS "register --this-engine") + if(CMAKE_HOST_WIN32) + execute_process( + COMMAND cmd /c ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.bat register --this-engine --override-home-folder ${home_directory} + RESULT_VARIABLE o3de_register_this_engine_cmd_result + ) + else() + execute_process( + COMMAND sh ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.sh register --this-engine --override-home-folder ${home_directory} + RESULT_VARIABLE o3de_register_this_engine_cmd_result + ) + endif() + if(o3de_register_this_engine_cmd_result) + message(FATAL_ERROR "An error occured trying to register --this-engine: ${o3de_register_this_engine_cmd_result}") + else() + message(STATUS "Engine ${O3DE_REGISTER_ENGINE_PATH} registration successfull.") + endif() + endif() + + if(O3DE_REGISTER_RESTRICTED_PATHS) + message(STATUS "O3DE_REGISTER_RESTRICTED_PATHS=${O3DE_REGISTER_RESTRICTED_PATHS}") + foreach(restricted_path ${O3DE_REGISTER_RESTRICTED_PATHS}) + if(CMAKE_HOST_WIN32) + execute_process( + COMMAND cmd /c ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.bat register --restricted-path ${restricted_path} --override-home-folder ${home_directory} + RESULT_VARIABLE o3de_register_restricted_cmd_result + ) + else() + execute_process( + COMMAND sh ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.sh register --restricted-path ${restricted_path} --override-home-folder ${home_directory} + RESULT_VARIABLE o3de_register_restricted_cmd_result + ) + endif() + if(o3de_register_restricted_cmd_result) + message(FATAL_ERROR "An error occured trying to ${O3DE_REGISTER_ENGINE_PATH}/scripts>o3de register --restricted-path ${restricted_path} --override-home-folder ${home_directory}: ${o3de_register_restricted_cmd_result}") + else() + message(STATUS "Restricted ${restricted_path} registration successfull.") + endif() + endforeach() + endif() + + if(O3DE_REGISTER_PROJECT_PATHS) + message(STATUS "O3DE_REGISTER_PROJECT_PATHS=${O3DE_REGISTER_PROJECT_PATHS}") + foreach(project_path ${O3DE_REGISTER_PROJECT_PATHS}) + if(CMAKE_HOST_WIN32) + execute_process( + COMMAND cmd /c ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.bat register --project-path ${project_path} --override-home-folder ${home_directory} + RESULT_VARIABLE o3de_register_project_cmd_result + ) + else() + execute_process( + COMMAND sh ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.sh register --project-path ${project_path} --override-home-folder ${home_directory} + RESULT_VARIABLE o3de_register_project_cmd_result + ) + endif() + if(o3de_register_project_cmd_result) + message(FATAL_ERROR "An error occured trying to ${O3DE_REGISTER_ENGINE_PATH}/scripts>o3de register --project-path ${project_path} --override-home-folder ${home_directory}") + else() + message(STATUS "Project ${project_path} registration successfull.") + endif() + endforeach() + endif() + + if(O3DE_REGISTER_GEM_PATHS) + message(STATUS "O3DE_REGISTER_GEM_PATHS=${O3DE_REGISTER_GEM_PATHS}") + foreach(gem_path ${O3DE_REGISTER_GEM_PATHS}) + if(CMAKE_HOST_WIN32) + execute_process( + COMMAND cmd /c ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.bat register --gem-path ${gem_path} --override-home-folder ${home_directory} + RESULT_VARIABLE o3de_register_gem_cmd_result + ) + else() + execute_process( + COMMAND sh ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.sh register --gem-path ${gem_path} --override-home-folder ${home_directory} + RESULT_VARIABLE o3de_register_gem_cmd_result + ) + endif() + if(o3de_register_gem_cmd_result) + message(FATAL_ERROR "An error occured trying to ${O3DE_REGISTER_ENGINE_PATH}/scripts>o3de register --gem-path ${gem_path} --override-home-folder ${home_directory}") + else() + message(STATUS "Gem ${gem_path} registration successfull.") + endif() + endforeach() + endif() + + if(O3DE_REGISTER_TEMPLATE_PATHS) + message(STATUS "O3DE_REGISTER_TEMPLATE_PATHS=${O3DE_REGISTER_TEMPLATE_PATHS}") + foreach(template_path ${O3DE_REGISTER_TEMPLATE_PATHS}) + if(CMAKE_HOST_WIN32) + execute_process( + COMMAND cmd /c ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.bat register --template-path ${template_path} --override-home-folder ${home_directory} + RESULT_VARIABLE o3de_register_template_cmd_result + ) + else() + execute_process( + COMMAND sh ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.sh register --template-path ${template_path} --override-home-folder ${home_directory} + RESULT_VARIABLE o3de_register_template_cmd_result + ) + endif() + if(o3de_register_template_cmd_result) + message(FATAL_ERROR "An error occured trying to ${O3DE_REGISTER_ENGINE_PATH}/scripts>o3de register --template-path ${template_path} --override-home-folder ${home_directory}") + else() + message(STATUS "Template ${template_path} registration successfull.") + endif() + endforeach() + endif() + + if(O3DE_REGISTER_REPO_URIS) + message(STATUS "O3DE_REGISTER_REPO_URIS=${O3DE_REGISTER_REPO_URIS}") + foreach(repo_uri ${O3DE_REGISTER_REPO_URIS}) + if(CMAKE_HOST_WIN32) + execute_process( + COMMAND cmd /c ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.bat register --repo-uri ${repo_uri} --override-home-folder ${home_directory} + RESULT_VARIABLE o3de_register_repo_cmd_result + ) + else() + execute_process( + COMMAND sh ${O3DE_REGISTER_ENGINE_PATH}/scripts/o3de.sh register --repo-uri ${repo_uri} --override-home-folder ${home_directory} + RESULT_VARIABLE o3de_register_repo_cmd_result + ) + endif() + if(o3de_register_repo_cmd_result) + message(FATAL_ERROR "An error occured trying to ${O3DE_REGISTER_ENGINE_PATH}/scripts>o3de register --repo-uri ${repo_uri} --override-home-folder ${home_directory}") + else() + message(STATUS "Repo ${repo_uri} registration successfull.") + endif() + endforeach() + endif() +endif() + +################################################################################ +# o3de manifest +################################################################################ +# Set manifest json path to the /.o3de/o3de_manifest.json +set(o3de_manifest_json_path ${home_directory}/.o3de/o3de_manifest.json) +if(NOT EXISTS ${o3de_manifest_json_path}) + message(FATAL_ERROR "${o3de_manifest_json_path} not found. You must o3de register --this-engine.") +endif() +file(READ ${o3de_manifest_json_path} manifest_json_data) + +################################################################################ +# o3de manifest name +################################################################################ +string(JSON o3de_manifest_name ERROR_VARIABLE json_error GET ${manifest_json_data} o3de_manifest_name) +message(STATUS "o3de_manifest_name: ${o3de_manifest_name}") +if(json_error) + message(FATAL_ERROR "Unable to read repo_name from '${o3de_manifest_json_path}', error: ${json_error}") +endif() + +################################################################################ +# o3de origin +################################################################################ +string(JSON o3de_origin ERROR_VARIABLE json_error GET ${manifest_json_data} origin) +if(json_error) + message(FATAL_ERROR "Unable to read origin from '${o3de_manifest_json_path}', error: ${json_error}") +endif() + +################################################################################ +# o3de default engines folder +################################################################################ +string(JSON o3de_default_engines_folder ERROR_VARIABLE json_error GET ${manifest_json_data} default_engines_folder) +message(STATUS "default_engines_folder: ${o3de_default_engines_folder}") +if(json_error) + message(FATAL_ERROR "Unable to read default_engines_folder from '${o3de_manifest_json_path}', error: ${json_error}") +endif() + +################################################################################ +# o3de default projects folder +################################################################################ +string(JSON o3de_default_projects_folder ERROR_VARIABLE json_error GET ${manifest_json_data} default_projects_folder) +message(STATUS "default_projects_folder: ${o3de_default_projects_folder}") +if(json_error) + message(FATAL_ERROR "Unable to read default_projects_folder from '${o3de_manifest_json_path}', error: ${json_error}") +endif() + +################################################################################ +# o3de default gems folder +################################################################################ +string(JSON o3de_default_gems_folder ERROR_VARIABLE json_error GET ${manifest_json_data} default_gems_folder) +message(STATUS "default_gems_folder: ${o3de_default_gems_folder}") +if(json_error) + message(FATAL_ERROR "Unable to read default_gems_folder from '${o3de_manifest_json_path}', error: ${json_error}") +endif() + +################################################################################ +# o3de default templates folder +################################################################################ +string(JSON o3de_default_templates_folder ERROR_VARIABLE json_error GET ${manifest_json_data} default_templates_folder) +message(STATUS "default_templates_folder: ${o3de_default_templates_folder}") +if(json_error) + message(FATAL_ERROR "Unable to read default_templates_folder from '${o3de_manifest_json_path}', error: ${json_error}") +endif() + +################################################################################ +# o3de default restricted folder +################################################################################ +string(JSON o3de_default_restricted_folder ERROR_VARIABLE json_error GET ${manifest_json_data} default_restricted_folder) +message(STATUS "default_restricted_folder: ${o3de_default_restricted_folder}") +if(json_error) + message(FATAL_ERROR "Unable to read default_restricted_folder from '${o3de_manifest_json_path}', error: ${json_error}") +endif() + +################################################################################ +# o3de projects +################################################################################ +string(JSON o3de_projects_count ERROR_VARIABLE json_error LENGTH ${manifest_json_data} projects) +if(json_error) + message(FATAL_ERROR "Unable to read key 'projects' from '${o3de_manifest_json_path}', error: ${json_error}") +endif() +if(${o3de_projects_count} GREATER 0) + math(EXPR o3de_projects_count "${o3de_projects_count}-1") + foreach(projects_index RANGE ${o3de_projects_count}) + string(JSON projects_path ERROR_VARIABLE json_error GET ${manifest_json_data} projects ${projects_index}) + if(json_error) + message(FATAL_ERROR "Unable to read projects[${projects_index}] '${o3de_manifest_json_path}', error: ${json_error}") + endif() + list(APPEND o3de_projects ${projects_path}) + list(APPEND o3de_global_projects ${projects_path}) + endforeach() +endif() + +################################################################################ +# o3de gems +################################################################################ +string(JSON o3de_gems_count ERROR_VARIABLE json_error LENGTH ${manifest_json_data} gems) +if(json_error) + message(FATAL_ERROR "Unable to read key 'gems' from '${o3de_manifest_json_path}', error: ${json_error}") +endif() +if(${o3de_gems_count} GREATER 0) + math(EXPR o3de_gems_count "${o3de_gems_count}-1") + foreach(gems_index RANGE ${o3de_gems_count}) + string(JSON gems_path ERROR_VARIABLE json_error GET ${manifest_json_data} gems ${gems_index}) + if(json_error) + message(FATAL_ERROR "Unable to read gems[${gems_index}] '${o3de_manifest_json_path}', error: ${json_error}") + endif() + list(APPEND o3de_gems ${gems_path}) + list(APPEND o3de_global_gems ${gems_path}) + endforeach() +endif() + +################################################################################ +# o3de templates +################################################################################ +string(JSON o3de_templates_count ERROR_VARIABLE json_error LENGTH ${manifest_json_data} templates) +if(json_error) + message(FATAL_ERROR "Unable to read key 'templates' from '${o3de_manifest_json_path}', error: ${json_error}") +endif() +if(${o3de_templates_count} GREATER 0) + math(EXPR o3de_templates_count "${o3de_templates_count}-1") + foreach(templates_index RANGE ${o3de_templates_count}) + string(JSON templates_path ERROR_VARIABLE json_error GET ${manifest_json_data} templates ${templates_index}) + if(json_error) + message(FATAL_ERROR "Unable to read templates[${templates_index}] '${o3de_manifest_json_path}', error: ${json_error}") + endif() + list(APPEND o3de_templates ${templates_path}) + list(APPEND o3de_global_templates ${templates_path}) + endforeach() +endif() + +################################################################################ +# o3de repos +################################################################################ +string(JSON o3de_repos_count ERROR_VARIABLE json_error LENGTH ${manifest_json_data} repos) +if(json_error) + message(FATAL_ERROR "Unable to read key 'repos' from '${o3de_manifest_json_path}', error: ${json_error}") +endif() +if(${o3de_repos_count} GREATER 0) + math(EXPR o3de_repos_count "${o3de_repos_count}-1") + foreach(repos_index RANGE ${o3de_repos_count}) + string(JSON repo_uri ERROR_VARIABLE json_error GET ${manifest_json_data} repos ${repos_index}) + if(json_error) + message(FATAL_ERROR "Unable to read repos[${repos_index}] '${o3de_manifest_json_path}', error: ${json_error}") + endif() + list(APPEND o3de_repos ${repo_uri}) + list(APPEND o3de_global_repos ${repo_uri}) + endforeach() +endif() + +################################################################################ +# o3de restricted +################################################################################ +string(JSON o3de_restricted_count ERROR_VARIABLE json_error LENGTH ${manifest_json_data} restricted) +if(json_error) + message(FATAL_ERROR "Unable to read key 'restricted' from '${o3de_manifest_json_path}', error: ${json_error}") +endif() +if(${o3de_restricted_count} GREATER 0) + math(EXPR o3de_restricted_count "${o3de_restricted_count}-1") + foreach(restricted_index RANGE ${o3de_restricted_count}) + string(JSON restricted_path ERROR_VARIABLE json_error GET ${manifest_json_data} restricted ${restricted_index}) + if(json_error) + message(FATAL_ERROR "Unable to read restricted[${restricted_index}] '${o3de_manifest_json_path}', error: ${json_error}") + endif() + list(APPEND o3de_restricted ${restricted_path}) + list(APPEND o3de_global_restricted ${restricted_path}) + endforeach() +endif() + +################################################################################ +# o3de engines +################################################################################ +string(JSON o3de_engines_count ERROR_VARIABLE json_error LENGTH ${manifest_json_data} engines) +if(json_error) + message(FATAL_ERROR "Unable to read key 'engines' from '${o3de_manifest_json_path}', error: ${json_error}") +endif() + +if(${o3de_engines_count} GREATER 0) + math(EXPR o3de_engines_count "${o3de_engines_count}-1") + # Either the engine_path and engine_json are set in which case the user is configuring from the engine + # or project_path and project_json are set in which case the user is configuring from the project. + # We need to know which engine_path the user is using so if the project_json is set then we need + # to read the project_json and disambiguate the engine_path. + if(NOT o3de_engine_path) + if(NOT o3de_project_json) + message(FATAL_ERROR "Neither o3de_engine_path nor o3de_project_json defined. Cannot determine engine!") + endif() + + # get the name of the engine this project uses + file(READ ${o3de_project_json} project_json_data) + string(JSON project_engine_name ERROR_VARIABLE json_error GET ${project_json_data} engine) + if(json_error) + message(FATAL_ERROR "Unable to read 'engine' from '${o3de_project_json}', error: ${json_error}") + endif() + + # search each engine in order from the manifest to find the matching engine name + foreach(engines_index RANGE ${o3de_engines_count}) + string(JSON engine_data ERROR_VARIABLE json_error GET ${manifest_json_data} engines ${engines_index}) + if(json_error) + message(FATAL_ERROR "Unable to read engines[${engines_index}] '${o3de_manifest_json_path}', error: ${json_error}") + endif() + + # get this engines path + string(JSON this_engine_path ERROR_VARIABLE json_error GET ${engine_data} path) + if(json_error) + message(FATAL_ERROR "Unable to read engine path from '${o3de_manifest_json_path}', error: ${json_error}") + endif() + + # add this engine to the engines list + list(APPEND o3de_engines ${this_engine_path}) + + # use path to get the engine.json + set(this_engine_json ${this_engine_path}/engine.json) + + # read the name of this engine + file(READ ${this_engine_json} this_engine_json_data) + string(JSON this_engine_name ERROR_VARIABLE json_error GET ${this_engine_json_data} engine_name) + if(json_error) + message(FATAL_ERROR "Unable to read engine_name from '${this_engine_json}', error: ${json_error}") + endif() + + # see if this engines name is the same as the one this projects should use + if(${this_engine_name} STREQUAL ${project_engine_name}) + message(STATUS "Found engine: '${project_engine_name}' at ${this_engine_path}") + set(o3de_engine_path ${this_engine_path}) + break() + endif() + endforeach() + endif() +endif() + +#we should have an engine_path at this point +if(NOT o3de_engine_path) + message(FATAL_ERROR "o3de_engine_path not defined. Cannot determine engine!") +endif() + +# now that we have an engine_path read in that engines o3de resources +if(${o3de_engines_count} GREATER -1) + foreach(engines_index RANGE ${o3de_engines_count}) + string(JSON engine_data ERROR_VARIABLE json_error GET ${manifest_json_data} engines ${engines_index}) + if(json_error) + message(FATAL_ERROR "Unable to read engines[${engines_index}] '${o3de_manifest_json_path}', error: ${json_error}") + endif() + + # get this engines path + string(JSON this_engine_path ERROR_VARIABLE json_error GET ${engine_data} path) + if(json_error) + message(FATAL_ERROR "Unable to read engine path from '${o3de_manifest_json_path}', error: ${json_error}") + endif() + + if(${o3de_engine_path} STREQUAL ${this_engine_path}) + ################################################################################ + # o3de engine projects + ################################################################################ + string(JSON o3de_engine_projects_count ERROR_VARIABLE json_error LENGTH ${engine_data} projects) + if(json_error) + message(FATAL_ERROR "Unable to read key 'projects' from '${engine_data}', error: ${json_error}") + endif() + if(${o3de_engine_projects_count} GREATER 0) + math(EXPR o3de_engine_projects_count "${o3de_engine_projects_count}-1") + foreach(engine_projects_index RANGE ${o3de_engine_projects_count}) + string(JSON engine_projects_path ERROR_VARIABLE json_error GET ${engine_data} projects ${engine_projects_index}) + if(json_error) + message(FATAL_ERROR "Unable to read engine projects[${projects_index}] '${o3de_manifest_json_path}', error: ${json_error}") + endif() + list(APPEND o3de_projects ${engine_projects_path}) + list(APPEND o3de_engine_projects ${engine_projects_path}) + endforeach() + endif() + + ################################################################################ + # o3de engine gems + ################################################################################ + string(JSON o3de_engine_gems_count ERROR_VARIABLE json_error LENGTH ${engine_data} gems) + if(json_error) + message(FATAL_ERROR "Unable to read key 'gems' from '${engine_data}', error: ${json_error}") + endif() + if(${o3de_engine_gems_count} GREATER 0) + math(EXPR o3de_engine_gems_count "${o3de_engine_gems_count}-1") + foreach(engine_gems_index RANGE ${o3de_engine_gems_count}) + string(JSON engine_gems_path ERROR_VARIABLE json_error GET ${engine_data} gems ${engine_gems_index}) + if(json_error) + message(FATAL_ERROR "Unable to read engine gems[${gems_index}] '${o3de_manifest_json_path}', error: ${json_error}") + endif() + list(APPEND o3de_gems ${engine_gems_path}) + list(APPEND o3de_engine_gems ${engine_gems_path}) + endforeach() + endif() + + ################################################################################ + # o3de engine templates + ################################################################################ + string(JSON o3de_engine_templates_count ERROR_VARIABLE json_error LENGTH ${engine_data} templates) + if(json_error) + message(FATAL_ERROR "Unable to read key 'templates' from '${o3de_manifest_json_path}', error: ${json_error}") + endif() + if(${o3de_engine_gems_count} GREATER 0) + math(EXPR o3de_engine_templates_count "${o3de_engine_templates_count}-1") + foreach(engine_templates_index RANGE ${o3de_engine_templates_count}) + string(JSON engine_templates_path ERROR_VARIABLE json_error GET ${engine_data} templates ${engine_templates_index}) + if(json_error) + message(FATAL_ERROR "Unable to read engine templates[${templates_index}] '${o3de_manifest_json_path}', error: ${json_error}") + endif() + list(APPEND o3de_templates ${engine_templates_path}) + list(APPEND o3de_engine_templates ${engine_templates_path}) + endforeach() + endif() + + ################################################################################ + # o3de engine restricted + ################################################################################ + string(JSON o3de_engine_restricted_count ERROR_VARIABLE json_error LENGTH ${engine_data} restricted) + if(json_error) + message(FATAL_ERROR "Unable to read key 'restricted' from '${o3de_manifest_json_path}', error: ${json_error}") + endif() + if(${o3de_engine_restricted_count} GREATER 0) + math(EXPR o3de_engine_restricted_count "${o3de_engine_restricted_count}-1") + foreach(engine_restricted_index RANGE ${o3de_engine_restricted_count}) + string(JSON engine_restricted_path ERROR_VARIABLE json_error GET ${engine_data} restricted ${engine_restricted_index}) + if(json_error) + message(FATAL_ERROR "Unable to read engine restricted[${engine_restricted_index}] '${o3de_manifest_json_path}', error: ${json_error}") + endif() + list(APPEND o3de_restricted ${engine_restricted_path}) + list(APPEND o3de_engine_restricted ${engine_restricted_path}) + endforeach() + endif() + + ################################################################################ + # o3de engine external_subdirectories + ################################################################################ + string(JSON o3de_external_subdirectories_count ERROR_VARIABLE json_error LENGTH ${engine_data} external_subdirectories) + if(json_error) + message(FATAL_ERROR "Unable to read key 'external_subdirectories' from '${o3de_manifest_json_path}', error: ${json_error}") + endif() + if(${o3de_external_subdirectories_count} GREATER 0) + math(EXPR o3de_external_subdirectories_count "${o3de_external_subdirectories_count}-1") + foreach(external_subdirectories_index RANGE ${o3de_external_subdirectories_count}) + string(JSON external_subdirectories_path ERROR_VARIABLE json_error GET ${engine_data} external_subdirectories ${external_subdirectories_index}) + if(json_error) + message(FATAL_ERROR "Unable to read engine external_subdirectories[${gems_index}] '${o3de_manifest_json_path}', error: ${json_error}") + endif() + list(APPEND o3de_engine_external_subdirectories ${external_subdirectories_path}) + endforeach() + endif() + + break() + + endif() + endforeach() +endif() + + +################################################################################ +#! o3de_engine_id: +# +# \arg:engine returns the engine association element from an o3de json +# \arg:o3de_json_file name of the o3de json file +################################################################################ +function(o3de_engine_id o3de_json_file engine) + file(READ ${o3de_json_file} json_data) + string(JSON engine_entry ERROR_VARIABLE json_error GET ${json_data} engine) + if(json_error) + message(WARNING "Unable to read engine from '${o3de_json_file}', error: ${json_error}") + message(WARNING "Setting engine to engine default 'o3de'") + set(engine_entry "o3de") + endif() + if(engine_entry) + set(${engine} ${engine_entry} PARENT_SCOPE) + endif() +endfunction() + + +################################################################################ +#! o3de_project_id: +# +# \arg:project returns the project association element from an o3de json +# \arg:o3de_json_file name of the o3de json file +################################################################################ +function(o3de_project_id o3de_json_file project) + file(READ ${o3de_json_file} json_data) + string(JSON project_entry ERROR_VARIABLE json_error GET ${json_data} project) + if(json_error) + message(FATAL_ERROR "Unable to read project from '${o3de_json_file}', error: ${json_error}") + endif() + if(project_entry) + set(${project} ${project_entry} PARENT_SCOPE) + endif() +endfunction() + + +################################################################################ +#! o3de_gem_id: +# +# \arg:gem returns the gem association element from an o3de json +# \arg:o3de_json_file name of the o3de json file +################################################################################ +function(o3de_gem_id o3de_json_file gem) + file(READ ${o3de_json_file} json_data) + string(JSON gem_entry ERROR_VARIABLE json_error GET ${json_data} gem) + if(json_error) + message(FATAL_ERROR "Unable to read gem from '${o3de_json_file}', error: ${json_error}") + endif() + if(gem_entry) + set(${gem} ${gem_entry} PARENT_SCOPE) + endif() +endfunction() + + +################################################################################ +#! o3de_template_id: +# +# \arg:template returns the template association element from an o3de json +# \arg:o3de_json_file name of the o3de json file +################################################################################ +function(o3de_template_id o3de_json_file template) + file(READ ${o3de_json_file} json_data) + string(JSON template_entry ERROR_VARIABLE json_error GET ${json_data} template) + if(json_error) + message(FATAL_ERROR "Unable to read template from '${o3de_json_file}', error: ${json_error}") + endif() + if(template_entry) + set(${template} ${template_entry} PARENT_SCOPE) + endif() +endfunction() + + +################################################################################ +#! o3de_repo_id: +# +# \arg:repo returns the repo association element from an o3de json +# \arg:o3de_json_file name of the o3de json file +################################################################################ +function(o3de_repo_id o3de_json_file repo) + file(READ ${o3de_json_file} json_data) + string(JSON repo_entry ERROR_VARIABLE json_error GET ${json_data} repo) + if(json_error) + message(FATAL_ERROR "Unable to read repo from '${o3de_json_file}', error: ${json_error}") + endif() + if(repo_entry) + set(${repo} ${repo_entry} PARENT_SCOPE) + endif() +endfunction() + + +################################################################################ +#! o3de_restricted_id: +# +# \arg:restricted returns the restricted association element from an o3de json, otherwise engine 'o3de' is assumed +# \arg:o3de_json_file name of the o3de json file +################################################################################ +function(o3de_restricted_id o3de_json_file restricted) + file(READ ${o3de_json_file} json_data) + string(JSON restricted_entry ERROR_VARIABLE json_error GET ${json_data} restricted) + if(json_error) + message(WARNING "Unable to read restricted from '${o3de_json_file}', error: ${json_error}") + message(WARNING "Setting restricted to engine default 'o3de'") + set(restricted_entry "o3de") + endif() + if(restricted_entry) + set(${restricted} ${restricted_entry} PARENT_SCOPE) + endif() +endfunction() + + +################################################################################ +#! o3de_find_engine_folder: +# +# \arg:engine_path returns the path of the o3de engine folder with name engine_name +# \arg:engine_name name of the engine +################################################################################ +function(o3de_find_engine_folder engine_name engine_path) + foreach(engine_entry ${o3de_engines}) + set(engine_json_file ${engine_entry}/engine.json) + file(READ ${engine_json_file} engine_json) + string(JSON this_engine_name ERROR_VARIABLE json_error GET ${engine_json} engine_name) + if(json_error) + message(WARNING "Unable to read engine_name from '${engine_json_file}', error: ${json_error}") + else() + if(this_engine_name STREQUAL engine_name) + set(${engine_path} ${engine_entry} PARENT_SCOPE) + return() + endif() + endif() + endforeach() + message(FATAL_ERROR "Unable to find repo_name: '${engine_name}'") +endfunction() + + +################################################################################ +#! o3de_find_project_folder: +# +# \arg:project_path returns the path of the o3de project folder with name project_name +# \arg:project_name name of the project +################################################################################ +function(o3de_find_project_folder project_name project_path) + foreach(project_entry ${o3de_projects}) + set(project_json_file ${project_entry}/project.json) + file(READ ${project_json_file} project_json) + string(JSON this_project_name ERROR_VARIABLE json_error GET ${project_json} project_name) + if(json_error) + message(WARNING "Unable to read project_name from '${project_json_file}', error: ${json_error}") + else() + if(this_project_name STREQUAL project_name) + set(${project_path} ${project_entry} PARENT_SCOPE) + return() + endif() + endif() + endforeach() + message(FATAL_ERROR "Unable to find project_name: '${project_name}'") +endfunction() + + +################################################################################ +#! o3de_find_gem_folder: +# +# \arg:gem_path returns the path of the o3de gem folder with name gem_name +# \arg:gem_name name of the gem +################################################################################ +function(o3de_find_gem_folder gem_name gem_path) + foreach(gem_entry ${o3de_gems}) + set(gem_json_file ${gem_entry}/gem.json) + file(READ ${gem_json_file} gem_json) + string(JSON this_gem_name ERROR_VARIABLE json_error GET ${gem_json} gem_name) + if(json_error) + message(WARNING "Unable to read gem_name from '${gem_json_file}', error: ${json_error}") + else() + if(this_gem_name STREQUAL gem_name) + set(${gem_path} ${gem_entry} PARENT_SCOPE) + return() + endif() + endif() + endforeach() + message(FATAL_ERROR "Unable to find gem_name: '${gem_name}'") +endfunction() + + +################################################################################ +#! o3de_find_template_folder: +# +# \arg:template_path returns the path of the o3de template folder with name template_name +# \arg:template_name name of the template +################################################################################ +function(o3de_find_template_folder template_name template_path) + foreach(template_entry ${o3de_templates}) + set(template_json_file ${template_entry}/template.json) + file(READ ${template_json_file} template_json) + string(JSON this_template_name ERROR_VARIABLE json_error GET ${template_json} template_name) + if(json_error) + message(WARNING "Unable to read template_name from '${template_json_file}', error: ${json_error}") + else() + if(this_template_name STREQUAL template_name) + set(${template_path} ${template_entry} PARENT_SCOPE) + return() + endif() + endif() + endforeach() + message(FATAL_ERROR "Unable to find template_name: '${template_name}'") +endfunction() + + +################################################################################ +#! o3de_find_repo_folder: +# +# \arg:repo_path returns the path of the o3de repo folder with name repo_name +# \arg:repo_name name of the repo +################################################################################ +function(o3de_find_repo_folder repo_name repo_path) + foreach(repo_entry ${o3de_repos}) + set(repo_json_file ${repo_entry}/repo.json) + file(READ ${repo_json_file} repo_json) + string(JSON this_repo_name ERROR_VARIABLE json_error GET ${repo_json} repo_name) + if(json_error) + message(WARNING "Unable to read repo_name from '${repo_json_file}', error: ${json_error}") + else() + if(this_repo_name STREQUAL repo_name) + set(${repo_path} ${repo_entry} PARENT_SCOPE) + return() + endif() + endif() + endforeach() + message(FATAL_ERROR "Unable to find repo_name: '${repo_name}'") +endfunction() + + +################################################################################ +#! o3de_find_restricted_folder: +# +# \arg:restricted_path returns the path of the o3de restricted folder with name restricted_name +# \arg:restricted_name name of the restricted +################################################################################ +function(o3de_find_restricted_folder restricted_name restricted_path) + foreach(restricted_entry ${o3de_restricted}) + set(restricted_json_file ${restricted_entry}/restricted.json) + file(READ ${restricted_json_file} restricted_json) + string(JSON this_restricted_name ERROR_VARIABLE json_error GET ${restricted_json} restricted_name) + if(json_error) + message(WARNING "Unable to read restricted_name from '${restricted_json_file}', error: ${json_error}") + else() + if(this_restricted_name STREQUAL restricted_name) + set(${restricted_path} ${restricted_entry} PARENT_SCOPE) + return() + endif() + endif() + endforeach() + message(FATAL_ERROR "Unable to find restricted_name: '${restricted_name}'") +endfunction() + + +################################################################################ +#! o3de_engine_name: +# +# \arg:engine returns the engine_name element from an engine.json +# \arg:o3de_engine_json_file name of the o3de json file +################################################################################ +function(o3de_engine_name o3de_engine_json_file engine) + file(READ ${o3de_engine_json_file} json_data) + string(JSON engine_entry ERROR_VARIABLE json_error GET ${json_data} engine_name) + if(json_error) + message(FATAL_ERROR "Unable to read engine_name from '${o3de_json_file}', error: ${json_error}") + endif() + if(engine_entry) + set(${engine} ${engine_entry} PARENT_SCOPE) + endif() +endfunction() + + +################################################################################ +#! o3de_project_name: +# +# \arg:project returns the project_name element from an project.json +# \arg:o3de_project_json_file name of the o3de json file +################################################################################ +function(o3de_project_name o3de_project_json_file project) + file(READ ${o3de_project_json_file} json_data) + string(JSON project_entry ERROR_VARIABLE json_error GET ${json_data} project_name) + if(json_error) + message(FATAL_ERROR "Unable to read project_name from '${o3de_json_file}', error: ${json_error}") + endif() + if(project_entry) + set(${project} ${project_entry} PARENT_SCOPE) + endif() +endfunction() + + +################################################################################ +#! o3de_gem_name: +# +# \arg:gem returns the gem_name element from an gem.json +# \arg:o3de_gem_json_file name of the o3de json file +################################################################################ +function(o3de_gem_name o3de_gem_json_file gem) + file(READ ${o3de_gem_json_file} json_data) + string(JSON gem_entry ERROR_VARIABLE json_error GET ${json_data} gem_name) + if(json_error) + message(FATAL_ERROR "Unable to read gem_name from '${o3de_json_file}', error: ${json_error}") + endif() + if(gem_entry) + set(${gem} ${gem_entry} PARENT_SCOPE) + endif() +endfunction() + + +################################################################################ +#! o3de_template_name: +# +# \arg:template returns the template_name element from an template json +# \arg:o3de_template_json_file name of the o3de json file +################################################################################ +function(o3de_template_name o3de_template_json_file template) + file(READ ${o3de_template_json_file} json_data) + string(JSON template_entry ERROR_VARIABLE json_error GET ${json_data} template_name) + if(json_error) + message(FATAL_ERROR "Unable to read template_name from '${o3de_json_file}', error: ${json_error}") + endif() + if(template_entry) + set(${template} ${template_entry} PARENT_SCOPE) + endif() +endfunction() + + +################################################################################ +#! o3de_repo_name: +# +# \arg:repo returns the repo_name element from an repo.json or o3de_manifest.json +# \arg:o3de_repo_json_file name of the o3de json file +################################################################################ +function(o3de_repo_name o3de_repo_json_file repo) + file(READ ${o3de_repo_json_file} json_data) + string(JSON repo_entry ERROR_VARIABLE json_error GET ${json_data} repo_name) + if(json_error) + message(FATAL_ERROR "Unable to read repo_name from '${o3de_json_file}', error: ${json_error}") + endif() + if(repo_entry) + set(${repo} ${repo_entry} PARENT_SCOPE) + endif() +endfunction() + + +################################################################################ +#! o3de_restricted_name: +# +# \arg:restricted returns the restricted association element from an o3de json +# \arg:o3de_json_file name of the o3de json file +################################################################################ +function(o3de_restricted_name o3de_json_file restricted) + file(READ ${o3de_json_file} json_data) + string(JSON restricted_entry ERROR_VARIABLE json_error GET ${json_data} restricted_name) + if(json_error) + message(WARNING "FATAL_ERROR to read restricted_name from '${o3de_json_file}', error: ${json_error}") + endif() + if(restricted_entry) + set(${restricted} ${restricted_entry} PARENT_SCOPE) + endif() +endfunction() + + +################################################################################ +#! o3de_engine_path: +# +# \arg:engine_path returns the path of the o3de engine folder with name engine_name +# \arg:engine_name name of the engine +################################################################################ +function(o3de_engine_path o3de_json_file engine_path) + o3de_engine_id(${o3de_json_file} engine_name) + if(engine_name) + o3de_find_engine_folder(${engine_name} engine_folder) + if(engine_folder) + set(${engine_path} ${engine_folder} PARENT_SCOPE) + endif() + endif() +endfunction() + + +################################################################################ +#! o3de_project_path: +# +# \arg:project_path returns the path of the o3de project folder with name project_name +# \arg:project_name name of the project +################################################################################ +function(o3de_project_path o3de_json_file project_path) + o3de_project_id(${o3de_json_file} project_name) + if(project_name) + o3de_find_project_folder(${project_name} project_folder) + if(project_folder) + set(${project_path} ${project_folder} PARENT_SCOPE) + endif() + endif() +endfunction() + + +################################################################################ +#! o3de_template_path: +# +# \arg:template_path returns the path of the o3de template folder with name template_name +# \arg:template_name name of the template +################################################################################ +function(o3de_template_path o3de_json_file template_path) + o3de_template_id(${o3de_json_file} template_name) + if(template_name) + o3de_find_template_folder(${template_name} template_folder) + if(template_folder) + set(${template_path} ${template_folder} PARENT_SCOPE) + endif() + endif() +endfunction() + + +################################################################################ +#! o3de_repo_path: +# +# \arg:repo_path returns the path of the o3de repo folder with name repo_name +# \arg:repo_name name of the repo +################################################################################ +function(o3de_repo_path o3de_json_file repo_path) + o3de_repo_id(${o3de_json_file} repo_name) + if(repo_name) + o3de_find_repo_folder(${repo_name} repo_folder) + if(repo_folder) + set(${repo_path} ${repo_folder} PARENT_SCOPE) + endif() + endif() +endfunction() + + +################################################################################ +#! o3de_restricted_path: +# +# \arg:restricted_path returns the path of the o3de restricted folder with name restricted_name +# \arg:restricted_name name of the restricted +################################################################################ +function(o3de_restricted_path o3de_json_file restricted_path) + o3de_restricted_id(${o3de_json_file} restricted_name) + if(restricted_name) + o3de_find_restricted_folder(${restricted_name} restricted_folder) + if(restricted_folder) + set(${restricted_path} ${restricted_folder} PARENT_SCOPE) + endif() + endif() +endfunction() diff --git a/engine.json b/engine.json index 5091605f4c..22ac2512f5 100644 --- a/engine.json +++ b/engine.json @@ -1,5 +1,6 @@ { "engine_name": "o3de", + "restricted": "o3de", "FileVersion": 1, "O3DEVersion": "0.0.0.0", "O3DECopyrightYear": 2021 diff --git a/scripts/build/Platform/Android/build_config.json b/scripts/build/Platform/Android/build_config.json index 699ccdf20a..d0fce80964 100644 --- a/scripts/build/Platform/Android/build_config.json +++ b/scripts/build/Platform/Android/build_config.json @@ -35,7 +35,7 @@ "PARAMETERS": { "CONFIGURATION":"debug", "OUTPUT_DIRECTORY":"build\\android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" @@ -60,7 +60,7 @@ "PARAMETERS": { "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"build\\android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" @@ -76,7 +76,7 @@ "PARAMETERS": { "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"build\\android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=FALSE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=FALSE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" @@ -93,7 +93,7 @@ "PARAMETERS": { "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"build\\windows_vs2019", - "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"AssetProcessorBatch", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -112,7 +112,7 @@ "PARAMETERS": { "CONFIGURATION":"release", "OUTPUT_DIRECTORY":"build\\android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" @@ -128,7 +128,7 @@ "PARAMETERS": { "CONFIGURATION":"release", "OUTPUT_DIRECTORY":"build\\mono_android", - "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=cmake\\Platform\\Android\\Toolchain_android.cmake -DANDROID_ABI=arm64-v8a -DANDROID_ARM_MODE=arm -DANDROID_ARM_NEON=FALSE -DANDROID_NATIVE_API_LEVEL=21 -DLY_NDK_DIR=\"!LY_3RDPARTY_PATH!\\android-ndk\\r21d\" -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS":"AutomatedTesting", "CMAKE_TARGET":"all", "CMAKE_BUILD_ARGS":"-j!NUMBER_OF_PROCESSORS!" diff --git a/scripts/build/Platform/Linux/build_config.json b/scripts/build/Platform/Linux/build_config.json index 4ae4c4ec0b..bbfc3e4269 100644 --- a/scripts/build/Platform/Linux/build_config.json +++ b/scripts/build/Platform/Linux/build_config.json @@ -37,7 +37,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -53,7 +53,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -66,7 +66,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -80,7 +80,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", "CTEST_OPTIONS": "-E (Gem::EMotionFX.Editor.Tests|Gem::AWSClientAuth.Tests|Gem::AWSCore.Editor.Tests) -L FRAMEWORK_googletest" @@ -92,7 +92,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all", "CTEST_OPTIONS": "-E (Gem::EMotionFX.Editor.Tests|Gem::AWSClientAuth.Tests|Gem::AWSCore.Editor.Tests) -L FRAMEWORK_googletest" @@ -108,7 +108,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "ASSET_PROCESSOR_BINARY": "bin/profile/AssetProcessorBatch", @@ -122,7 +122,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=FALSE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "ASSET_PROCESSOR_BINARY": "bin/profile/AssetProcessorBatch", @@ -140,7 +140,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", "CTEST_OPTIONS": "-L \"(SUITE_periodic)\"" @@ -156,7 +156,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", "CTEST_OPTIONS": "-L \"(SUITE_benchmark)\"" @@ -172,7 +172,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } @@ -187,7 +187,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/mono_linux", - "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4", + "CMAKE_OPTIONS": "-G 'Ninja Multi-Config' -DCMAKE_C_COMPILER=clang-6.0 -DCMAKE_CXX_COMPILER=clang++-6.0 -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE -DLY_PARALLEL_LINK_JOBS=4 -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "all" } diff --git a/scripts/build/Platform/Mac/build_config.json b/scripts/build/Platform/Mac/build_config.json index 1e6ca79d8e..f312279fe6 100644 --- a/scripts/build/Platform/Mac/build_config.json +++ b/scripts/build/Platform/Mac/build_config.json @@ -37,7 +37,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } @@ -51,7 +51,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } @@ -66,7 +66,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=FALSE", + "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=FALSE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } @@ -81,7 +81,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "ASSET_PROCESSOR_BINARY": "bin/profile/AssetProcessorBatch", @@ -99,7 +99,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", "CTEST_OPTIONS": "-L \"(SUITE_periodic)\"" @@ -115,7 +115,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", "CTEST_OPTIONS": "-L \"(SUITE_benchmark)\"" @@ -131,7 +131,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } @@ -146,7 +146,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/mono_mac", - "CMAKE_OPTIONS": "-G Xcode -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"${WORKSPACE}/home\" -DO3DE_REGISTER_ENGINE_PATH=\"${WORKSPACE}/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD" } diff --git a/scripts/build/Platform/Windows/build_config.json b/scripts/build/Platform/Windows/build_config.json index 263539cd4a..ff57e007cc 100644 --- a/scripts/build/Platform/Windows/build_config.json +++ b/scripts/build/Platform/Windows/build_config.json @@ -87,7 +87,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_BUILD_WITH_INCREMENTAL_LINKING_DEBUG=FALSE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_BUILD_WITH_INCREMENTAL_LINKING_DEBUG=FALSE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -101,7 +101,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_BUILD_WITH_INCREMENTAL_LINKING_DEBUG=FALSE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_BUILD_WITH_INCREMENTAL_LINKING_DEBUG=FALSE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_smoke TEST_SUITE_main", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -118,7 +118,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -134,7 +134,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=FALSE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=FALSE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -149,7 +149,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_smoke TEST_SUITE_main", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -169,7 +169,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_smoke TEST_SUITE_main", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -187,7 +187,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "AssetProcessorBatch", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -206,7 +206,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_periodic", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -227,7 +227,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_sandbox", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -245,7 +245,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "TEST_SUITE_benchmark", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo", @@ -263,7 +263,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -279,7 +279,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build\\mono_windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_MONOLITHIC_GAME=TRUE -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" @@ -294,7 +294,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build\\windows_vs2019", - "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_DISABLE_TEST_MODULES=TRUE -DCMAKE_INSTALL_PREFIX=install", + "CMAKE_OPTIONS": "-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DLY_DISABLE_TEST_MODULES=TRUE -DCMAKE_INSTALL_PREFIX=install -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "", "CMAKE_TARGET": "INSTALL", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" diff --git a/scripts/build/Platform/Windows/package_build_config.json b/scripts/build/Platform/Windows/package_build_config.json index a5ca861377..40f479a098 100644 --- a/scripts/build/Platform/Windows/package_build_config.json +++ b/scripts/build/Platform/Windows/package_build_config.json @@ -4,7 +4,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "windows_vs2017", - "CMAKE_OPTIONS": "-G \"Visual Studio 15 2017\" -A x64 -T host=x64 -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G \"Visual Studio 15 2017\" -A x64 -T host=x64 -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AtomTest;AtomSampleViewer", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m:4 /p:CL_MPCount=!HALF_PROCESSORS! /nologo" @@ -15,7 +15,7 @@ "PARAMETERS": { "CONFIGURATION":"profile", "OUTPUT_DIRECTORY":"windows_vs2019", - "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS":"-G \"Visual Studio 16 2019\" -DCMAKE_SYSTEM_VERSION=10.0 -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS":"AtomTest;AtomSampleViewer", "CMAKE_TARGET":"ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "/m /nologo" diff --git a/scripts/build/Platform/iOS/build_config.json b/scripts/build/Platform/iOS/build_config.json index 75b5e7b10d..4566d243ee 100644 --- a/scripts/build/Platform/iOS/build_config.json +++ b/scripts/build/Platform/iOS/build_config.json @@ -27,7 +27,7 @@ "PARAMETERS": { "CONFIGURATION": "debug", "OUTPUT_DIRECTORY": "build/ios", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "-destination generic/platform=iOS" @@ -44,7 +44,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/ios", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "-destination generic/platform=iOS" @@ -60,7 +60,7 @@ "PARAMETERS": { "CONFIGURATION": "profile", "OUTPUT_DIRECTORY": "build/ios", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=FALSE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=FALSE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "-destination generic/platform=iOS" @@ -94,7 +94,7 @@ "PARAMETERS": { "CONFIGURATION": "release", "OUTPUT_DIRECTORY": "build/ios", - "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE", + "CMAKE_OPTIONS": "-G Xcode -DCMAKE_TOOLCHAIN_FILE=cmake/Platform/iOS/Toolchain_ios.cmake -DLY_MONOLITHIC_GAME=TRUE -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED=FALSE -DLY_IOS_CODE_SIGNING_IDENTITY=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS=\"\" -DCMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED=FALSE -DLY_UNITY_BUILD=TRUE -DO3DE_HOME_PATH=\"!WORKSPACE!/home\" -DO3DE_REGISTER_ENGINE_PATH=\"!WORKSPACE!/o3de\" -DO3DE_REGISTER_THIS_ENGINE=TRUE", "CMAKE_LY_PROJECTS": "AutomatedTesting", "CMAKE_TARGET": "ALL_BUILD", "CMAKE_NATIVE_BUILD_ARGS": "-destination generic/platform=iOS" diff --git a/scripts/o3de.bat b/scripts/o3de.bat index 9dd0d5b539..0a65b722d7 100644 --- a/scripts/o3de.bat +++ b/scripts/o3de.bat @@ -33,4 +33,6 @@ popd EXIT /b 1 :end popd +EXIT /b %ERRORLEVEL% + diff --git a/scripts/o3de.py b/scripts/o3de.py index 1c528704f4..dbb9c53e4b 100755 --- a/scripts/o3de.py +++ b/scripts/o3de.py @@ -19,15 +19,13 @@ if ROOT_DEV_PATH not in sys.path: sys.path.append(ROOT_DEV_PATH) from cmake.Tools import engine_template -from cmake.Tools import current_project -from cmake.Tools import add_remove_gem +from cmake.Tools import global_project from cmake.Tools import registration def add_args(parser, subparsers) -> None: - current_project.add_args(parser, subparsers) + global_project.add_args(parser, subparsers) engine_template.add_args(parser, subparsers) - add_remove_gem.add_args(parser, subparsers) registration.add_args(parser, subparsers) diff --git a/scripts/o3de.sh b/scripts/o3de.sh index 6b789b494f..dde8808551 100755 --- a/scripts/o3de.sh +++ b/scripts/o3de.sh @@ -15,11 +15,9 @@ #Note this does not actually change the working directory SCRIPT_DIR=$(cd `dirname $0` && pwd) -#The engine root is always the parent of the scripts directory, so $(dirname "$SCRIPT_DIR") should get us engine root -ENGINE_ROOT=$(dirname "$SCRIPT_DIR") - -#The engines python is in the engineroot/python -PYTHON_DIRECTORY="$ENGINE_ROOT/python" +#python should be in the base path +BASE_PATH=$(dirname "$SCRIPT_DIR") +PYTHON_DIRECTORY="$BASE_PATH/python" #If engine python exists use it, if not try the system python if [ ! -d "$PYTHON_DIRECTORY" ]; then @@ -33,4 +31,5 @@ if [ ! -f "$PYTHON_EXECUTABLE" ]; then fi #run the o3de.py pass along the command -$PYTHON_EXECUTABLE "$SCRIPT_DIR/o3de.py" $* \ No newline at end of file +$PYTHON_EXECUTABLE "$SCRIPT_DIR/o3de.py" $* +exit $? \ No newline at end of file diff --git a/scripts/project_manager/projects.py b/scripts/project_manager/projects.py index 5cd506c515..51db7a6440 100755 --- a/scripts/project_manager/projects.py +++ b/scripts/project_manager/projects.py @@ -12,47 +12,45 @@ # PySide project and gem selector GUI import os +import pathlib import sys import argparse import json import logging import subprocess -import threading -import platform from logging.handlers import RotatingFileHandler - from typing import List - -from pathlib import Path from pyside import add_pyside_environment, is_pyside_ready, uninstall_env +engine_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..')) +sys.path.append(engine_path) +executable_path = '' + logger = logging.getLogger() logger.setLevel(logging.INFO) -log_path = os.path.join(os.path.dirname(__file__), "logs") -if not os.path.isdir(log_path): - os.makedirs(log_path) -log_path = os.path.join(log_path, "project_manager.log") -log_file_handler = RotatingFileHandler(filename=log_path, maxBytes=1024 * 1024, backupCount=1) +from cmake.Tools import engine_template +from cmake.Tools import registration + +o3de_folder = registration.get_o3de_folder() +o3de_logs_folder = registration.get_o3de_logs_folder() +project_manager_log_file_path = o3de_log_folder / "project_manager.log" +log_file_handler = RotatingFileHandler(filename=project_manager_log_file_path, maxBytes=1024 * 1024, backupCount=1) formatter = logging.Formatter('%(asctime)s | %(levelname)s : %(message)s') log_file_handler.setFormatter(formatter) logger.addHandler(log_file_handler) logger.info("Starting Project Manager") -engine_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..')) -sys.path.append(engine_path) -executable_path = '' - def initialize_pyside_from_parser(): # Parse arguments up top. We need to know the path to our binaries and QT libs in particular to load up # PySide parser = argparse.ArgumentParser() - parser.add_argument('--executable_path', required=True, help='Path to Executable to launch with project') - parser.add_argument('--binaries_path', default=None, help='Path to QT Binaries necessary for PySide. If not' - 'provided executable_path folder is assumed') - parser.add_argument('--parent_pid', default=0, help='Process ID of launching process') + parser.add_argument('--executable-path', required=True, help='Path to Executable to launch with project') + parser.add_argument('--binaries-path', default=None, help='Path to QT Binaries necessary for PySide. If not' + ' provided executable_path folder is assumed') + parser.add_argument('--parent-pid', default=0, help='Process ID of launching process') args = parser.parse_args() @@ -81,21 +79,6 @@ except ImportError as e: logger.error(f"PySide2 imports successful") -from cmake.Tools import engine_template -from cmake.Tools import add_remove_gem - -ui_path = os.path.join(os.path.join(os.path.dirname(__file__), 'ui')) -ui_file = 'projects.ui' -ui_icon_file = 'projects.ico' -manage_gems_file = 'manage_gems.ui' -create_project_file = 'create_project.ui' - -mru_file_name = 'o3de_projects.json' -default_settings_folder = os.path.join(Path.home(), '.o3de') - -# Used to indicate a folder which appears to be a valid o3de project -project_marker_file = 'project.json' - class DialogLoggerSignaller(QObject): send_to_dialog = Signal(str) @@ -132,14 +115,16 @@ class DialogLogger(logging.Handler): self.signaller.send_to_dialog.emit(self.format(record)) - -class ProjectDialog(QObject): +class ProjectManagerDialog(QObject): """ - Main project dialog class responsible for displaying the project selection list + Main project manager dialog is responsible for displaying the project selection list and output pane """ - def __init__(self, parent=None, my_ui_path=ui_path, settings_folder=default_settings_folder): - super(ProjectDialog, self).__init__(parent) + def __init__(self, parent=None): + super(ProjectManagerDialog, self).__init__(parent) + + self.ui_path = (pathlib.Path(__file__).parent / 'ui').resolve() + self.home_folder = registration.get_home_folder() self.log_display = None self.dialog_logger = DialogLogger(self) @@ -147,76 +132,100 @@ class ProjectDialog(QObject): logger.setLevel(logging.INFO) self.dialog_logger.signaller.send_to_dialog.connect(self.handle_log_message) - self.displayed_projects = [] + self.mru_file_path = o3de_folder / 'mru.json' - self.mru_file_path = os.path.join(settings_folder, mru_file_name) + self.create_from_template_ui_file_path = self.ui_path / 'create_from_template.ui' + self.create_gem_ui_file_path = self.ui_path / 'create_gem.ui' + self.create_project_ui_file_path = self.ui_path / 'create_project.ui' + self.manage_project_gem_targets_ui_file_path = self.ui_path / 'manage_gem_targets.ui' + self.project_manager_icon_file_path = self.ui_path / 'project_manager.ico' + self.project_manager_ui_file_path = self.ui_path / 'project_manager.ui' - self.my_ui_file_path = os.path.join(my_ui_path, ui_file) - self.my_ui_icon_path = os.path.join(my_ui_path, ui_icon_file) - self.my_ui_manage_gems_path = os.path.join(my_ui_path, manage_gems_file) - self.my_ui_create_project_path = os.path.join(my_ui_path, create_project_file) - self.ui_dir = my_ui_path - self.ui_file = QFile(self.my_ui_file_path) - self.ui_file.open(QFile.ReadOnly) + self.project_manager_ui_file = QFile(self.project_manager_ui_file_path.as_posix()) + self.project_manager_ui_file.open(QFile.ReadOnly) loader = QUiLoader() - self.dialog = loader.load(self.ui_file) - self.dialog.setWindowIcon(QIcon(self.my_ui_icon_path)) + self.dialog = loader.load(self.project_manager_ui_file) + self.dialog.setWindowIcon(QIcon(self.project_manager_icon_file_path.as_posix())) self.dialog.setFixedSize(self.dialog.size()) - self.browse_projects_button = self.dialog.findChild(QPushButton, 'browseProjectsButton') - self.browse_projects_button.clicked.connect(self.browse_projects_handler) - self.log_display = self.dialog.findChild(QLabel, 'logDisplay') + self.project_list_box = self.dialog.findChild(QComboBox, 'projectListBox') + self.refresh_project_list() + mru = self.get_mru_list() + if len(mru): + last_mru = pathlib.Path(mru[0]).resolve() + for this_slot in range(self.project_list_box.count()): + item_text = self.project_list_box.itemText(this_slot) + if last_mru.as_posix() in item_text: + self.project_list_box.setCurrentIndex(this_slot) + break self.create_project_button = self.dialog.findChild(QPushButton, 'createProjectButton') self.create_project_button.clicked.connect(self.create_project_handler) + self.create_gem_button = self.dialog.findChild(QPushButton, 'createGemButton') + self.create_gem_button.clicked.connect(self.create_gem_handler) + self.create_template_button = self.dialog.findChild(QPushButton, 'createTemplateButton') + self.create_template_button.clicked.connect(self.create_template_handler) + self.create_from_template_button = self.dialog.findChild(QPushButton, 'createFromTemplateButton') + self.create_from_template_button.clicked.connect(self.create_from_template_handler) + + self.add_project_button = self.dialog.findChild(QPushButton, 'addProjectButton') + self.add_project_button.clicked.connect(self.add_project_handler) + self.add_gem_button = self.dialog.findChild(QPushButton, 'addGemButton') + self.add_gem_button.clicked.connect(self.add_gem_handler) + self.add_template_button = self.dialog.findChild(QPushButton, 'addTemplateButton') + self.add_template_button.clicked.connect(self.add_template_handler) + self.add_restricted_button = self.dialog.findChild(QPushButton, 'addRestrictedButton') + self.add_restricted_button.clicked.connect(self.add_restricted_handler) + + self.remove_project_button = self.dialog.findChild(QPushButton, 'removeProjectButton') + self.remove_project_button.clicked.connect(self.remove_project_handler) + self.remove_gem_button = self.dialog.findChild(QPushButton, 'removeGemButton') + self.remove_gem_button.clicked.connect(self.remove_gem_handler) + self.remove_template_button = self.dialog.findChild(QPushButton, 'removeTemplateButton') + self.remove_template_button.clicked.connect(self.remove_template_handler) + self.remove_restricted_button = self.dialog.findChild(QPushButton, 'removeRestrictedButton') + self.remove_restricted_button.clicked.connect(self.remove_restricted_handler) + + self.manage_runtime_project_gem_targets_button = self.dialog.findChild(QPushButton, 'manageRuntimeGemTargetsButton') + self.manage_runtime_project_gem_targets_button.clicked.connect(self.manage_runtime_project_gem_targets_handler) + self.manage_tool_project_gem_targets_button = self.dialog.findChild(QPushButton, 'manageToolGemTargetsButton') + self.manage_tool_project_gem_targets_button.clicked.connect(self.manage_tool_project_gem_targets_handler) + self.manage_server_project_gem_targets_button = self.dialog.findChild(QPushButton, 'manageServerGemTargetsButton') + self.manage_server_project_gem_targets_button.clicked.connect(self.manage_server_project_gem_targets_handler) + + self.log_display = self.dialog.findChild(QLabel, 'logDisplay') self.ok_cancel_button = self.dialog.findChild(QDialogButtonBox, 'okCancel') self.ok_cancel_button.accepted.connect(self.accepted_handler) - self.manage_gems_button = self.dialog.findChild(QPushButton, 'manageGemsButton') - self.manage_gems_button.clicked.connect(self.manage_gems_handler) - - self.project_list_box = self.dialog.findChild(QComboBox, 'projectListBox') - self.add_projects(self.get_mru_list()) - self.project_gem_list = [] - self.dialog.show() - self.load_thread = None - self.gems_list = [] - self.load_gems() + def refresh_project_list(self) -> None: + projects = registration.get_all_projects() + self.project_list_box.clear() + for this_slot in range(len(projects)): + display_name = f'{os.path.basename(os.path.normpath(projects[this_slot]))} ({projects[this_slot]})' + self.project_list_box.addItem(display_name) + self.project_list_box.setItemData(self.project_list_box.count() - 1, projects[this_slot], + Qt.ToolTipRole) - def update_gems(self) -> None: + def accepted_handler(self) -> None: """ - Perform a full refresh of active project and available gems. Loads both from - data on disk and refreshes UI + Override for handling "Ok" on main project dialog to first check whether the user has selected a project and + prompt them to if not. If a project is selected will attempt to open it. :return: None """ - project_path = self.path_for_selection() - if not project_path: - self.project_gem_list = set() - else: - self.project_gem_list = add_remove_gem.get_project_gem_list(self.path_for_selection()) - project_gem_model = QStandardItemModel() - for item in sorted(self.project_gem_list): - project_gem_model.appendRow(QStandardItem(item)) - self.project_gems.setModel(project_gem_model) - - add_gem_model = QStandardItemModel() - for item in self.gems_list: - if item.get('Name') in self.project_gem_list: - continue - model_item = QStandardItem(item.get('Name')) - model_item.setData(item.get('Path'), Qt.UserRole) - add_gem_model.appendRow(model_item) - self.add_gems_list.setModel(add_gem_model) - - def get_selected_project_name(self) -> str: - return os.path.basename(self.path_for_selection()) + if not self.project_list_box.currentText(): + msg_box = QMessageBox(parent=self.dialog) + msg_box.setWindowTitle("O3DE") + msg_box.setText("Please select a project") + msg_box.exec() + return + self.launch_with_project_path(self.get_selected_project_path()) def get_launch_project(self) -> str: - return os.path.normpath(self.path_for_selection()) + return os.path.normpath(self.get_selected_project_path()) def get_executable_launch_params(self) -> list: """ @@ -228,363 +237,718 @@ class ProjectDialog(QObject): f'-regset="/Amazon/AzCore/Bootstrap/project_path={self.get_launch_project()}"'] return launch_params - def load_gems(self) -> None: + def launch_with_project_path(self, project_path: str) -> None: """ - Starts another thread to discover available gems. This requires file discovery/parsing and would likely - cause a noticeable pause if it were not on another thread + Launch the desired application given the selected project + :param project_path: Path to currently selected project :return: None """ - self.load_thread = threading.Thread(target=self.load_gems_thread_func) - self.load_thread.start() + logger.info(f'Attempting to open {project_path}') + self.update_mru_list(project_path) + launch_params = self.get_executable_launch_params() + logger.info(f'Launching with params {launch_params}') + subprocess.run(launch_params, env=uninstall_env()) + + def get_selected_project_path(self) -> str: + if self.project_list_box.currentIndex() == -1: + logger.warning("No project selected") + return "" + return self.project_list_box.itemData(self.project_list_box.currentIndex(), Qt.ToolTipRole) - def load_gems_thread_func(self) -> None: + def get_selected_project_name(self) -> str: + project_data = registration.get_project_data(project_path=self.get_selected_project_path()) + return project_data['project_name'] + + def create_project_handler(self): """ - Actual load function for load_gems thread. Blocking call to lower level find method to fill out gems_list + Opens the Create Project pane. Retrieves a list of available templates for display :return: None """ - self.gems_list = add_remove_gem.find_all_gems([os.path.join(engine_path, 'Gems')]) + loader = QUiLoader() + self.create_project_file = QFile(self.create_project_ui_file_path.as_posix()) + + if not self.create_project_file: + logger.error(f'Failed to create project UI file at {self.create_project_file}') + return + + self.create_project_dialog = loader.load(self.create_project_file) + + if not self.create_project_dialog: + logger.error(f'Failed to load create project dialog file at {self.create_project_file}') + return + + self.create_project_ok_button = self.create_project_dialog.findChild(QDialogButtonBox, 'okCancel') + self.create_project_ok_button.accepted.connect(self.create_project_accepted_handler) + + self.create_project_template_list = self.create_project_dialog.findChild(QListView, 'projectTemplates') + self.refresh_create_project_template_list() + + self.create_project_dialog.exec() - def load_template_list(self) -> None: + def create_project_accepted_handler(self) -> None: """ - Search for available templates to fill out template list + Searches the available gems list for selected gems and attempts to add each one to the current project. + Updates UI after completion. :return: None """ - self.project_templates = engine_template.find_all_project_templates([os.path.join(engine_path, 'Templates')]) - def get_gem_info(self, gem_name: str) -> str: + selected_item = self.create_project_template_list.selectionModel().currentIndex() + project_template_path = self.create_project_template_list.model().data(selected_item) + if not project_template_path: + return + + folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Project Name", + registration.get_o3de_projects_folder().as_posix()) + folder_dialog.setFileMode(QFileDialog.AnyFile) + folder_dialog.setOptions(QFileDialog.ShowDirsOnly) + project_count = 0 + project_name = "MyNewProject" + while os.path.exists(os.path.join(engine_path, project_name)): + project_name = f"MyNewProject{project_count}" + project_count += 1 + folder_dialog.selectFile(project_name) + project_folder = None + if folder_dialog.exec(): + project_folder = folder_dialog.selectedFiles() + if project_folder: + if engine_template.create_project(project_path=project_folder[0], + template_path=project_template_path) == 0: + # Success + registration.register(project_path=project_folder[0]) + self.refresh_project_list() + msg_box = QMessageBox(parent=self.dialog) + msg_box.setWindowTitle("O3DE") + msg_box.setText(f"Project {project_folder[0]} created.") + msg_box.exec() + return + + def create_gem_handler(self): """ - Provided a known gem name provides gem info - :param gem_name: Name of known gem. Names are based on Gem. module names in CMakeLists.txt rather - than folders a Gem lives in. - :return: Dictionary with data about gem if known + Opens the Create Gem pane. Retrieves a list of available templates for display + :return: None """ - for this_gem in self.gems_list: - if this_gem.get('Name') == gem_name: - this_gem - return None + loader = QUiLoader() + self.create_gem_file = QFile(self.create_gem_ui_file_path.as_posix()) + + if not self.create_gem_file: + logger.error(f'Failed to create gem UI file at {self.create_gem_file}') + return + + self.create_gem_dialog = loader.load(self.create_gem_file) + + if not self.create_gem_dialog: + logger.error(f'Failed to load create gem dialog file at {self.create_gem_file}') + return + + self.create_gem_ok_button = self.create_gem_dialog.findChild(QDialogButtonBox, 'okCancel') + self.create_gem_ok_button.accepted.connect(self.create_gem_accepted_handler) + + self.create_gem_template_list = self.create_gem_dialog.findChild(QListView, 'gemTemplates') + self.refresh_create_gem_template_list() - def get_gem_info_by_path(self, gem_path: str) -> str: + self.create_gem_dialog.exec() + + def create_gem_accepted_handler(self) -> None: """ - Provided a gem path returns the gem info if known - :param gem_path: Path to gem - :return: Dictionary with data about gem if known + Searches the available gems list for selected gems and attempts to add each one to the current gem. + Updates UI after completion. + :return: None """ - for this_gem in self.gems_list: - if this_gem.get('Path') == gem_path: - return this_gem - return None + selected_item = self.create_gem_template_list.selectionModel().currentIndex() + gem_template_path = self.create_gem_template_list.model().data(selected_item) + if not gem_template_path: + return - def path_for_gem(self, gem_name: str) -> str: + folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Gem Name", + registration.get_o3de_gems_folder().as_posix()) + folder_dialog.setFileMode(QFileDialog.AnyFile) + folder_dialog.setOptions(QFileDialog.ShowDirsOnly) + gem_count = 0 + gem_name = "MyNewGem" + while os.path.exists(os.path.join(engine_path, gem_name)): + gem_name = f"MyNewGem{gem_count}" + gem_count += 1 + folder_dialog.selectFile(gem_name) + gem_folder = None + if folder_dialog.exec(): + gem_folder = folder_dialog.selectedFiles() + if gem_folder: + if engine_template.create_gem(gem_path=gem_folder[0], + template_path=gem_template_path) == 0: + # Success + registration.register(gem_path=gem_folder[0]) + msg_box = QMessageBox(parent=self.dialog) + msg_box.setWindowTitle("O3DE") + msg_box.setText(f"Gem {gem_folder[0]} created.") + msg_box.exec() + return + + def create_template_handler(self): """ - Provided a known gem name provides the full path on disk - :param gem_name: Name of known gem. Names are based on Gem. module names in CMakeLists.txt rather - than folders a Gem lives in. - :return: Path to CMakeLists.txt containing the Gem modules + Opens a foldr select dialog and lets the user select the source folder they want to make a template + out of, then opens a second folder select dialog to get where they want to put the template and it name + :return: None """ - for this_gem in self.gems_list: - if this_gem.get('Name') == gem_name: - return this_gem.get('Path') - return '' - def open_project(self, project_path: str) -> None: + source_folder = QFileDialog.getExistingDirectory(self.dialog, + "Select a Folder to make a template out of.", + registration.get_o3de_folder().as_posix()) + if not source_folder: + return + + destination_template_folder_dialog = QFileDialog(self.dialog, + "Select where the template is to be created and named.", + registration.get_o3de_templates_folder().as_posix()) + destination_template_folder_dialog.setFileMode(QFileDialog.AnyFile) + destination_template_folder_dialog.setOptions(QFileDialog.ShowDirsOnly) + destination_folder = None + if destination_template_folder_dialog.exec(): + destination_folder = destination_template_folder_dialog.selectedFiles() + if not destination_folder: + return + + if engine_template.create_template(source_path=source_folder, + template_path=destination_folder[0]) == 0: + # Success + registration.register(template_path=destination_folder[0]) + msg_box = QMessageBox(parent=self.dialog) + msg_box.setWindowTitle("O3DE") + msg_box.setText(f"Template {destination_folder[0]} created.") + msg_box.exec() + return + + def create_from_template_handler(self): """ - Launch the desired application given the selected project - :param project_path: Path to currently selected project + Opens the Create from_template pane. Retrieves a list of available from_templates for display :return: None """ - logger.info(f'Attempting to open {project_path}') - self.update_mru_list(project_path) - launch_params = self.get_executable_launch_params() - try: - logger.info(f'Launching with params {launch_params}') - subprocess.Popen(launch_params, env=uninstall_env()) - quit(0) - except subprocess.CalledProcessError as e: - logger.error(f'Failed to start executable with launch params {launch_params} error {e}') + loader = QUiLoader() + self.create_from_template_file = QFile(self.create_from_template_ui_file_path.as_posix()) + + if not self.create_from_template_file: + logger.error(f'Failed to create from_template UI file at {self.create_from_template_file}') + return + + self.create_from_template_dialog = loader.load(self.create_from_template_file) + + if not self.create_from_template_dialog: + logger.error(f'Failed to load create from_template dialog file at {self.create_from_template_file}') + return + + self.create_from_template_ok_button = self.create_from_template_dialog.findChild(QDialogButtonBox, 'okCancel') + self.create_from_template_ok_button.accepted.connect(self.create_from_template_accepted_handler) - def path_for_selection(self) -> str: + self.create_from_template_list = self.create_from_template_dialog.findChild(QListView, 'genericTemplates') + self.refresh_create_from_template_list() + + self.create_from_template_dialog.exec() + + def create_from_template_accepted_handler(self) -> None: """ - Retrive the full path to the project the user currently has selected in the drop down - :return: str path to project + Searches the available gems list for selected gems and attempts to add each one to the current gem. + Updates UI after completion. + :return: None """ - if self.project_list_box.currentIndex() == -1: - logger.warning("No project selected") - return "" - return self.project_list_box.itemData(self.project_list_box.currentIndex(), Qt.ToolTipRole) + create_gem_item = self.get_selected_gem_template() + if not create_gem_item: + return - def accepted_handler(self) -> None: + folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Gem Name", + registration.get_o3de_gems_folder().as_posix()) + folder_dialog.setFileMode(QFileDialog.AnyFile) + folder_dialog.setOptions(QFileDialog.ShowDirsOnly) + gem_count = 0 + gem_name = "MyNewGem" + while os.path.exists(os.path.join(engine_path, gem_name)): + gem_name = f"MyNewGem{gem_count}" + gem_count += 1 + folder_dialog.selectFile(gem_name) + gem_folder = None + if folder_dialog.exec(): + gem_folder = folder_dialog.selectedFiles() + if gem_folder: + if engine_template.create_gem(gem_folder[0], create_gem_item[1]) == 0: + # Success + msg_box = QMessageBox(parent=self.dialog) + msg_box.setWindowTitle("O3DE") + msg_box.setText(f"gem {os.path.basename(os.path.normpath(gem_folder[0]))} created." + " Build your\nnew gem before hitting OK to launch.") + msg_box.exec() + return + + def add_project_handler(self): """ - Override for handling "Ok" on main project dialog to first check whether the user has selected a project and - prompt them to if not. If a project is selected will attempt to open it. + Open a file search dialog looking for a folder which contains a valid project. If valid + will update the mru list with the new entry, if invalid will warn the user. :return: None """ - if not self.project_list_box.currentText(): - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText("Please select a project") - msg_box.exec() - return - self.open_project(self.path_for_selection()) + project_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Project Folder", + registration.get_o3de_projects_folder().as_posix()) + if project_folder: + if registration.register(project_path=project_folder) == 0: + # Success + self.refresh_project_list() - def is_project_folder(self, project_path: str) -> bool: + msg_box = QMessageBox(parent=self.dialog) + msg_box.setWindowTitle("O3DE") + msg_box.setText(f"Added Project {project_folder}.") + msg_box.exec() + return + + def add_gem_handler(self): """ - Checks whether the supplied path appears to be a canonical project folder. - Root of a valid project should contain the canonical file - :param project_path: - :return: + Open a file search dialog looking for a folder which contains a gem. If valid + will update the mru list with the new entry, if invalid will warn the user. + :return: None """ - return os.path.isfile(os.path.join(project_path, project_marker_file)) + gem_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Gem Folder", + registration.get_o3de_gems_folder().as_posix()) + if gem_folder: + if registration.register(gem_path=gem_folder) == 0: + # Success + msg_box = QMessageBox(parent=self.dialog) + msg_box.setWindowTitle("O3DE") + msg_box.setText(f"Added Gem {gem_folder}.") + msg_box.exec() + return - def add_new_project(self, project_folder, update_mru=True, reset_selected=True, validate=True): + def add_template_handler(self): """ - Handle request to add a new project to our display and mru lists. Validation checks whether the folder - appears to be a project. Duplicates should never be added with or without validation. - :param project_folder: Absolute path to project folder - :param update_mru: Should the project also be promoted to "most recent" in our mru list - :param reset_selected: Update our drop down to show this as our current selection - :param validate: Verify the folder appears to be a valid project folder - :return: + Open a file search dialog looking for a folder which contains a valid template. If valid + will update the mru list with the new entry, if invalid will warn the user. + :return: None """ - if validate: - if not self.is_project_folder(project_folder): - QMessageBox.warning(self.dialog, "Invalid Project Folder", - f"{project_folder} does not contain a {project_marker_file}" - f" and does not appear to be a valid project") - return - self.add_projects([project_folder]) - if reset_selected: - self.project_list_box.setCurrentIndex(self.project_list_box.count() - 1) - if update_mru: - self.update_mru_list(project_folder) + template_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Template Folder", + registration.get_o3de_templates_folder().as_posix()) + if template_folder: + if registration.register(template_path=template_folder) == 0: + # Success + msg_box = QMessageBox(parent=self.dialog) + msg_box.setWindowTitle("O3DE") + msg_box.setText(f"Added Template {template_folder}.") + msg_box.exec() + return + + def add_restricted_handler(self): + """ + Open a file search dialog looking for a folder which contains a valid template. If valid + will update the mru list with the new entry, if invalid will warn the user. + :return: None + """ + restricted_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Restricted Folder", + registration.get_o3de_restricted_folder().as_posix()) + if restricted_folder: + if registration.register(restricted_path=restricted_folder) == 0: + # Success + msg_box = QMessageBox(parent=self.dialog) + msg_box.setWindowTitle("O3DE") + msg_box.setText(f"Added Restricted {restricted_folder}.") + msg_box.exec() + return - def browse_projects_handler(self): + def remove_project_handler(self): """ - Open a file search dialog looking for a folder which contains a valid project marker. If valid + Open a file search dialog looking for a folder which contains a valid project. If valid will update the mru list with the new entry, if invalid will warn the user. :return: None """ - project_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Project Folder", engine_path) + project_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Project Folder", + registration.get_o3de_projects_folder().as_posix()) if project_folder: - self.add_new_project(project_folder) + if registration.register(project_path=project_folder, remove=True) == 0: + # Success + self.refresh_project_list() + msg_box = QMessageBox(parent=self.dialog) + msg_box.setWindowTitle("O3DE") + msg_box.setText(f"Removed Project {project_folder}.") + msg_box.exec() return - def get_selected_project_gems(self) -> list: + def remove_gem_handler(self): """ - :return: List of (GemName, GemPath) of currently selected gems in the project gems list + Open a file search dialog looking for a folder which contains a gem. If valid + will update the mru list with the new entry, if invalid will warn the user. + :return: None """ - selected_items = self.project_gems.selectionModel().selectedRows() - return [self.project_gems.model().data(item) for item in selected_items] + gem_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Gem Folder", + registration.get_o3de_gems_folder().as_posix()) + if gem_folder: + if registration.register(gem_path=gem_folder, remove=True) == 0: + # Success + msg_box = QMessageBox(parent=self.dialog) + msg_box.setWindowTitle("O3DE") + msg_box.setText(f"Removed Gem {gem_folder}.") + msg_box.exec() + return - def remove_gems_handler(self): + def remove_template_handler(self): """ - Finds the currently selected gems in the active gems list and attempts to remove each and updates the UI. + Open a file search dialog looking for a folder which contains a valid template. If valid + will update the mru list with the new entry, if invalid will warn the user. :return: None """ - remove_gems = self.get_selected_project_gems() + template_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Template Folder", + registration.get_o3de_templates_folder().as_posix()) + if template_folder: + if registration.register(template_path=template_folder, remove=True) == 0: + # Success + msg_box = QMessageBox(parent=self.dialog) + msg_box.setWindowTitle("O3DE") + msg_box.setText(f"Removed Template {template_folder}.") + msg_box.exec() + return - for this_gem in remove_gems: - gem_path = self.path_for_gem(this_gem) - add_remove_gem.add_remove_gem(add=False, - dev_root=engine_path, - gem_path=gem_path or os.path.join(engine_path, 'Gems', this_gem), - gem_target=this_gem, - project_path=self.path_for_selection(), - dependencies_file=None, - runtime_dependency=True, - tool_dependency=True, - server_dependency=True) - self.update_gems() + def remove_restricted_handler(self): + """ + Open a file search dialog looking for a folder which contains a valid template. If valid + will update the mru list with the new entry, if invalid will warn the user. + :return: None + """ + restricted_folder = QFileDialog.getExistingDirectory(self.dialog, "Select Restricted Folder", + registration.get_o3de_restricted_folder().as_posix()) + if restricted_folder: + if registration.register(restricted_path=restricted_folder, remove=True) == 0: + # Success + msg_box = QMessageBox(parent=self.dialog) + msg_box.setWindowTitle("O3DE") + msg_box.setText(f"Removed Restricted {restricted_folder}.") + msg_box.exec() + return - def manage_gems_handler(self): + def manage_runtime_project_gem_targets_handler(self): """ Opens the Gem management pane. Waits for the load thread to complete if still running and displays all active gems for the current project as well as all available gems which aren't currently active. :return: None """ - if not self.path_for_selection(): + if not self.get_selected_project_path(): msg_box = QMessageBox(parent=self.dialog) msg_box.setWindowTitle("O3DE") msg_box.setText("Please select a project") msg_box.exec() return - self.load_thread.join() loader = QUiLoader() - self.manage_gems_file = QFile(self.my_ui_manage_gems_path) + self.manage_project_gem_targets_file = QFile(self.manage_project_gem_targets_ui_file_path.as_posix()) - if not self.manage_gems_file: - logger.error(f'Failed to load gems UI file at {self.manage_gems_file}') + if not self.manage_project_gem_targets_file: + logger.error(f'Failed to load manage gem targets UI file at {self.manage_project_gem_targets_ui_file_path}') return - self.manage_gems_dialog = loader.load(self.manage_gems_file) + self.manage_project_gem_targets_dialog = loader.load(self.manage_project_gem_targets_file) - if not self.manage_gems_dialog: - logger.error(f'Failed to load gems dialog file at {self.manage_gems_file}') + if not self.manage_project_gem_targets_dialog: + logger.error(f'Failed to load gems dialog file at {self.manage_project_gem_targets_ui_file_path.as_posix()}') return - self.manage_gems_dialog.setWindowTitle(f"Manage Gems for {self.get_selected_project_name()}") - - self.add_gems_button = self.manage_gems_dialog.findChild(QPushButton, 'addGemsButton') - self.add_gems_button.clicked.connect(self.add_gems_handler) + self.manage_project_gem_targets_dialog.setWindowTitle(f"Manage Runtime Gem Targets for Project:" + f" {self.get_selected_project_name()}") - self.add_gems_list = self.manage_gems_dialog.findChild(QListView, 'addGemsList') + self.add_gem_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, 'addGemTargetsButton') + self.add_gem_button.clicked.connect(self.add_runtime_project_gem_targets_handler) - self.remove_gems_button = self.manage_gems_dialog.findChild(QPushButton, 'removeGemsButton') - self.remove_gems_button.clicked.connect(self.remove_gems_handler) + self.available_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, + 'availableGemTargetsList') + self.refresh_runtime_project_gem_targets_available_list() - self.project_gems = self.manage_gems_dialog.findChild(QListView, 'projectGems') - self.update_gems() + self.remove_project_gem_targets_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, + 'removeGemTargetsButton') + self.remove_project_gem_targets_button.clicked.connect(self.remove_runtime_project_gem_targets_handler) - self.manage_gems_dialog.exec() + self.enabled_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, + 'enabledGemTargetsList') + self.refresh_runtime_project_gem_targets_enabled_list() - def get_selected_add_gems(self) -> list: - """ - Find returns a list of currently selected gems in the add listas (GemName, GemPath) - :return: - """ - selected_items = self.add_gems_list.selectionModel().selectedRows() - return [(self.add_gems_list.model().data(item), self.add_gems_list.model().data(item, Qt.UserRole)) for - item in selected_items] + self.manage_project_gem_targets_dialog.exec() - def add_gems_handler(self) -> None: + def manage_tool_project_gem_targets_handler(self): """ - Searches the available gems list for selected gems and attempts to add each one to the current project. - Updates UI after completion. + Opens the Gem management pane. Waits for the load thread to complete if still running and displays all + active gems for the current project as well as all available gems which aren't currently active. :return: None """ - add_gems_list = self.get_selected_add_gems() - for this_gem in add_gems_list: - gem_info = self.get_gem_info_by_path(this_gem[1]) - if not gem_info: - logger.error(f'Unknown gem {this_gem}!') - continue - add_remove_gem.add_remove_gem(add=True, - dev_root=engine_path, - gem_path=this_gem[1], - gem_target=gem_info.get('Name'), - project_path=self.path_for_selection(), - dependencies_file=None, - runtime_dependency=gem_info.get('Runtime', False), - tool_dependency=gem_info.get('Tools', False), - server_dependency=gem_info.get('Tools', False)) - self.update_gems() - def create_project_handler(self): - """ - Opens the Create Project pane. Retrieves a list of available templates for display - :return: None - """ + if not self.get_selected_project_path(): + msg_box = QMessageBox(parent=self.dialog) + msg_box.setWindowTitle("O3DE") + msg_box.setText("Please select a project") + msg_box.exec() + return + loader = QUiLoader() - self.create_project_file = QFile(self.my_ui_create_project_path) + self.manage_project_gem_targets_file = QFile(self.manage_project_gem_targets_ui_file_path.as_posix()) - if not self.create_project_file: - logger.error(f'Failed to create project UI file at {self.create_project_file}') + if not self.manage_project_gem_targets_file: + logger.error(f'Failed to load manage gem targets UI file at {self.manage_project_gem_targets_ui_file_path}') return - self.create_project_dialog = loader.load(self.create_project_file) + self.manage_project_gem_targets_dialog = loader.load(self.manage_project_gem_targets_file) - if not self.create_project_dialog: - logger.error(f'Failed to load create project dialog file at {self.create_project_file}') + if not self.manage_project_gem_targets_dialog: + logger.error( + f'Failed to load gems dialog file at {self.manage_project_gem_targets_ui_file_path.as_posix()}') return - self.create_project_ok_button = self.create_project_dialog.findChild(QDialogButtonBox, 'okCancel') - self.create_project_ok_button.accepted.connect(self.create_project_accepted_handler) + self.manage_project_gem_targets_dialog.setWindowTitle(f"Manage Tool Gem Targets for Project:" + f" {self.get_selected_project_name()}") - self.project_template_list = self.create_project_dialog.findChild(QListView, 'projectTemplates') + self.add_gem_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, 'addGemTargetsButton') + self.add_gem_button.clicked.connect(self.add_tool_project_gem_targets_handler) - self.load_template_list() + self.available_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, + 'availableGemTargetsList') + self.refresh_tool_project_gem_targets_available_list() - self.load_template_model = QStandardItemModel() - for item in self.project_templates: - model_item = QStandardItem(item[0]) - model_item.setData(item[1], Qt.UserRole) - self.load_template_model.appendRow(model_item) + self.remove_project_gem_targets_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, + 'removeGemTargetsButton') + self.remove_project_gem_targets_button.clicked.connect(self.remove_tool_project_gem_targets_handler) - self.project_template_list.setModel(self.load_template_model) + self.enabled_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, + 'enabledGemTargetsList') + self.refresh_tool_project_gem_targets_enabled_list() - self.create_project_dialog.exec() + self.manage_project_gem_targets_dialog.exec() - def get_selected_project_template(self) -> tuple: + def manage_server_project_gem_targets_handler(self): """ - Get the current pair templatename, path to template selecte dby the user - :return: pair + Opens the Gem management pane. Waits for the load thread to complete if still running and displays all + active gems for the current project as well as all available gems which aren't currently active. + :return: None """ - selected_item = self.project_template_list.selectionModel().currentIndex() - if not selected_item.isValid(): - logger.warning("Select a template to create from") - return None + if not self.get_selected_project_path(): + msg_box = QMessageBox(parent=self.dialog) + msg_box.setWindowTitle("O3DE") + msg_box.setText("Please select a project") + msg_box.exec() + return - create_project_item = (self.project_template_list.model().data(selected_item), - self.project_template_list.model().data(selected_item, Qt.UserRole)) - return create_project_item + loader = QUiLoader() + self.manage_project_gem_targets_file = QFile(self.manage_project_gem_targets_ui_file_path.as_posix()) - def create_project_accepted_handler(self) -> None: - """ - Searches the available gems list for selected gems and attempts to add each one to the current project. - Updates UI after completion. - :return: None - """ - create_project_item = self.get_selected_project_template() - if not create_project_item: + if not self.manage_project_gem_targets_file: + logger.error(f'Failed to load manage gem targets UI file at {self.manage_project_gem_targets_ui_file_path}') return - folder_dialog = QFileDialog(self.dialog, "Select a Folder and Enter a New Project Name", engine_path) - folder_dialog.setFileMode(QFileDialog.AnyFile) - folder_dialog.setOptions(QFileDialog.ShowDirsOnly) - project_count = 0 - project_name = "MyNewProject" - while os.path.exists(os.path.join(engine_path, project_name)): - project_name = f"MyNewProject{project_count}" - project_count += 1 - folder_dialog.selectFile(project_name) - project_folder = None - if folder_dialog.exec(): - project_folder = folder_dialog.selectedFiles() - if project_folder: - if engine_template.create_project(engine_path, project_folder[0], create_project_item[1]) == 0: - # Success - self.add_new_project(project_folder[0], validate=False) - msg_box = QMessageBox(parent=self.dialog) - msg_box.setWindowTitle("O3DE") - msg_box.setText(f"Project {os.path.basename(os.path.normpath(project_folder[0]))} created." - " Build your\nnew project before hitting OK to launch.") - msg_box.exec() - return + self.manage_project_gem_targets_dialog = loader.load(self.manage_project_gem_targets_file) - def get_display_name(self, project_path: str) -> str: - """ - Returns the project path in the format to be displayed in the projects MRU list - :param project_path: Path to the project folder - :return: Formatted path - """ - return f'{os.path.basename(os.path.normpath(project_path))} ({project_path})' + if not self.manage_project_gem_targets_dialog: + logger.error( + f'Failed to load gems dialog file at {self.manage_project_gem_targets_ui_file_path.as_posix()}') + return - def add_projects(self, new_list: List[str]) -> None: - """ - Attempt to add a list of known projects. Performs validation first that the supplied folder appears valid and - silently drops invalid folders - these can simply be folders which were previously valid and are in the MRU list - but have been moved or deleted. Used both when loading the MRU list or when a user browses or creates a new - project - :param new_list: List of full paths to projects to add - :return: None - """ - new_display_items = [] - new_display_paths = [] - for this_item in new_list: - if self.is_project_folder(this_item) and this_item not in self.displayed_projects: - self.displayed_projects.append(this_item) - new_display_items.append(self.get_display_name(this_item)) - new_display_paths.append(this_item) - # Storing the full path in the tooltip, if this is altered we need to store this elsewhere - # as it's used when selecting a project to open - - for this_slot in range(len(new_display_items)): - self.project_list_box.addItem(new_display_items[this_slot]) - self.project_list_box.setItemData(self.project_list_box.count() - 1, new_display_paths[this_slot], - Qt.ToolTipRole) + self.manage_project_gem_targets_dialog.setWindowTitle(f"Manage Server Gem Targets for Project:" + f" {self.get_selected_project_name()}") + + self.add_gem_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, 'addGemTargetsButton') + self.add_gem_button.clicked.connect(self.add_server_project_gem_targets_handler) + + self.available_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, + 'availableGemTargetsList') + self.refresh_server_project_gem_targets_available_list() + + self.remove_project_gem_targets_button = self.manage_project_gem_targets_dialog.findChild(QPushButton, + 'removeGemTargetsButton') + self.remove_project_gem_targets_button.clicked.connect(self.remove_server_project_gem_targets_handler) + + self.enabled_gem_targets_list = self.manage_project_gem_targets_dialog.findChild(QListView, + 'enabledGemTargetsList') + self.refresh_server_project_gem_targets_enabled_list() + + self.manage_project_gem_targets_dialog.exec() + + def manage_project_gem_targets_get_selected_available_gems(self) -> list: + selected_items = self.available_gem_targets_list.selectionModel().selectedRows() + return [(self.available_gem_targets_list.model().data(item)) for item in selected_items] + + def manage_project_gem_targets_get_selected_enabled_gems(self) -> list: + selected_items = self.enabled_gem_targets_list.selectionModel().selectedRows() + return [(self.enabled_gem_targets_list.model().data(item)) for item in selected_items] + + def add_runtime_project_gem_targets_handler(self) -> None: + gem_paths = registration.get_all_gems() + for gem_target in self.manage_project_gem_targets_get_selected_available_gems(): + for gem_path in gem_paths: + this_gems_targets = registration.get_gem_targets(gem_path=gem_path) + for this_gem_target in this_gems_targets: + if gem_target == this_gem_target: + registration.add_gem_to_project(gem_path=gem_path, + gem_target=gem_target, + project_path=self.get_selected_project_path(), + runtime_dependency=True) + self.refresh_runtime_project_gem_targets_available_list() + self.refresh_runtime_project_gem_targets_enabled_list() + return + self.refresh_runtime_project_gem_targets_available_list() + self.refresh_runtime_project_gem_targets_enabled_list() + + def remove_runtime_project_gem_targets_handler(self): + gem_paths = registration.get_all_gems() + for gem_target in self.manage_project_gem_targets_get_selected_enabled_gems(): + for gem_path in gem_paths: + this_gems_targets = registration.get_gem_targets(gem_path=gem_path) + for this_gem_target in this_gems_targets: + if gem_target == this_gem_target: + registration.remove_gem_from_project(gem_path=gem_path, + gem_target=gem_target, + project_path=self.get_selected_project_path(), + runtime_dependency=True) + self.refresh_runtime_project_gem_targets_available_list() + self.refresh_runtime_project_gem_targets_enabled_list() + return + self.refresh_runtime_project_gem_targets_available_list() + self.refresh_runtime_project_gem_targets_enabled_list() + + def add_tool_project_gem_targets_handler(self) -> None: + gem_paths = registration.get_all_gems() + for gem_target in self.manage_project_gem_targets_get_selected_available_gems(): + for gem_path in gem_paths: + this_gems_targets = registration.get_gem_targets(gem_path=gem_path) + for this_gem_target in this_gems_targets: + if gem_target == this_gem_target: + registration.add_gem_to_project(gem_path=gem_path, + gem_target=gem_target, + project_path=self.get_selected_project_path(), + tool_dependency=True) + self.refresh_tool_project_gem_targets_available_list() + self.refresh_tool_project_gem_targets_enabled_list() + return + self.refresh_tool_project_gem_targets_available_list() + self.refresh_tool_project_gem_targets_enabled_list() + + def remove_tool_project_gem_targets_handler(self): + gem_paths = registration.get_all_gems() + for gem_target in self.manage_project_gem_targets_get_selected_enabled_gems(): + for gem_path in gem_paths: + this_gems_targets = registration.get_gem_targets(gem_path=gem_path) + for this_gem_target in this_gems_targets: + if gem_target == this_gem_target: + registration.remove_gem_from_project(gem_path=gem_path, + gem_target=gem_target, + project_path=self.get_selected_project_path(), + tool_dependency=True) + self.refresh_tool_project_gem_targets_available_list() + self.refresh_tool_project_gem_targets_enabled_list() + return + self.refresh_tool_project_gem_targets_available_list() + self.refresh_tool_project_gem_targets_enabled_list() + + def add_server_project_gem_targets_handler(self) -> None: + gem_paths = registration.get_all_gems() + for gem_target in self.manage_project_gem_targets_get_selected_available_gems(): + for gem_path in gem_paths: + this_gems_targets = registration.get_gem_targets(gem_path=gem_path) + for this_gem_target in this_gems_targets: + if gem_target == this_gem_target: + registration.add_gem_to_project(gem_path=gem_path, + gem_target=gem_target, + project_path=self.get_selected_project_path(), + server_dependency=True) + self.refresh_server_project_gem_targets_available_list() + self.refresh_server_project_gem_targets_enabled_list() + return + self.refresh_server_project_gem_targets_available_list() + self.refresh_server_project_gem_targets_enabled_list() + + def remove_server_project_gem_targets_handler(self): + gem_paths = registration.get_all_gems() + for gem_target in self.manage_project_gem_targets_get_selected_enabled_gems(): + for gem_path in gem_paths: + this_gems_targets = registration.get_gem_targets(gem_path=gem_path) + for this_gem_target in this_gems_targets: + if gem_target == this_gem_target: + registration.remove_gem_from_project(gem_path=gem_path, + gem_target=gem_target, + project_path=self.get_selected_project_path(), + server_dependency=True) + self.refresh_server_project_gem_targets_available_list() + self.refresh_server_project_gem_targets_enabled_list() + return + self.refresh_server_project_gem_targets_available_list() + self.refresh_server_project_gem_targets_enabled_list() + + def refresh_runtime_project_gem_targets_enabled_list(self) -> None: + enabled_project_gem_targets_model = QStandardItemModel() + enabled_project_gem_targets = registration.get_project_runtime_gem_targets( + project_path=self.get_selected_project_path()) + for gem_target in sorted(enabled_project_gem_targets): + model_item = QStandardItem(gem_target) + enabled_project_gem_targets_model.appendRow(model_item) + self.enabled_gem_targets_list.setModel(enabled_project_gem_targets_model) + + def refresh_runtime_project_gem_targets_available_list(self) -> None: + available_project_gem_targets_model = QStandardItemModel() + enabled_project_gem_targets = registration.get_project_runtime_gem_targets( + project_path=self.get_selected_project_path()) + all_gem_targets = registration.get_all_gem_targets() + for gem_target in sorted(all_gem_targets): + if gem_target not in enabled_project_gem_targets: + model_item = QStandardItem(gem_target) + available_project_gem_targets_model.appendRow(model_item) + self.available_gem_targets_list.setModel(available_project_gem_targets_model) + + def refresh_tool_project_gem_targets_enabled_list(self) -> None: + enabled_project_gem_targets_model = QStandardItemModel() + enabled_project_gem_targets = registration.get_project_tool_gem_targets( + project_path=self.get_selected_project_path()) + for gem_target in sorted(enabled_project_gem_targets): + model_item = QStandardItem(gem_target) + enabled_project_gem_targets_model.appendRow(model_item) + self.enabled_gem_targets_list.setModel(enabled_project_gem_targets_model) + + def refresh_tool_project_gem_targets_available_list(self) -> None: + available_project_gem_targets_model = QStandardItemModel() + enabled_project_gem_targets = registration.get_project_tool_gem_targets( + project_path=self.get_selected_project_path()) + all_gem_targets = registration.get_all_gem_targets() + for gem_target in sorted(all_gem_targets): + if gem_target not in enabled_project_gem_targets: + model_item = QStandardItem(gem_target) + available_project_gem_targets_model.appendRow(model_item) + self.available_gem_targets_list.setModel(available_project_gem_targets_model) + + def refresh_server_project_gem_targets_enabled_list(self) -> None: + enabled_project_gem_targets_model = QStandardItemModel() + enabled_project_gem_targets = registration.get_project_server_gem_targets( + project_path=self.get_selected_project_path()) + for gem_target in sorted(enabled_project_gem_targets): + model_item = QStandardItem(gem_target) + enabled_project_gem_targets_model.appendRow(model_item) + self.enabled_gem_targets_list.setModel(enabled_project_gem_targets_model) + + def refresh_server_project_gem_targets_available_list(self) -> None: + available_project_gem_targets_model = QStandardItemModel() + enabled_project_gem_targets = registration.get_project_server_gem_targets( + project_path=self.get_selected_project_path()) + all_gem_targets = registration.get_all_gem_targets() + for gem_target in sorted(all_gem_targets): + if gem_target not in enabled_project_gem_targets: + model_item = QStandardItem(gem_target) + available_project_gem_targets_model.appendRow(model_item) + self.available_gem_targets_list.setModel(available_project_gem_targets_model) + + def refresh_create_project_template_list(self) -> None: + self.create_project_template_model = QStandardItemModel() + for project_template_path in registration.get_project_templates(): + model_item = QStandardItem(project_template_path) + self.create_project_template_model.appendRow(model_item) + self.create_project_template_list.setModel(self.create_project_template_model) + + def refresh_create_gem_template_list(self) -> None: + self.create_gem_template_model = QStandardItemModel() + for gem_template_path in registration.get_gem_templates(): + model_item = QStandardItem(gem_template_path) + self.create_gem_template_model.appendRow(model_item) + self.create_gem_template_list.setModel(self.create_gem_template_model) + + def refresh_create_from_template_list(self) -> None: + self.create_from_template_model = QStandardItemModel() + for generic_template_path in registration.get_generic_templates(): + model_item = QStandardItem(generic_template_path) + self.create_from_template_model.appendRow(model_item) + self.create_from_template_list.setModel(self.create_from_template_model) def update_mru_list(self, used_project: str) -> None: """ @@ -621,7 +985,7 @@ class ProjectDialog(QObject): def get_mru_list(self) -> List[str]: """ - Retrive the current MRU list. Does not perform validation that the projects still appear valid + Retrieve the current MRU list. Does not perform validation that the projects still appear valid :return: list of full path strings to project folders """ if not os.path.exists(os.path.dirname(self.mru_file_path)): @@ -653,6 +1017,6 @@ class ProjectDialog(QObject): if __name__ == "__main__": dialog_app = QApplication(sys.argv) - my_dialog = ProjectDialog() + my_dialog = ProjectManagerDialog() dialog_app.exec_() sys.exit(0) diff --git a/scripts/project_manager/tests/test_projects.py b/scripts/project_manager/tests/test_projects.py index 69895accfd..d073cf6c7a 100755 --- a/scripts/project_manager/tests/test_projects.py +++ b/scripts/project_manager/tests/test_projects.py @@ -9,54 +9,67 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # + import pytest +''' import os import sys import tempfile import logging +import pathlib from unittest.mock import MagicMock logger = logging.getLogger() # Code lives one folder above -projects_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) -sys.path.append(projects_path) +project_manager_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..')) +sys.path.append(project_manager_path) from pyside import add_pyside_environment, is_configuration_valid from ly_test_tools import WINDOWS -project_marker_file = "project.json" +sys.path.append(os.path.realpath(os.path.join(os.path.dirname(__file__), '..', '..', '..'))) +executable_path = '' +from cmake.Tools import registration +from cmake.Tools import engine_template + class ProjectHelper: def __init__(self): - self._temp_directory = tempfile.TemporaryDirectory() - self.temp_project_root = self._temp_directory.name - self.temp_file_dir = os.path.join(self.temp_project_root, "o3de") + self._temp_directory = pathlib.Path(tempfile.TemporaryDirectory().name).resolve() + self._temp_directory.mkdir(parents=True, exist_ok=True) + + self.home_path = self._temp_directory + registration.override_home_folder = self.home_path + self.engine_path = registration.get_this_engine_path() + if registration.register(engine_path=self.engine_path): + assert True, f"Failed to register the engine." + + if registration.register_shipped_engine_o3de_objects(): + assert True, f"Failed to register shipped engine objects." + + self.projects_folder = registration.get_o3de_projects_folder() + if not self.projects_folder.is_dir(): + assert True self.application = None self.dialog = None - if not os.path.exists(self.temp_file_dir): - os.makedirs(self.temp_file_dir) - def create_empty_projects(self): - self.project_1_dir = os.path.join(self.temp_project_root, "Project1") - if not os.path.exists(self.project_1_dir): - os.makedirs(self.project_1_dir) - with open(os.path.join(self.project_1_dir,project_marker_file), 'w') as marker_file: - marker_file.write("{}") - self.project_2_dir = os.path.join(self.temp_project_root, "Project2") - if not os.path.exists(self.project_2_dir): - os.makedirs(self.project_2_dir) - with open(os.path.join(self.project_2_dir, project_marker_file), 'w') as marker_file: - marker_file.write("{}") - self.project_3_dir = os.path.join(self.temp_project_root, "Project3") - if not os.path.exists(self.project_3_dir): - os.makedirs(self.project_3_dir) - with open(os.path.join(self.project_3_dir, project_marker_file), 'w') as marker_file: - marker_file.write("{}") - self.invalid_project_dir = os.path.join(self.temp_project_root, "InvalidProject") + self.project_1_dir = self.projects_folder / "Project1" + if engine_template.create_project(project_manager_path=self.project_1_dir): + assert True, f"Failed to create Project1." + + self.project_2_dir = self.projects_folder / "Project2" + if engine_template.create_project(project_manager_path=self.project_2_dir): + assert True, f"Failed to create Project2." + + self.project_3_dir = self.projects_folder / "Project3" + if engine_template.create_project(project_manager_path=self.project_3_dir): + assert True, f"Failed to create Project3." + self.invalid_project_dir = self.projects_folder / "InvalidProject" + self.invalid_project_dir.mkdir(parents=True, exist_ok=True) def setup_dialog_test(self, workspace): add_pyside_environment(workspace.paths.build_directory()) @@ -66,6 +79,7 @@ class ProjectHelper: # need to use the profile version of PySide which works with the profile QT libs which aren't in the debug # folder we've built. return None + from PySide2.QtWidgets import QApplication, QMessageBox if QApplication.instance(): @@ -74,13 +88,13 @@ class ProjectHelper: self.application = QApplication(sys.argv) assert self.application - from projects import ProjectDialog + from projects import ProjectManagerDialog try: - self.dialog = ProjectDialog(settings_folder=self.temp_file_dir) + self.dialog = ProjectManagerDialog(settings_folder=self.home_path) return self.dialog except Exception as e: - logger.error(f'Failed to create ProjectDialog with error {e}') + logger.error(f'Failed to create ProjectManagerDialog with error {e}') return None def create_project_from_template(self, project_name) -> bool: @@ -90,21 +104,24 @@ class ProjectHelper: :return: True for Success, False for failure """ from PySide2.QtWidgets import QWidget, QFileDialog - from projects import ProjectDialog + from projects import ProjectManagerDialog QWidget.exec = MagicMock() self.dialog.create_project_handler() QWidget.exec.assert_called_once() assert len(self.dialog.project_templates), 'Failed to find any project templates' - ProjectDialog.get_selected_project_template = MagicMock(return_value=self.dialog.project_templates[0]) + ProjectManagerDialog.get_selected_project_template = MagicMock(return_value=self.dialog.project_templates[0]) QFileDialog.exec = MagicMock() - create_project_dir = os.path.join(self.temp_project_root, project_name) - QFileDialog.selectedFiles = MagicMock(return_value=[create_project_dir]) + create_project_path = self.projects_folder / project_name + QFileDialog.selectedFiles = MagicMock(return_value=[create_project_path]) self.dialog.create_project_accepted_handler() - assert os.path.isdir(create_project_dir), f"Expected project folder not found at {create_project_dir}" - assert QWidget.exec.call_count == 2, "Message box confirming project creation failed to show" + if create_project_path.is_dir(): + assert True, f"Expected project creation folder not found at {create_project_path}" + + if QWidget.exec.call_count == 2: + assert True, "Message box confirming project creation failed to show" @pytest.fixture @@ -113,7 +130,7 @@ def project_helper(): @pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently") -@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent +@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent def test_logger_handler(workspace, project_helper): my_dialog = project_helper.setup_dialog_test(workspace) if not my_dialog: @@ -126,7 +143,7 @@ def test_logger_handler(workspace, project_helper): @pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently") -@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent +@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent def test_mru_list(workspace, project_helper): my_dialog = project_helper.setup_dialog_test(workspace) if not my_dialog: @@ -140,16 +157,16 @@ def test_mru_list(workspace, project_helper): assert len(mru_list) == 0, f'MRU list unexpectedly had entries: {mru_list}' QMessageBox.warning = MagicMock() - my_dialog.add_new_project('TestProjectInvalid') + my_dialog.add_project(project_helper.invalid_project_dir) mru_list = my_dialog.get_mru_list() assert len(mru_list) == 0, f'MRU list unexpectedly added an invalid project : {mru_list}' QMessageBox.warning.assert_called_once() - my_dialog.add_new_project(project_helper.project_1_dir) + my_dialog.add_project(project_helper.project_1_dir) mru_list = my_dialog.get_mru_list() assert len(mru_list) == 1, f'MRU list failed to add project at {project_helper.project_1_dir}' - my_dialog.add_new_project(project_helper.project_1_dir) + my_dialog.add_project(project_helper.project_1_dir) mru_list = my_dialog.get_mru_list() assert len(mru_list) == 1, f'MRU list added project at {project_helper.project_1_dir} a second time : {mru_list}' @@ -157,7 +174,7 @@ def test_mru_list(workspace, project_helper): mru_list = my_dialog.get_mru_list() assert len(mru_list) == 1, f'MRU list added project at {project_helper.project_1_dir} a second time : {mru_list}' - my_dialog.add_new_project(project_helper.project_2_dir) + my_dialog.add_project(project_helper.project_2_dir) mru_list = my_dialog.get_mru_list() assert len(mru_list) == 2, f'MRU list failed to add project at {project_helper.project_2_dir}' @@ -170,13 +187,13 @@ def test_mru_list(workspace, project_helper): assert mru_list[0] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't first item" assert mru_list[1] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't second item" - my_dialog.add_new_project(project_helper.invalid_project_dir) + my_dialog.add_project(project_helper.invalid_project_dir) mru_list = my_dialog.get_mru_list() assert len(mru_list) == 2, f'MRU list added invalid item {mru_list}' assert mru_list[0] == project_helper.project_1_dir, f"{project_helper.project_1_dir} wasn't first item" assert mru_list[1] == project_helper.project_2_dir, f"{project_helper.project_2_dir} wasn't second item" - my_dialog.add_new_project(project_helper.project_3_dir) + my_dialog.add_project(project_helper.project_3_dir) mru_list = my_dialog.get_mru_list() assert len(mru_list) == 3, f'MRU list failed to add {project_helper.project_3_dir} : {mru_list}' assert mru_list[0] == project_helper.project_3_dir, f"{project_helper.project_3_dir} wasn't first item" @@ -185,7 +202,7 @@ def test_mru_list(workspace, project_helper): @pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently") -@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent +@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent def test_create_project(workspace, project_helper): my_dialog = project_helper.setup_dialog_test(workspace) if not my_dialog: @@ -195,7 +212,7 @@ def test_create_project(workspace, project_helper): @pytest.mark.skipif(not WINDOWS, reason="PySide2 only works on windows currently") -@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent +@pytest.mark.parametrize('project', ['']) # Workspace wants a project, but this test is not project dependent def test_add_remove_gems(workspace, project_helper): my_dialog = project_helper.setup_dialog_test(workspace) if not my_dialog: @@ -203,32 +220,36 @@ def test_add_remove_gems(workspace, project_helper): my_project_name = "TestAddRemoveGems" - project_helper.create_project_from_template(my_project_name) - my_project_path = os.path.join(project_helper.temp_project_root, my_project_name) + project_helper.create_project_from_template(project_manager_path=my_project_name) + my_project_path = project_helper.projects_folder / my_project_name from PySide2.QtWidgets import QWidget, QFileDialog - from projects import ProjectDialog + from projects import ProjectManagerDialog - assert my_dialog.path_for_selection() == my_project_path, "Gems project not selected" + assert my_dialog.get_selected_project_path() == my_project_path, "TestAddRemoveGems project not selected" QWidget.exec = MagicMock() my_dialog.manage_gems_handler() - assert my_dialog.manage_gems_dialog, "No gem management dialog created" + assert my_dialog.manage_gem_targets_dialog, "No gem management dialog created" QWidget.exec.assert_called_once() - assert len(my_dialog.gems_list), 'Failed to find any gems' - my_test_gem = my_dialog.gems_list[0] - my_test_gem_name = my_test_gem.get("Name") - my_test_gem_path = my_test_gem.get("Path") - my_test_gem_selection = (my_test_gem_name, my_test_gem_path) - ProjectDialog.get_selected_add_gems = MagicMock(return_value=[my_test_gem_selection]) + if not len(my_dialog.all_gems_list): + assert True, 'Failed to find any gems' + my_test_gem_path = my_dialog.all_gems_list[0] + gem_data = registration.get_gem_data(my_test_gem_path) + my_test_gem_selection = (my_test_gem_name, my_test_gem_path) + ProjectManagerDialog.get_selected_add_gems = MagicMock(return_value=[my_test_gem_selection]) assert my_test_gem_name, "No Name set in test gem" assert my_test_gem_name not in my_dialog.project_gem_list, f'Gem {my_test_gem_name} already in project gem list' + my_dialog.add_gems_handler() assert my_test_gem_name in my_dialog.project_gem_list, f'Gem {my_test_gem_name} failed to add to gem list' - ProjectDialog.get_selected_project_gems = MagicMock(return_value=[my_test_gem_name]) + + ProjectManagerDialog.get_selected_project_gems = MagicMock(return_value=[my_test_gem_name]) my_dialog.remove_gems_handler() assert my_test_gem_name not in my_dialog.project_gem_list, f'Gem {my_test_gem_name} still in project gem list' +''' - +def test_project_place_holder(): + pass \ No newline at end of file diff --git a/scripts/project_manager/ui/create_from_template.ui b/scripts/project_manager/ui/create_from_template.ui new file mode 100644 index 0000000000..b6a8740594 --- /dev/null +++ b/scripts/project_manager/ui/create_from_template.ui @@ -0,0 +1,94 @@ + + + createFromTemplateDialog + + + + 0 + 0 + 467 + 288 + + + + Create From Template + + + + + 50 + 250 + 400 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 10 + 20 + 449 + 221 + + + + QAbstractItemView::SingleSelection + + + + + + 10 + 0 + 300 + 16 + + + + Available Templates + + + + + + + okCancel + accepted() + createFromTemplateDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + okCancel + rejected() + createFromTemplateDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/scripts/project_manager/ui/create_gem.ui b/scripts/project_manager/ui/create_gem.ui new file mode 100644 index 0000000000..5a5fd14a6d --- /dev/null +++ b/scripts/project_manager/ui/create_gem.ui @@ -0,0 +1,94 @@ + + + createGemDialog + + + + 0 + 0 + 467 + 288 + + + + Create Gem + + + + + 50 + 250 + 400 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 10 + 20 + 449 + 221 + + + + QAbstractItemView::SingleSelection + + + + + + 10 + 0 + 300 + 16 + + + + Available Templates + + + + + + + okCancel + accepted() + createGemDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + okCancel + rejected() + createGemDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/scripts/project_manager/ui/create_project.ui b/scripts/project_manager/ui/create_project.ui index b4c8fb5caa..67a13325b0 100644 --- a/scripts/project_manager/ui/create_project.ui +++ b/scripts/project_manager/ui/create_project.ui @@ -6,7 +6,7 @@ 0 0 - 226 + 467 288 @@ -18,7 +18,7 @@ 50 250 - 171 + 400 32 @@ -34,7 +34,7 @@ 10 20 - 211 + 449 221 @@ -47,7 +47,7 @@ 10 0 - 101 + 300 16 diff --git a/scripts/project_manager/ui/manage_gems.ui b/scripts/project_manager/ui/manage_gem_targets.ui similarity index 74% rename from scripts/project_manager/ui/manage_gems.ui rename to scripts/project_manager/ui/manage_gem_targets.ui index 36083bd9df..ea9e607d42 100644 --- a/scripts/project_manager/ui/manage_gems.ui +++ b/scripts/project_manager/ui/manage_gem_targets.ui @@ -1,24 +1,24 @@ - addGemsDialog - + manageGemTargetsDialog + 0 0 - 635 - 327 + 702 + 297 - Manage Gems + Manage Gem Targets - 460 - 290 - 171 + 310 + 260 + 71 32 @@ -29,10 +29,10 @@ QDialogButtonBox::Close - + - 380 + 440 50 250 221 @@ -48,7 +48,7 @@ QAbstractItemView::ExtendedSelection - + 10 @@ -64,14 +64,14 @@ - 380 + 440 30 - 91 + 251 16 - Available Gems + Available Gem Targets @@ -79,38 +79,38 @@ 10 30 - 71 + 251 16 - Enabled Gems + Enabled Gem Targets - + - 267 + 264 130 - 105 + 171 23 - Remove Gems >> + Remove Gem Targets >> - + - 267 + 264 100 - 105 + 171 23 - << Add Gems + << Add Gem Targets @@ -118,12 +118,12 @@ 10 5 - 241 - 16 + 400 + 21 - Adding new Gems may require rebuilding + Adding new Gem Targets may require rebuilding @@ -132,7 +132,7 @@ close accepted() - addGemsDialog + manageGemTargetsDialog accept() @@ -148,7 +148,7 @@ close rejected() - addGemsDialog + manageGemTargetsDialog reject() diff --git a/scripts/project_manager/ui/projects.ico b/scripts/project_manager/ui/project_manager.ico similarity index 100% rename from scripts/project_manager/ui/projects.ico rename to scripts/project_manager/ui/project_manager.ico diff --git a/scripts/project_manager/ui/project_manager.ui b/scripts/project_manager/ui/project_manager.ui new file mode 100644 index 0000000000..6b3b5f758c --- /dev/null +++ b/scripts/project_manager/ui/project_manager.ui @@ -0,0 +1,407 @@ + + + Dialog + + + + 0 + 0 + 712 + 395 + + + + + 1 + 0 + + + + O3DE + + + Select and manage your projects for O3DE + + + + + 540 + 360 + 161 + 31 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 10 + 30 + 691 + 31 + + + + Current project to launch or manage gems for. + + + + + + 10 + 10 + 47 + 13 + + + + Project + + + + + + 270 + 220 + 431 + 141 + + + + QFrame::Panel + + + QFrame::Sunken + + + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + 10 + 70 + 241 + 151 + + + + Create + + + + + 10 + 110 + 221 + 31 + + + + Create a new O3DE object from a pre configured template. + + + Create From Template + + + + + + 10 + 20 + 221 + 31 + + + + Create a new O3DE object from a pre configured template. + + + Create Project + + + + + + 11 + 50 + 221 + 31 + + + + Create a new O3DE object from a pre configured template. + + + Create Gem + + + + + + 11 + 80 + 221 + 31 + + + + Create a new O3DE object from a pre configured template. + + + Create Template + + + + + + + 260 + 70 + 451 + 141 + + + + Registration + + + + + 10 + 80 + 211 + 31 + + + + Browse for an existing O3DE project. + + + Add Template + + + + + + 10 + 20 + 211 + 31 + + + + Browse for an existing O3DE project. + + + Add Project + + + + + + 10 + 50 + 211 + 31 + + + + Browse for an existing O3DE project. + + + Add Gem + + + + + + 10 + 110 + 211 + 31 + + + + Browse for an existing O3DE project. + + + Add Restricted + + + + + + 230 + 110 + 211 + 31 + + + + Browse for an existing O3DE project. + + + Remove Restricted + + + + + + 230 + 20 + 211 + 31 + + + + Browse for an existing O3DE project. + + + Remove Project + + + + + + 230 + 50 + 211 + 31 + + + + Browse for an existing O3DE project. + + + Remove Gem + + + + + + 230 + 80 + 211 + 31 + + + + Browse for an existing O3DE project. + + + Remove Template + + + + + + + 10 + 230 + 241 + 121 + + + + Manage Project + + + + + 10 + 80 + 221 + 31 + + + + Add or remove gems from your selected project. Gems add and remove additional assets and features to projects. + + + Manage Server Gem Targets + + + + + + 10 + 50 + 221 + 31 + + + + Add or remove gems from your selected project. Gems add and remove additional assets and features to projects. + + + Manage Tool Gem Targets + + + + + + 10 + 20 + 221 + 31 + + + + Add or remove gems from your selected project. Gems add and remove additional assets and features to projects. + + + Manage Runtime Gem Targets + + + + + + + + okCancel + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + okCancel + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/scripts/project_manager/ui/projects.ui b/scripts/project_manager/ui/projects.ui deleted file mode 100644 index 4445029dda..0000000000 --- a/scripts/project_manager/ui/projects.ui +++ /dev/null @@ -1,167 +0,0 @@ - - - Dialog - - - - 0 - 0 - 464 - 136 - - - - - 1 - 0 - - - - O3DE - - - Select and manage your projects for O3DE - - - - - 290 - 100 - 171 - 31 - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - 10 - 30 - 451 - 31 - - - - Current project to launch or manage gems for. - - - - - - 10 - 70 - 91 - 23 - - - - Create a new O3DE project from a pre configured template. - - - Create New - - - - - - 10 - 10 - 47 - 13 - - - - Project - - - - - - 110 - 70 - 91 - 23 - - - - Browse for an existing O3DE project. - - - Browse - - - - - - 350 - 70 - 91 - 23 - - - - Add or remove gems from your selected project. Gems add and remove additional assets and features to projects. - - - Manage Gems - - - - - - 5 - 110 - 275 - 16 - - - - - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - okCancel - accepted() - Dialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - okCancel - rejected() - Dialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - -