diff --git a/AutomatedTesting/project.json b/AutomatedTesting/project.json index 5ee4ee68f8..9222777ef3 100644 --- a/AutomatedTesting/project.json +++ b/AutomatedTesting/project.json @@ -10,5 +10,7 @@ "version_name": "1.0.0.0", "orientation": "landscape" }, - "engine": "o3de" -} \ No newline at end of file + "engine": "o3de", + "display_name": "AutomatedTesting", + "icon_path": "preview.png" +} diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qss b/Code/Tools/ProjectManager/Resources/ProjectManager.qss index 1fc808b72c..3d100ec170 100644 --- a/Code/Tools/ProjectManager/Resources/ProjectManager.qss +++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qss @@ -250,10 +250,6 @@ QTabBar::tab:focus { margin-top:30px; } -#projectPreviewLabel { - margin: 10px 0 5px 0; -} - #projectTemplate { margin: 25px 0 0 50px; } @@ -329,6 +325,16 @@ QTabBar::tab:focus { border:none; } +#projectSettingsSectionTitle +{ + font-size:18px; +} + +#projectSmallInfoLabel +{ + font-size:10px; +} + #projectSettingsTab::tab-bar { left: 60px; } diff --git a/Code/Tools/ProjectManager/Source/Application.cpp b/Code/Tools/ProjectManager/Source/Application.cpp index 08a812999f..a9ab59a991 100644 --- a/Code/Tools/ProjectManager/Source/Application.cpp +++ b/Code/Tools/ProjectManager/Source/Application.cpp @@ -70,7 +70,7 @@ namespace O3DE::ProjectManager } m_pythonBindings = AZStd::make_unique(GetEngineRoot()); - AZ_Assert(m_pythonBindings, "Failed to create PythonBindings"); + if (!m_pythonBindings->PythonStarted()) { if (!interactive) @@ -112,6 +112,8 @@ namespace O3DE::ProjectManager } } + m_settings = AZStd::make_unique(); + if (!RegisterEngine(interactive)) { return false; diff --git a/Code/Tools/ProjectManager/Source/Application.h b/Code/Tools/ProjectManager/Source/Application.h index 8f633b28c4..dc75c1a46e 100644 --- a/Code/Tools/ProjectManager/Source/Application.h +++ b/Code/Tools/ProjectManager/Source/Application.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #endif @@ -37,6 +38,7 @@ namespace O3DE::ProjectManager bool RegisterEngine(bool interactive); AZStd::unique_ptr m_pythonBindings; + AZStd::unique_ptr m_settings; QSharedPointer m_app; QSharedPointer m_mainWindow; diff --git a/Code/Tools/ProjectManager/Source/ExternalLinkDialog.cpp b/Code/Tools/ProjectManager/Source/ExternalLinkDialog.cpp index 122a5cbf5a..2d24913b5d 100644 --- a/Code/Tools/ProjectManager/Source/ExternalLinkDialog.cpp +++ b/Code/Tools/ProjectManager/Source/ExternalLinkDialog.cpp @@ -8,9 +8,7 @@ #include #include -#include - -#include +#include #include #include @@ -87,12 +85,6 @@ namespace O3DE::ProjectManager void ExternalLinkDialog::SetSkipDialogSetting(bool state) { - auto settingsRegistry = AZ::SettingsRegistry::Get(); - if (settingsRegistry) - { - QString settingsKey = GetExternalLinkWarningKey(); - settingsRegistry->Set(settingsKey.toStdString().c_str(), state); - SaveProjectManagerSettings(); - } + SettingsInterface::Get()->Set(ISettings::ExternalLinkWarningKey, state); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp index acea6ce378..d960145057 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemFilterWidget.cpp @@ -7,6 +7,9 @@ */ #include + +#include + #include #include #include diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h index 23e95cf487..264971428a 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h @@ -9,7 +9,6 @@ #pragma once #if !defined(Q_MOC_RUN) -#include #include #include #include diff --git a/Code/Tools/ProjectManager/Source/LinkWidget.cpp b/Code/Tools/ProjectManager/Source/LinkWidget.cpp index f25e38db67..bdafec24d8 100644 --- a/Code/Tools/ProjectManager/Source/LinkWidget.cpp +++ b/Code/Tools/ProjectManager/Source/LinkWidget.cpp @@ -8,9 +8,7 @@ #include #include -#include - -#include +#include #include #include @@ -33,13 +31,7 @@ namespace O3DE::ProjectManager { // Check if user request not to be shown external link warning dialog bool skipDialog = false; - auto settingsRegistry = AZ::SettingsRegistry::Get(); - - if (settingsRegistry) - { - QString settingsKey = GetExternalLinkWarningKey(); - settingsRegistry->Get(skipDialog, settingsKey.toStdString().c_str()); - } + SettingsInterface::Get()->Get(skipDialog, ISettings::ExternalLinkWarningKey); if (!skipDialog) { diff --git a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp index 4febb65782..f1f56993ca 100644 --- a/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp @@ -16,6 +16,8 @@ #include #include #include + +#include #include #include @@ -225,7 +227,7 @@ namespace O3DE::ProjectManager moreGemsLabel->setObjectName("moreGems"); templateDetailsLayout->addWidget(moreGemsLabel); - QLabel* browseCatalogLabel = new QLabel(tr("Browse the Gems Catalog to further customize your project."), this); + QLabel* browseCatalogLabel = new QLabel(tr("Browse the Gems Catalog to further customize your project."), this); browseCatalogLabel->setObjectName("browseCatalog"); browseCatalogLabel->setWordWrap(true); templateDetailsLayout->addWidget(browseCatalogLabel); diff --git a/Code/Tools/ProjectManager/Source/ProjectBuilderController.cpp b/Code/Tools/ProjectManager/Source/ProjectBuilderController.cpp index ecb125ae4e..e2c54f622d 100644 --- a/Code/Tools/ProjectManager/Source/ProjectBuilderController.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectBuilderController.cpp @@ -9,9 +9,7 @@ #include #include #include -#include - -#include +#include #include #include @@ -29,14 +27,8 @@ namespace O3DE::ProjectManager m_worker = new ProjectBuilderWorker(m_projectInfo); m_worker->moveToThread(&m_workerThread); - auto settingsRegistry = AZ::SettingsRegistry::Get(); - if (settingsRegistry) - { - // Remove key here in case Project Manager crashing while building that causes HandleResults to not be called - QString settingsKey = GetProjectBuiltSuccessfullyKey(m_projectInfo.m_projectName); - settingsRegistry->Remove(settingsKey.toStdString().c_str()); - SaveProjectManagerSettings(); - } + // Remove key here in case Project Manager crashed while building because that causes HandleResults to not be called + SettingsInterface::Get()->SetProjectBuiltSuccessfully(m_projectInfo, false); connect(&m_workerThread, &QThread::finished, m_worker, &ProjectBuilderWorker::deleteLater); connect(&m_workerThread, &QThread::started, m_worker, &ProjectBuilderWorker::BuildProject); @@ -91,8 +83,6 @@ namespace O3DE::ProjectManager void ProjectBuilderController::HandleResults(const QString& result) { - QString settingsKey = GetProjectBuiltSuccessfullyKey(m_projectInfo.m_projectName); - if (!result.isEmpty()) { if (result.contains(tr("log"))) @@ -122,12 +112,7 @@ namespace O3DE::ProjectManager emit NotifyBuildProject(m_projectInfo); } - auto settingsRegistry = AZ::SettingsRegistry::Get(); - if (settingsRegistry) - { - settingsRegistry->Remove(settingsKey.toStdString().c_str()); - SaveProjectManagerSettings(); - } + SettingsInterface::Get()->SetProjectBuiltSuccessfully(m_projectInfo, false); emit Done(false); return; @@ -136,12 +121,7 @@ namespace O3DE::ProjectManager { m_projectInfo.m_buildFailed = false; - auto settingsRegistry = AZ::SettingsRegistry::Get(); - if (settingsRegistry) - { - settingsRegistry->Set(settingsKey.toStdString().c_str(), true); - SaveProjectManagerSettings(); - } + SettingsInterface::Get()->SetProjectBuiltSuccessfully(m_projectInfo, true); } emit Done(true); diff --git a/Code/Tools/ProjectManager/Source/ProjectInfo.cpp b/Code/Tools/ProjectManager/Source/ProjectInfo.cpp index b4a1e84831..0d19fff74a 100644 --- a/Code/Tools/ProjectManager/Source/ProjectInfo.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectInfo.cpp @@ -9,14 +9,13 @@ #include #include -#include - namespace O3DE::ProjectManager { ProjectInfo::ProjectInfo( const QString& path, const QString& projectName, const QString& displayName, + const QString& id, const QString& origin, const QString& summary, const QString& iconPath, @@ -26,6 +25,7 @@ namespace O3DE::ProjectManager : m_path(path) , m_projectName(projectName) , m_displayName(displayName) + , m_id(id) , m_origin(origin) , m_summary(summary) , m_iconPath(iconPath) @@ -49,6 +49,10 @@ namespace O3DE::ProjectManager { return false; } + if (m_id != rhs.m_id) + { + return false; + } if (m_origin != rhs.m_origin) { return false; @@ -80,7 +84,7 @@ namespace O3DE::ProjectManager bool ProjectInfo::IsValid() const { - return !m_path.isEmpty() && !m_projectName.isEmpty(); + return !m_path.isEmpty() && !m_projectName.isEmpty() && !m_id.isEmpty(); } const QString& ProjectInfo::GetProjectDisplayName() const diff --git a/Code/Tools/ProjectManager/Source/ProjectInfo.h b/Code/Tools/ProjectManager/Source/ProjectInfo.h index 2ce1e8c491..4dcb21aa24 100644 --- a/Code/Tools/ProjectManager/Source/ProjectInfo.h +++ b/Code/Tools/ProjectManager/Source/ProjectInfo.h @@ -9,7 +9,6 @@ #pragma once #if !defined(Q_MOC_RUN) -#include #include #include #include @@ -26,6 +25,7 @@ namespace O3DE::ProjectManager const QString& path, const QString& projectName, const QString& displayName, + const QString& id, const QString& origin, const QString& summary, const QString& iconPath, @@ -45,6 +45,7 @@ namespace O3DE::ProjectManager // From project.json QString m_projectName; QString m_displayName; + QString m_id; QString m_origin; QString m_summary; QString m_iconPath; diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerSettings.cpp b/Code/Tools/ProjectManager/Source/ProjectManagerSettings.cpp deleted file mode 100644 index 66480aab3e..0000000000 --- a/Code/Tools/ProjectManager/Source/ProjectManagerSettings.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#include - -#include -#include -#include - -namespace O3DE::ProjectManager -{ - void SaveProjectManagerSettings() - { - auto settingsRegistry = AZ::SettingsRegistry::Get(); - AZ::SettingsRegistryMergeUtils::DumperSettings dumperSettings; - dumperSettings.m_prettifyOutput = true; - dumperSettings.m_jsonPointerPrefix = ProjectManagerKeyPrefix; - - AZStd::string stringBuffer; - AZ::IO::ByteContainerStream stringStream(&stringBuffer); - if (!AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream( - *settingsRegistry, ProjectManagerKeyPrefix, stringStream, dumperSettings)) - { - AZ_Warning("ProjectManager", false, "Could not save Project Manager settings to stream"); - return; - } - - AZ::IO::FixedMaxPath o3deUserPath = AZ::Utils::GetO3deManifestDirectory(); - o3deUserPath /= AZ::SettingsRegistryInterface::RegistryFolder; - o3deUserPath /= "ProjectManager.setreg"; - - bool saved = false; - constexpr auto configurationMode = - AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY; - - AZ::IO::SystemFile outputFile; - if (outputFile.Open(o3deUserPath.c_str(), configurationMode)) - { - saved = outputFile.Write(stringBuffer.data(), stringBuffer.size()) == stringBuffer.size(); - } - - AZ_Warning("ProjectManager", saved, "Unable to save Project Manager registry file to path: %s", o3deUserPath.c_str()); - } - - QString GetProjectBuiltSuccessfullyKey(const QString& projectName) - { - return QString("%1/Projects/%2/BuiltSuccessfully").arg(ProjectManagerKeyPrefix).arg(projectName); - } - - QString GetExternalLinkWarningKey() - { - return QString("%1/SkipExternalLinkWarning").arg(ProjectManagerKeyPrefix); - } -} diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerSettings.h b/Code/Tools/ProjectManager/Source/ProjectManagerSettings.h deleted file mode 100644 index 93cfbc5ceb..0000000000 --- a/Code/Tools/ProjectManager/Source/ProjectManagerSettings.h +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#pragma once - -#if !defined(Q_MOC_RUN) -#include -#endif - -namespace O3DE::ProjectManager -{ - static constexpr char ProjectManagerKeyPrefix[] = "/O3DE/ProjectManager"; - - void SaveProjectManagerSettings(); - QString GetProjectBuiltSuccessfullyKey(const QString& projectName); - QString GetExternalLinkWarningKey(); -} diff --git a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp index d4ac43d655..45023d7f80 100644 --- a/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp @@ -149,7 +149,7 @@ namespace O3DE::ProjectManager void ProjectSettingsScreen::OnProjectPathUpdated() { - Validate(); + ValidateProjectName() && ValidateProjectPath(); } bool ProjectSettingsScreen::Validate() diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp index 948736eed1..7e5675918d 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include @@ -291,13 +291,9 @@ namespace O3DE::ProjectManager // Check whether project manager has successfully built the project if (currentButton) { - auto settingsRegistry = AZ::SettingsRegistry::Get(); bool projectBuiltSuccessfully = false; - if (settingsRegistry) - { - QString settingsKey = GetProjectBuiltSuccessfullyKey(project.m_projectName); - settingsRegistry->Get(projectBuiltSuccessfully, settingsKey.toStdString().c_str()); - } + SettingsInterface::Get()->GetProjectBuiltSuccessfully(projectBuiltSuccessfully, project); + if (!projectBuiltSuccessfully) { currentButton->ShowBuildRequired(); diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 00ece7396d..35bdfe0031 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -729,9 +729,9 @@ namespace O3DE::ProjectManager auto createProjectResult = m_engineTemplate.attr("create_project")( projectPath, - QString_To_Py_String(projectInfo.m_projectName), - QString_To_Py_Path(projectTemplatePath) - ); + QString_To_Py_String(projectInfo.m_projectName), // project_path + QString_To_Py_Path(projectTemplatePath) // template_path + ); if (createProjectResult.cast() == 0) { createdProjectInfo = ProjectInfoFromPath(projectPath); @@ -864,6 +864,7 @@ namespace O3DE::ProjectManager { projectInfo.m_projectName = Py_To_String(projectData["project_name"]); projectInfo.m_displayName = Py_To_String_Optional(projectData, "display_name", projectInfo.m_projectName); + projectInfo.m_id = Py_To_String_Optional(projectData, "project_id", projectInfo.m_id); projectInfo.m_origin = Py_To_String_Optional(projectData, "origin", projectInfo.m_origin); projectInfo.m_summary = Py_To_String_Optional(projectData, "summary", projectInfo.m_summary); projectInfo.m_iconPath = Py_To_String_Optional(projectData, "icon", ProjectPreviewImagePath); @@ -968,6 +969,7 @@ namespace O3DE::ProjectManager QString_To_Py_Path(projectInfo.m_path), pybind11::none(), // proj_name not used QString_To_Py_String(projectInfo.m_projectName), + QString_To_Py_String(projectInfo.m_id), QString_To_Py_String(projectInfo.m_origin), QString_To_Py_String(projectInfo.m_displayName), QString_To_Py_String(projectInfo.m_summary), diff --git a/Code/Tools/ProjectManager/Source/Settings.cpp b/Code/Tools/ProjectManager/Source/Settings.cpp new file mode 100644 index 0000000000..0a8b600dd9 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/Settings.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +#include +#include +#include + +namespace O3DE::ProjectManager +{ + Settings::Settings(bool saveToDisk) + : m_saveToDisk(saveToDisk) + { + m_settingsRegistry = AZ::SettingsRegistry::Get(); + + AZ_Assert(m_settingsRegistry, "Failed to create Settings"); + } + + void Settings::Save() + { + AZ::SettingsRegistryMergeUtils::DumperSettings dumperSettings; + dumperSettings.m_prettifyOutput = true; + dumperSettings.m_jsonPointerPrefix = ProjectManagerKeyPrefix; + + AZStd::string stringBuffer; + AZ::IO::ByteContainerStream stringStream(&stringBuffer); + if (!AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream( + *m_settingsRegistry, ProjectManagerKeyPrefix, stringStream, dumperSettings)) + { + AZ_Warning("ProjectManager", false, "Could not save Project Manager settings to stream"); + return; + } + + AZ::IO::FixedMaxPath o3deUserPath = AZ::Utils::GetO3deManifestDirectory(); + o3deUserPath /= AZ::SettingsRegistryInterface::RegistryFolder; + o3deUserPath /= "ProjectManager.setreg"; + + bool saved = false; + constexpr auto configurationMode = + AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY; + + AZ::IO::SystemFile outputFile; + if (outputFile.Open(o3deUserPath.c_str(), configurationMode)) + { + saved = outputFile.Write(stringBuffer.data(), stringBuffer.size()) == stringBuffer.size(); + } + + AZ_Warning("ProjectManager", saved, "Unable to save Project Manager registry file to path: %s", o3deUserPath.c_str()); + } + + void Settings::OnSettingsChanged() + { + if (m_saveToDisk) + { + Save(); + } + } + + bool Settings::Get(QString& result, const QString& settingsKey) + { + bool success = false; + + AZStd::string settingsValue; + success = m_settingsRegistry->Get(settingsValue, settingsKey.toStdString().c_str()); + + result = settingsValue.c_str(); + return success; + } + + bool Settings::Get(bool& result, const QString& settingsKey) + { + return m_settingsRegistry->Get(result, settingsKey.toStdString().c_str()); + } + + bool Settings::Set(const QString& settingsKey, const QString& settingsValue) + { + bool success = false; + + success = m_settingsRegistry->Set(settingsKey.toStdString().c_str(), settingsValue.toStdString().c_str()); + OnSettingsChanged(); + + return success; + } + + bool Settings::Set(const QString& settingsKey, bool settingsValue) + { + bool success = false; + + success = m_settingsRegistry->Set(settingsKey.toStdString().c_str(), settingsValue); + OnSettingsChanged(); + + return success; + } + + bool Settings::Remove(const QString& settingsKey) + { + bool success = false; + + success = m_settingsRegistry->Remove(settingsKey.toStdString().c_str()); + OnSettingsChanged(); + + return success; + } + + bool Settings::Copy(const QString& settingsKeyOrig, const QString& settingsKeyDest, bool removeOrig) + { + bool success = false; + AZStd::string settingsValue; + + success = m_settingsRegistry->Get(settingsValue, settingsKeyOrig.toStdString().c_str()); + + if (success) + { + success = m_settingsRegistry->Set(settingsKeyDest.toStdString().c_str(), settingsValue); + if (success) + { + if (removeOrig) + { + success = m_settingsRegistry->Remove(settingsKeyOrig.toStdString().c_str()); + } + OnSettingsChanged(); + } + } + + return success; + } + + QString Settings::GetProjectKey(const ProjectInfo& projectInfo) + { + return QString("%1/Projects/%2/%3").arg(ProjectManagerKeyPrefix, projectInfo.m_id, projectInfo.m_projectName); + } + + bool Settings::GetBuiltSuccessfullyPaths(AZStd::set& result) + { + return m_settingsRegistry->GetObject>(result, ProjectsBuiltSuccessfullyKey); + } + + bool Settings::GetProjectBuiltSuccessfully(bool& result, const ProjectInfo& projectInfo) + { + AZStd::set builtPathsResult; + bool success = GetBuiltSuccessfullyPaths(builtPathsResult); + + // Check if buildPath is listed as successfully built + AZStd::string projectPath = projectInfo.m_path.toStdString().c_str(); + if (builtPathsResult.contains(projectPath)) + { + result = true; + } + // No project built statuses known + else + { + result = false; + } + + return success; + } + + bool Settings::SetProjectBuiltSuccessfully(const ProjectInfo& projectInfo, bool successfullyBuilt) + { + AZStd::set builtPathsResult; + bool success = GetBuiltSuccessfullyPaths(builtPathsResult); + + AZStd::string projectPath = projectInfo.m_path.toStdString().c_str(); + if (successfullyBuilt) + { + //Add successfully built path to set + builtPathsResult.insert(projectPath); + } + else + { + // Remove unsuccessfully built path from set + builtPathsResult.erase(projectPath); + } + + success = m_settingsRegistry->SetObject>(ProjectsBuiltSuccessfullyKey, builtPathsResult); + OnSettingsChanged(); + + return success; + } + +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/Settings.h b/Code/Tools/ProjectManager/Source/Settings.h new file mode 100644 index 0000000000..416d17b53b --- /dev/null +++ b/Code/Tools/ProjectManager/Source/Settings.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +#include +#include + +namespace AZ +{ + class SettingsRegistryInterface; +} + +namespace O3DE::ProjectManager +{ + class Settings + : public SettingsInterface::Registrar + { + public: + Settings(bool saveToDisk = true); + + bool Get(QString& result, const QString& settingsKey) override; + bool Get(bool& result, const QString& settingsKey) override; + bool Set(const QString& settingsKey, const QString& settingsValue) override; + bool Set(const QString& settingsKey, bool settingsValue) override; + bool Remove(const QString& settingsKey) override; + bool Copy(const QString& settingsKeyOrig, const QString& settingsKeyDest, bool removeOrig = false) override; + + QString GetProjectKey(const ProjectInfo& projectInfo) override; + + bool GetProjectBuiltSuccessfully(bool& result, const ProjectInfo& projectInfo) override; + bool SetProjectBuiltSuccessfully(const ProjectInfo& projectInfo, bool successfullyBuilt) override; + + private: + void Save(); + void OnSettingsChanged(); + + bool GetBuiltSuccessfullyPaths(AZStd::set& result); + + bool m_saveToDisk; + AZ::SettingsRegistryInterface* m_settingsRegistry = nullptr; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/SettingsInterface.h b/Code/Tools/ProjectManager/Source/SettingsInterface.h new file mode 100644 index 0000000000..da1363dee5 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/SettingsInterface.h @@ -0,0 +1,101 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#include + +#include +#include + +namespace O3DE::ProjectManager +{ + //! Interface used to interact with the settings functions + class ISettings + { + public: + AZ_RTTI(O3DE::ProjectManager::ISettings, "{95D87D95-0E04-462F-8B0B-ED15C0A9F090}"); + AZ_DISABLE_COPY_MOVE(ISettings); + + static constexpr char ProjectManagerKeyPrefix[] = "/O3DE/ProjectManager"; + static constexpr char ExternalLinkWarningKey[] = "/O3DE/ProjectManager/SkipExternalLinkWarning"; + static constexpr char ProjectsBuiltSuccessfullyKey[] = "/O3DE/ProjectManager/SuccessfulBuildPaths"; + + ISettings() = default; + virtual ~ISettings() = default; + + /** + * Get the value for a string settings key + * @param result Store string result in this variable + * @param settingsKey The key to get the value in + * @return true if all calls to settings registry were successful + */ + virtual bool Get(QString& result, const QString& settingsKey) = 0; + /** + * Get the value for a bool settings key + * @param result Store bool result in this variable + * @param settingsKey The key to get the value in + * @return true if all calls to settings registry were successful + */ + virtual bool Get(bool& result, const QString& settingsKey) = 0; + + /** + * Set the value for a string settings key + * @param settingsKey The key to set the value in + * @param settingsValue String value to set key to + * @return true if all calls to settings registry were successful + */ + virtual bool Set(const QString& settingsKey, const QString& settingsValue) = 0; + /** + * Set the value for a bool settings key + * @param settingsKey The key to set the value in + * @param settingsValue Bool value to set key to + * @return true if all calls to settings registry were successful + */ + virtual bool Set(const QString& settingsKey, bool settingsValue) = 0; + + /** + * Remove settings key + * @param settingsKey The key to remove + * @return true if all calls to settings registry were successful + */ + virtual bool Remove(const QString& settingsKey) = 0; + + /** + * Copy the string settings value from one key to another + * @param settingsKeyOrig The original key to copy from + * @param settingsKeyDest The destination key to copy to + * @param removeOrig(Optional) Delete the original key if true + * @return true if all calls to settings registry were successful + */ + virtual bool Copy(const QString& settingsKeyOrig, const QString& settingsKeyDest, bool removeOrig = false) = 0; + + /** + * Generate prefix for project settings key + * @param projectInfo Project for settings key + * @return QString Prefix for project specific settings key + */ + virtual QString GetProjectKey(const ProjectInfo& projectInfo) = 0; + + /** + * Get the build status for a project + * @param result Store bool build status in this variable + * @param projectInfo Project to check built status for + * @return true if all calls to settings registry were successful + */ + virtual bool GetProjectBuiltSuccessfully(bool& result, const ProjectInfo& projectInfo) = 0; + /** + * Set the build status for a project + * @param projectInfo Project to set built status for + * @param successfullyBuilt Bool value to set build status to + * @return true if all calls to settings registry were successful + */ + virtual bool SetProjectBuiltSuccessfully(const ProjectInfo& projectInfo, bool successfullyBuilt) = 0; + }; + + using SettingsInterface = AZ::Interface; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp index 36d78ed2ac..0e5f6f3ca5 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectCtrl.cpp @@ -16,8 +16,7 @@ #include #include #include -#include -#include +#include #include #include @@ -306,17 +305,11 @@ namespace O3DE::ProjectManager if (newProjectSettings.m_projectName != m_projectInfo.m_projectName) { - // update reg key - QString oldSettingsKey = GetProjectBuiltSuccessfullyKey(m_projectInfo.m_projectName); - QString newSettingsKey = GetProjectBuiltSuccessfullyKey(newProjectSettings.m_projectName); - - auto settingsRegistry = AZ::SettingsRegistry::Get(); - bool projectBuiltSuccessfully = false; - if (settingsRegistry && settingsRegistry->Get(projectBuiltSuccessfully, oldSettingsKey.toStdString().c_str())) - { - settingsRegistry->Set(newSettingsKey.toStdString().c_str(), projectBuiltSuccessfully); - SaveProjectManagerSettings(); - } + // Remove project build successfully paths for both old and new project names + // because a full rebuild is required when moving projects + auto settings = SettingsInterface::Get(); + settings->SetProjectBuiltSuccessfully(m_projectInfo, false); + settings->SetProjectBuiltSuccessfully(newProjectSettings, false); } if (!newProjectSettings.m_newPreviewImagePath.isEmpty()) diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp index a430102c27..30735dcc97 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp @@ -16,6 +16,7 @@ #include #include #include +#include namespace O3DE::ProjectManager { @@ -31,12 +32,10 @@ namespace O3DE::ProjectManager m_verticalLayout->addWidget(m_projectPreview); QVBoxLayout* previewExtrasLayout = new QVBoxLayout(this); - previewExtrasLayout->setAlignment(Qt::AlignLeft); - previewExtrasLayout->setContentsMargins(50, 0, 0, 0); + previewExtrasLayout->setAlignment(Qt::AlignTop); + previewExtrasLayout->setContentsMargins(30, 45, 30, 0); - QLabel* projectPreviewLabel = new QLabel(tr("Select an image (PNG). Minimum %1 x %2 pixels.") - .arg(QString::number(ProjectPreviewImageWidth), QString::number(ProjectPreviewImageHeight))); - projectPreviewLabel->setObjectName("projectPreviewLabel"); + QLabel* projectPreviewLabel = new QLabel(tr("Project Preview")); previewExtrasLayout->addWidget(projectPreviewLabel); m_projectPreviewImage = new QLabel(this); @@ -44,7 +43,53 @@ namespace O3DE::ProjectManager m_projectPreviewImage->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); previewExtrasLayout->addWidget(m_projectPreviewImage); - m_verticalLayout->addLayout(previewExtrasLayout); + QLabel* projectPreviewInfoLabel = new QLabel(tr("Select an image (PNG). Minimum %1 x %2 pixels.") + .arg(QString::number(ProjectPreviewImageWidth), QString::number(ProjectPreviewImageHeight))); + projectPreviewInfoLabel->setObjectName("projectSmallInfoLabel"); + projectPreviewInfoLabel->setWordWrap(true); + previewExtrasLayout->addWidget(projectPreviewInfoLabel); + + m_horizontalLayout->addLayout(previewExtrasLayout); + + m_verticalLayout->addSpacing(10); + + // Collapse button + QHBoxLayout* advancedCollapseLayout = new QHBoxLayout(); + advancedCollapseLayout->setContentsMargins(50, 0, 0, 0); + + m_advancedSettingsCollapseButton = new QPushButton(); + m_advancedSettingsCollapseButton->setCheckable(true); + m_advancedSettingsCollapseButton->setChecked(true); + m_advancedSettingsCollapseButton->setFlat(true); + m_advancedSettingsCollapseButton->setFocusPolicy(Qt::NoFocus); + m_advancedSettingsCollapseButton->setFixedWidth(s_collapseButtonSize); + connect(m_advancedSettingsCollapseButton, &QPushButton::clicked, this, &UpdateProjectSettingsScreen::UpdateAdvancedSettingsCollapseState); + advancedCollapseLayout->addWidget(m_advancedSettingsCollapseButton); + + // Category title + QLabel* advancedLabel = new QLabel(tr("Advanced Settings")); + advancedLabel->setObjectName("projectSettingsSectionTitle"); + advancedCollapseLayout->addWidget(advancedLabel); + m_verticalLayout->addLayout(advancedCollapseLayout); + + m_verticalLayout->addSpacing(5); + + // Everything in the advanced settings widget will be collapsed/uncollapsed + { + m_advancedSettingWidget = new QWidget(); + m_verticalLayout->addWidget(m_advancedSettingWidget); + + QVBoxLayout* advancedSettingsLayout = new QVBoxLayout(); + advancedSettingsLayout->setMargin(0); + advancedSettingsLayout->setAlignment(Qt::AlignTop); + m_advancedSettingWidget->setLayout(advancedSettingsLayout); + + m_projectId = new FormLineEditWidget(tr("Project ID"), "", this); + connect(m_projectId->lineEdit(), &QLineEdit::textChanged, this, &UpdateProjectSettingsScreen::OnProjectIdUpdated); + advancedSettingsLayout->addWidget(m_projectId); + } + + UpdateAdvancedSettingsCollapseState(); } ProjectManagerScreen UpdateProjectSettingsScreen::GetScreenEnum() @@ -56,6 +101,7 @@ namespace O3DE::ProjectManager { m_projectInfo.m_displayName = m_projectName->lineEdit()->text(); m_projectInfo.m_path = m_projectPath->lineEdit()->text(); + m_projectInfo.m_id = m_projectId->lineEdit()->text(); if (m_userChangedPreview) { @@ -70,8 +116,9 @@ namespace O3DE::ProjectManager m_projectInfo = projectInfo; m_projectName->lineEdit()->setText(projectInfo.GetProjectDisplayName()); - m_projectPath->lineEdit()->setText(projectInfo.m_path); + m_projectId->lineEdit()->setText(projectInfo.m_id); + UpdateProjectPreviewPath(); } @@ -88,7 +135,7 @@ namespace O3DE::ProjectManager bool UpdateProjectSettingsScreen::Validate() { - return ProjectSettingsScreen::Validate() && ValidateProjectPreview(); + return ProjectSettingsScreen::Validate() && ValidateProjectPreview() && ValidateProjectId(); } void UpdateProjectSettingsScreen::ResetProjectPreviewPath() @@ -106,6 +153,11 @@ namespace O3DE::ProjectManager QPixmap(m_projectPreview->lineEdit()->text()).scaled(m_projectPreviewImage->size(), Qt::KeepAspectRatioByExpanding)); } + void UpdateProjectSettingsScreen::OnProjectIdUpdated() + { + ValidateProjectId(); + } + bool UpdateProjectSettingsScreen::ValidateProjectPath() { bool projectPathIsValid = true; @@ -155,4 +207,31 @@ namespace O3DE::ProjectManager return projectPreviewIsValid; } + bool UpdateProjectSettingsScreen::ValidateProjectId() + { + bool projectIdIsValid = true; + if (m_projectId->lineEdit()->text().isEmpty()) + { + projectIdIsValid = false; + m_projectId->setErrorLabelText(tr("Project ID cannot be empty.")); + } + + m_projectId->setErrorLabelVisible(!projectIdIsValid); + return projectIdIsValid; + } + + void UpdateProjectSettingsScreen::UpdateAdvancedSettingsCollapseState() + { + if (m_advancedSettingsCollapseButton->isChecked()) + { + m_advancedSettingsCollapseButton->setIcon(QIcon(":/ArrowDownLine.svg")); + m_advancedSettingWidget->hide(); + } + else + { + m_advancedSettingsCollapseButton->setIcon(QIcon(":/ArrowUpLine.svg")); + m_advancedSettingWidget->show(); + } + } + } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h index 22d7794de4..28ea032985 100644 --- a/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h +++ b/Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h @@ -12,6 +12,7 @@ #endif QT_FORWARD_DECLARE_CLASS(QLabel) +QT_FORWARD_DECLARE_CLASS(QPushButton) namespace O3DE::ProjectManager { @@ -33,16 +34,27 @@ namespace O3DE::ProjectManager public slots: void UpdateProjectPreviewPath(); void PreviewPathChanged(); + void OnProjectIdUpdated(); protected: bool ValidateProjectPath() override; virtual bool ValidateProjectPreview(); + bool ValidateProjectId(); + + inline constexpr static int s_collapseButtonSize = 24; FormBrowseEditWidget* m_projectPreview; QLabel* m_projectPreviewImage; + FormLineEditWidget* m_projectId; + + QPushButton* m_advancedSettingsCollapseButton = nullptr; + QWidget* m_advancedSettingWidget = nullptr; ProjectInfo m_projectInfo; bool m_userChangedPreview; //! Did the user change the project preview path + + protected slots: + void UpdateAdvancedSettingsCollapseState(); }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index 468e8dcbc5..915b1a072f 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -58,8 +58,6 @@ set(FILES Source/CreateProjectCtrl.cpp Source/UpdateProjectCtrl.h Source/UpdateProjectCtrl.cpp - Source/ProjectManagerSettings.h - Source/ProjectManagerSettings.cpp Source/ProjectsScreen.h Source/ProjectsScreen.cpp Source/ProjectSettingsScreen.h @@ -72,6 +70,9 @@ set(FILES Source/ProjectButtonWidget.cpp Source/ScreenHeaderWidget.h Source/ScreenHeaderWidget.cpp + Source/Settings.h + Source/Settings.cpp + Source/SettingsInterface.h Source/LinkWidget.h Source/LinkWidget.cpp Source/TagWidget.h diff --git a/Code/Tools/ProjectManager/project_manager_tests_files.cmake b/Code/Tools/ProjectManager/project_manager_tests_files.cmake index 2bfe343038..012a27d13a 100644 --- a/Code/Tools/ProjectManager/project_manager_tests_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_tests_files.cmake @@ -10,8 +10,9 @@ set(FILES Resources/ProjectManager.qrc Resources/ProjectManager.qss tests/ApplicationTests.cpp - tests/PythonBindingsTests.cpp tests/GemCatalogTests.cpp + tests/SettingsTests.cpp + tests/PythonBindingsTests.cpp tests/main.cpp tests/UtilsTests.cpp ) diff --git a/Code/Tools/ProjectManager/tests/SettingsTests.cpp b/Code/Tools/ProjectManager/tests/SettingsTests.cpp new file mode 100644 index 0000000000..d994a59737 --- /dev/null +++ b/Code/Tools/ProjectManager/tests/SettingsTests.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include + +#include + +namespace O3DE::ProjectManager +{ + class SettingsTests + : public ::UnitTest::ScopedAllocatorSetupFixture + { + public: + ~SettingsTests() override = default; + void SetUp() override + { + UnitTest::ScopedAllocatorSetupFixture::SetUp(); + + m_registry = AZStd::make_unique(); + // Store off the old global settings registry to restore after each test + m_oldSettingsRegistry = AZ::SettingsRegistry::Get(); + if (m_oldSettingsRegistry != nullptr) + { + AZ::SettingsRegistry::Unregister(m_oldSettingsRegistry); + } + AZ::SettingsRegistry::Register(m_registry.get()); + + m_serializeContext = AZStd::make_unique(); + m_registrationContext = AZStd::make_unique(); + + m_registry->SetContext(m_serializeContext.get()); + m_registry->SetContext(m_registrationContext.get()); + + AZ::JsonSystemComponent::Reflect(m_registrationContext.get()); + + m_serializeContext->RegisterGenericType>(); + + m_settings = AZStd::make_unique(/*saveToDisk*/ false); + + m_projectInfo.m_path = "Z:/ProjectTestPath"; + } + + void TearDown() override + { + m_settings.reset(); + + m_registrationContext->EnableRemoveReflection(); + AZ::JsonSystemComponent::Reflect(m_registrationContext.get()); + m_registrationContext->DisableRemoveReflection(); + + m_registrationContext.reset(); + m_serializeContext.reset(); + + // Restore the old global settings registry + AZ::SettingsRegistry::Unregister(m_registry.get()); + if (m_oldSettingsRegistry != nullptr) + { + AZ::SettingsRegistry::Register(m_oldSettingsRegistry); + m_oldSettingsRegistry = nullptr; + } + m_registry.reset(); + + UnitTest::ScopedAllocatorSetupFixture::TearDown(); + } + + protected: + AZStd::unique_ptr m_settings; + const QString m_settingsPath = "/Testing/TestKey"; + const QString m_newSettingsPath = "/Testing/NewTestKey"; + ProjectInfo m_projectInfo; + + private: + AZ::SettingsRegistryInterface* m_oldSettingsRegistry = nullptr; + AZStd::unique_ptr m_registry; + AZStd::unique_ptr m_serializeContext; + AZStd::unique_ptr m_registrationContext; + }; + + TEST_F(SettingsTests, Settings_GetUnsetPathBool_ReturnsFalse) + { + bool settingsResult = false; + EXPECT_FALSE(m_settings->Get(settingsResult, m_settingsPath)); + EXPECT_FALSE(settingsResult); + } + + TEST_F(SettingsTests, Settings_SetAndGetValueBool_Success) + { + bool settingsResult = false; + EXPECT_FALSE(m_settings->Get(settingsResult, m_settingsPath)); + + EXPECT_TRUE(m_settings->Set(m_settingsPath, true)); + + settingsResult = false; + EXPECT_TRUE(m_settings->Get(settingsResult, m_settingsPath)); + EXPECT_TRUE(settingsResult); + } + + TEST_F(SettingsTests, Settings_GetUnsetPathString_ReturnsFalse) + { + QString settingsResult; + EXPECT_FALSE(m_settings->Get(settingsResult, m_settingsPath)); + EXPECT_TRUE(settingsResult.isEmpty()); + } + + TEST_F(SettingsTests, Settings_SetAndGetValueString_Success) + { + QString settingsResult; + EXPECT_FALSE(m_settings->Get(settingsResult, m_settingsPath)); + + QString settingsValue = "TestValue"; + + EXPECT_TRUE(m_settings->Set(m_settingsPath, settingsValue)); + + EXPECT_TRUE(m_settings->Get(settingsResult, m_settingsPath)); + EXPECT_TRUE(settingsResult == settingsValue); + } + + TEST_F(SettingsTests, Settings_CopyStringRemoveOriginal_SuccessAndRemovesOriginal) + { + QString settingsResult; + EXPECT_FALSE(m_settings->Get(settingsResult, m_newSettingsPath)); + + QString settingsValue = "TestValue"; + + EXPECT_TRUE(m_settings->Set(m_settingsPath, settingsValue)); + + EXPECT_TRUE(m_settings->Copy(m_settingsPath, m_newSettingsPath, /*removeOrig*/ true)); + + // Check that old path value is removed + EXPECT_FALSE(m_settings->Get(settingsResult, m_settingsPath)); + + EXPECT_TRUE(m_settings->Get(settingsResult, m_newSettingsPath)); + EXPECT_TRUE(settingsResult == settingsValue); + } + + TEST_F(SettingsTests, Settings_RemoveProjectManagerKey_RemovesKey) + { + QString settingsResult; + EXPECT_FALSE(m_settings->Get(settingsResult, m_settingsPath)); + + QString settingsValue = "TestValue"; + + EXPECT_TRUE(m_settings->Set(m_settingsPath, settingsValue)); + EXPECT_TRUE(m_settings->Get(settingsResult, m_settingsPath)); + + EXPECT_TRUE(m_settings->Remove(m_settingsPath)); + EXPECT_FALSE(m_settings->Get(settingsResult, m_settingsPath)); + } + + TEST_F(SettingsTests, Settings_GetUnsetBuildPath_ReturnsFalse) + { + bool buildResult = true; + EXPECT_FALSE(m_settings->GetProjectBuiltSuccessfully(buildResult, m_projectInfo)); + EXPECT_FALSE(buildResult); + } + + TEST_F(SettingsTests, Settings_SetProjectBuiltSuccessfully_ReturnsTrue) + { + EXPECT_TRUE(m_settings->SetProjectBuiltSuccessfully(m_projectInfo, true)); + + bool buildResult = false; + EXPECT_TRUE(m_settings->GetProjectBuiltSuccessfully(buildResult, m_projectInfo)); + EXPECT_TRUE(buildResult); + } + + TEST_F(SettingsTests, Settings_SetProjectBuiltUnsuccessfully_ReturnsFalse) + { + EXPECT_TRUE(m_settings->SetProjectBuiltSuccessfully(m_projectInfo, false)); + + bool buildResult = false; + EXPECT_TRUE(m_settings->GetProjectBuiltSuccessfully(buildResult, m_projectInfo)); + EXPECT_FALSE(buildResult); + } +} diff --git a/Templates/DefaultProject/Template/project.json b/Templates/DefaultProject/Template/project.json index b52b0baf27..f8d4643ce9 100644 --- a/Templates/DefaultProject/Template/project.json +++ b/Templates/DefaultProject/Template/project.json @@ -1,5 +1,6 @@ { "project_name": "${Name}", + "project_id": "${ProjectId}", "origin": "The primary repo for ${Name} goes here: i.e. http://www.mydomain.com", "license": "What license ${Name} uses goes here: i.e. https://opensource.org/licenses/MIT", "display_name": "${Name}", diff --git a/Templates/MinimalProject/Template/project.json b/Templates/MinimalProject/Template/project.json index b52b0baf27..f8d4643ce9 100644 --- a/Templates/MinimalProject/Template/project.json +++ b/Templates/MinimalProject/Template/project.json @@ -1,5 +1,6 @@ { "project_name": "${Name}", + "project_id": "${ProjectId}", "origin": "The primary repo for ${Name} goes here: i.e. http://www.mydomain.com", "license": "What license ${Name} uses goes here: i.e. https://opensource.org/licenses/MIT", "display_name": "${Name}", diff --git a/engine.json b/engine.json index 0aa3238f49..737ac24fea 100644 --- a/engine.json +++ b/engine.json @@ -86,7 +86,6 @@ "Gems/WhiteBox" ], "projects": [ - "AtomTest", "AutomatedTesting" ], "templates": [ diff --git a/scripts/o3de/o3de/engine_template.py b/scripts/o3de/o3de/engine_template.py index 369086c9c3..6b2e098cf0 100755 --- a/scripts/o3de/o3de/engine_template.py +++ b/scripts/o3de/o3de/engine_template.py @@ -1114,7 +1114,7 @@ def create_from_template(destination_path: pathlib.Path, try: template_json_data = json.load(s) except KeyError as e: - logger.error(f'Could read template json {template_json}: {str(e)}.') + logger.error(f'Could not read template json {template_json}: {str(e)}.') return 1 # read template name from the json @@ -1339,7 +1339,8 @@ def create_project(project_path: pathlib.Path, no_register: bool = False, system_component_class_id: str = None, editor_system_component_class_id: str = None, - module_id: str = None) -> int: + module_id: str = None, + project_id: str = None) -> int: """ Template instantiation specialization that makes all default assumptions for a Project template instantiation, reducing the effort needed in instancing a project @@ -1367,6 +1368,7 @@ def create_project(project_path: pathlib.Path, :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 + :param project_id: optionally specify a str for the project id, default is random uuid :return: 0 for success or non 0 failure code """ if template_name and template_path: @@ -1406,7 +1408,7 @@ def create_project(project_path: pathlib.Path, try: template_json_data = json.load(s) except json.JSONDecodeError as e: - logger.error(f'Could read template json {template_json}: {str(e)}.') + logger.error(f'Could not read template json {template_json}: {str(e)}.') return 1 # read template name from the json @@ -1578,6 +1580,12 @@ def create_project(project_path: pathlib.Path, replacements.append(("${NameLower}", project_name.lower())) replacements.append(("${SanitizedCppName}", sanitized_cpp_name)) + # was a project id specified + if project_id: + replacements.append(("${ProjectId}", project_id)) + else: + replacements.append(("${ProjectId}", '{' + str(uuid.uuid4()) + '}')) + # module id is a uuid with { and - if module_id: replacements.append(("${ModuleClassId}", module_id)) @@ -1785,7 +1793,7 @@ def create_gem(gem_path: pathlib.Path, try: template_json_data = json.load(s) except json.JSONDecodeError as e: - logger.error(f'Could read template json {template_json}: {str(e)}.') + logger.error(f'Could not read template json {template_json}: {str(e)}.') return 1 # read template name from the json @@ -2123,7 +2131,8 @@ def _run_create_project(args: argparse) -> int: args.no_register, args.system_component_class_id, args.editor_system_component_class_id, - args.module_id) + args.module_id, + args.project_id) def _run_create_gem(args: argparse) -> int: @@ -2417,6 +2426,9 @@ def add_args(subparsers) -> None: create_project_subparser.add_argument('--module-id', type=uuid.UUID, required=False, help='The uuid you want to associate with the module, default is a random' ' uuid Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}') + create_project_subparser.add_argument('--project-id', type=str, required=False, + help='The str id you want to associate with the project, default is a random uuid' + ' Ex. {b60c92eb-3139-454b-a917-a9d3c5819594}') create_project_subparser.add_argument('-f', '--force', action='store_true', default=False, help='Copies over instantiated template directory even if it exist.') create_project_subparser.add_argument('--no-register', action='store_true', default=False, diff --git a/scripts/o3de/o3de/project_properties.py b/scripts/o3de/o3de/project_properties.py index bbddd5adab..9ee55773b5 100644 --- a/scripts/o3de/o3de/project_properties.py +++ b/scripts/o3de/o3de/project_properties.py @@ -31,6 +31,7 @@ def get_project_props(name: str = None, path: pathlib.Path = None) -> dict: def edit_project_props(proj_path: pathlib.Path = None, proj_name: str = None, new_name: str = None, + new_id: str = None, new_origin: str = None, new_display: str = None, new_summary: str = None, @@ -42,14 +43,15 @@ def edit_project_props(proj_path: pathlib.Path = None, if not proj_json: return 1 - - if new_origin: - proj_json['origin'] = new_origin if new_name: if not utils.validate_identifier(new_name): logger.error(f'Project name must be fewer than 64 characters, contain only alphanumeric, "_" or "-" characters, and start with a letter. {new_name}') return 1 - proj_json['project_name'] = new_name + proj_json['project_name'] = new_name + if new_id: + proj_json['project_id'] = new_id + if new_origin: + proj_json['origin'] = new_origin if new_display: proj_json['display_name'] = new_display if new_summary: @@ -80,6 +82,7 @@ def _edit_project_props(args: argparse) -> int: return edit_project_props(args.project_path, args.project_name, args.project_new_name, + args.project_id, args.project_origin, args.project_display, args.project_summary, @@ -98,6 +101,8 @@ def add_parser_args(parser): group = parser.add_argument_group('properties', 'arguments for modifying individual project properties.') group.add_argument('-pnn', '--project-new-name', type=str, required=False, help='Sets the name for the project.') + group.add_argument('-pid', '--project-id', type=str, required=False, + help='Sets the ID for the project.') group.add_argument('-po', '--project-origin', type=str, required=False, help='Sets description or url for project origin (such as project host, repository, owner...etc).') group.add_argument('-pd', '--project-display', type=str, required=False, diff --git a/scripts/o3de/o3de/register.py b/scripts/o3de/o3de/register.py index 7625ee8ba9..a175104997 100644 --- a/scripts/o3de/o3de/register.py +++ b/scripts/o3de/o3de/register.py @@ -570,7 +570,7 @@ def remove_invalid_o3de_projects(manifest_path: pathlib.Path = None) -> int: result = 0 for project in json_data.get('projects', []): - if not validation.valid_o3de_project_json(pathlib.Path(project).resolve() / 'project.json'): + if not validation.valid_o3de_project_json(pathlib.Path(project).resolve() / 'project.json', generate_uuid=True): logger.warning(f"Project path {project} is invalid.") # Attempt to unregister all invalid projects even if previous projects failed to unregister # but combine the result codes of each command. diff --git a/scripts/o3de/o3de/validation.py b/scripts/o3de/o3de/validation.py index cd0958a250..3daa995c98 100644 --- a/scripts/o3de/o3de/validation.py +++ b/scripts/o3de/o3de/validation.py @@ -10,6 +10,7 @@ This file validating o3de object json files """ import json import pathlib +import uuid def valid_o3de_json_dict(json_data: dict, key: str) -> bool: @@ -45,7 +46,7 @@ def valid_o3de_engine_json(file_name: str or pathlib.Path) -> bool: return True -def valid_o3de_project_json(file_name: str or pathlib.Path) -> bool: +def valid_o3de_project_json(file_name: str or pathlib.Path, generate_uuid: bool = True) -> bool: file_name = pathlib.Path(file_name).resolve() if not file_name.is_file(): return False @@ -54,8 +55,22 @@ def valid_o3de_project_json(file_name: str or pathlib.Path) -> bool: try: json_data = json.load(f) _ = json_data['project_name'] + + if not generate_uuid: + _ = json_data['project_id'] + else: + test = json_data.get('project_id', 'No ID') + generate_new_id = test == 'No ID' + except (json.JSONDecodeError, KeyError): return False + + # Generate a random uuid for the project json if it is missing instead of failing if generate_uuid is true + if generate_uuid and generate_new_id: + with file_name.open('w') as f: + new_uuid = '{' + str(uuid.uuid4()) + '}' + json_data.update({'project_id': new_uuid}) + f.write(json.dumps(json_data, indent=4) + '\n') return True diff --git a/scripts/o3de/tests/unit_test_project_properties.py b/scripts/o3de/tests/unit_test_project_properties.py index 0236e6cf90..dcf736f016 100644 --- a/scripts/o3de/tests/unit_test_project_properties.py +++ b/scripts/o3de/tests/unit_test_project_properties.py @@ -16,6 +16,7 @@ from o3de import project_properties TEST_PROJECT_JSON_PAYLOAD = ''' { "project_name": "TestProject", + "project_id": "{24114e69-306d-4de6-b3b4-4cb1a3eca58e}", "origin": "The primary repo for TestProject goes here: i.e. http://www.mydomain.com", "license": "What license TestProject uses goes here: i.e. https://opensource.org/licenses/MIT", "display_name": "TestProject", @@ -45,20 +46,20 @@ def init_project_json_data(request): @pytest.mark.usefixtures('init_project_json_data') class TestEditProjectProperties: - @pytest.mark.parametrize("project_path, project_name, project_new_name, project_origin, project_display,\ - project_summary, project_icon, add_tags, delete_tags,\ - replace_tags, expected_result", [ + @pytest.mark.parametrize("project_path, project_name, project_new_name, project_id, project_origin,\ + project_display, project_summary, project_icon,\ + add_tags, delete_tags, replace_tags, expected_result", [ pytest.param(pathlib.PurePath('E:/TestProject'), - 'test', 'test', 'editing by pytest', 'Unit Test', 'pyTest project', 'pytest.bmp', 'A B C', + 'test', 'test', 'editing by pytest', 'ID', 'Unit Test', 'pyTest project', 'pytest.bmp', 'A B C', 'B', 'D E F', 0), pytest.param('', - 'test', 'test', 'editing by pytest', 'Unit Test', 'pyTest project', 'pytest.bmp', 'A B C', + 'test', 'test', 'editing by pytest', 'ID', 'Unit Test', 'pyTest project', 'pytest.bmp', 'A B C', 'B', 'D E F', 1) ] ) - def test_edit_project_properties(self, project_path, project_name, project_new_name, project_origin, project_display, - project_summary, project_icon, add_tags, delete_tags, - replace_tags, expected_result): + def test_edit_project_properties(self, project_path, project_name, project_new_name, project_id, project_origin, + project_display, project_summary, project_icon, add_tags, + delete_tags, replace_tags, expected_result): def get_project_json_data(project_name: str, project_path) -> dict: if not project_path: @@ -72,12 +73,14 @@ class TestEditProjectProperties: with patch('o3de.manifest.get_project_json_data', side_effect=get_project_json_data) as get_project_json_data_patch, \ patch('o3de.manifest.save_o3de_manifest', side_effect=save_o3de_manifest) as save_o3de_manifest_patch: - result = project_properties.edit_project_props(project_path, project_name, project_new_name, project_origin, - project_display, project_summary, project_icon, - add_tags, delete_tags, replace_tags) + result = project_properties.edit_project_props(project_path, project_name, project_new_name, project_id, + project_origin, project_display, project_summary, project_icon, + add_tags, delete_tags, replace_tags) assert result == expected_result if project_path: assert self.project_json.data + assert self.project_json.data.get('project_name', '') == project_new_name + assert self.project_json.data.get('project_id', '') == project_id assert self.project_json.data.get('origin', '') == project_origin assert self.project_json.data.get('display_name', '') == project_display assert self.project_json.data.get('summary', '') == project_summary