Refactored ProjectManagerSettings, added tests, changed project built successful to be tracked by project path, added project ids, changed project settings path to use project id and name

Signed-off-by: nggieber <52797929+AMZN-nggieber@users.noreply.github.com>
This commit is contained in:
nggieber
2021-12-06 09:22:03 -08:00
parent 6150a60e57
commit 15237af23c
28 changed files with 622 additions and 132 deletions
+4 -2
View File
@@ -10,5 +10,7 @@
"version_name": "1.0.0.0",
"orientation": "landscape"
},
"engine": "o3de"
}
"engine": "o3de",
"display_name": "AutomatedTesting",
"icon_path": "preview.png"
}
+3
View File
@@ -89,6 +89,9 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
3rdParty::Qt::Widgets
3rdParty::Python
3rdParty::pybind11
AZ::AzCore
AZ::AzCoreTestCommon
AZ::AzTestShared
AZ::AzTest
AZ::AzFramework
AZ::AzFrameworkTestShared
@@ -10,8 +10,6 @@
#include <LinkWidget.h>
#include <ProjectManagerSettings.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <QDialogButtonBox>
#include <QLabel>
#include <QPushButton>
@@ -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();
}
PMSettings::SetProjectManagerKey(PMSettings::GetExternalLinkWarningKey(), state);
}
} // namespace O3DE::ProjectManager
@@ -9,7 +9,6 @@
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Math/Uuid.h>
#include <QString>
#include <QStringList>
#include <QVector>
@@ -33,13 +33,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());
}
PMSettings::GetProjectManagerKey(skipDialog, PMSettings::GetExternalLinkWarningKey());
if (!skipDialog)
{
@@ -16,6 +16,8 @@
#include <EngineInfo.h>
#include <CreateProjectCtrl.h>
#include <TagWidget.h>
#include <AzCore/Math/Uuid.h>
#include <AzQtComponents/Components/FlowLayout.h>
#include <QVBoxLayout>
@@ -41,9 +43,11 @@ namespace O3DE::ProjectManager
{
const QString defaultName = GetDefaultProjectName();
const QString defaultPath = QDir::toNativeSeparators(GetDefaultProjectPath() + "/" + defaultName);
const QString randomUuid = GenerateNewProjectId();
m_projectName->lineEdit()->setText(defaultName);
m_projectPath->lineEdit()->setText(defaultPath);
m_projectId->lineEdit()->setText(randomUuid);
// if we don't use a QFrame we cannot "contain" the widgets inside and move them around
// as a group
@@ -173,6 +177,14 @@ namespace O3DE::ProjectManager
return QDir::toNativeSeparators(GetDefaultProjectPath() + "/" + projectName);
}
QString NewProjectSettingsScreen::GenerateNewProjectId()
{
AZStd::string uuid;
AZ::Uuid::CreateRandom().ToString(uuid);
return QString(uuid.c_str());
}
ProjectManagerScreen NewProjectSettingsScreen::GetScreenEnum()
{
return ProjectManagerScreen::NewProjectSettings;
@@ -225,7 +237,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);
@@ -47,6 +47,7 @@ namespace O3DE::ProjectManager
private:
QString GetDefaultProjectName();
QString GetDefaultProjectPath();
QString GenerateNewProjectId();
QString GetProjectAutoPath();
QFrame* CreateTemplateDetails(int margin);
void UpdateTemplateDetails(const ProjectTemplateInfo& templateInfo);
@@ -29,14 +29,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 crashing while building that causes HandleResults to not be called
PMSettings::SetProjectBuiltSuccessfully(m_projectInfo, false);
connect(&m_workerThread, &QThread::finished, m_worker, &ProjectBuilderWorker::deleteLater);
connect(&m_workerThread, &QThread::started, m_worker, &ProjectBuilderWorker::BuildProject);
@@ -91,8 +85,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 +114,7 @@ namespace O3DE::ProjectManager
emit NotifyBuildProject(m_projectInfo);
}
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
settingsRegistry->Remove(settingsKey.toStdString().c_str());
SaveProjectManagerSettings();
}
PMSettings::SetProjectBuiltSuccessfully(m_projectInfo, false);
emit Done(false);
return;
@@ -136,12 +123,7 @@ namespace O3DE::ProjectManager
{
m_projectInfo.m_buildFailed = false;
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
settingsRegistry->Set(settingsKey.toStdString().c_str(), true);
SaveProjectManagerSettings();
}
PMSettings::SetProjectBuiltSuccessfully(m_projectInfo, true);
}
emit Done(true);
@@ -9,14 +9,13 @@
#include <ProjectInfo.h>
#include <ProjectManagerDefs.h>
#include <QDir>
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
@@ -9,7 +9,6 @@
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Math/Uuid.h>
#include <QUrl>
#include <QString>
#include <QStringList>
@@ -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;
@@ -14,46 +14,235 @@
namespace O3DE::ProjectManager
{
void SaveProjectManagerSettings()
namespace PMSettings
{
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))
bool SaveProjectManagerSettings()
{
AZ_Warning("ProjectManager", false, "Could not save Project Manager settings to stream");
return;
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 false;
}
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());
return saved;
}
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))
bool GetProjectManagerKey(QString& result, const QString& settingsKey)
{
saved = outputFile.Write(stringBuffer.data(), stringBuffer.size()) == stringBuffer.size();
bool success = false;
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
AZStd::string settingsValue;
success = settingsRegistry->Get(settingsValue, settingsKey.toStdString().c_str());
result = settingsValue.c_str();
}
return success;
}
AZ_Warning("ProjectManager", saved, "Unable to save Project Manager registry file to path: %s", o3deUserPath.c_str());
}
bool GetProjectManagerKey(bool& result, const QString& settingsKey)
{
bool success = false;
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
success = settingsRegistry->Get(result, settingsKey.toStdString().c_str());
}
QString GetProjectBuiltSuccessfullyKey(const QString& projectName)
{
return QString("%1/Projects/%2/BuiltSuccessfully").arg(ProjectManagerKeyPrefix).arg(projectName);
}
return success;
}
QString GetExternalLinkWarningKey()
{
return QString("%1/SkipExternalLinkWarning").arg(ProjectManagerKeyPrefix);
}
}
bool SetProjectManagerKey(const QString& settingsKey, const QString& settingsValue, bool saveToDisk)
{
bool success = false;
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
success = settingsRegistry->Set(settingsKey.toStdString().c_str(), settingsValue.toStdString().c_str());
if (saveToDisk)
{
SaveProjectManagerSettings();
}
}
return success;
}
bool SetProjectManagerKey(const QString& settingsKey, bool settingsValue, bool saveToDisk)
{
bool success = false;
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
success = settingsRegistry->Set(settingsKey.toStdString().c_str(), settingsValue);
if (saveToDisk)
{
SaveProjectManagerSettings();
}
}
return success;
}
bool RemoveProjectManagerKey(const QString& settingsKey, bool saveToDisk)
{
bool success = false;
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
success = settingsRegistry->Remove(settingsKey.toStdString().c_str());
if (saveToDisk)
{
SaveProjectManagerSettings();
}
}
return success;
}
bool CopyProjectManagerKeyString(const QString& settingsKeyOrig, const QString& settingsKeyDest, bool removeOrig, bool saveToDisk)
{
bool success = false;
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
AZStd::string settingsValue;
success = settingsRegistry->Get(settingsValue, settingsKeyOrig.toStdString().c_str());
if (success)
{
success = settingsRegistry->Set(settingsKeyDest.toStdString().c_str(), settingsValue);
if (success)
{
if (removeOrig)
{
success = settingsRegistry->Remove(settingsKeyOrig.toStdString().c_str());
}
if (saveToDisk)
{
SaveProjectManagerSettings();
}
}
}
}
return success;
}
QString GetProjectKey(const ProjectInfo& projectInfo)
{
return QString("%1/Projects/%2/%3").arg(ProjectManagerKeyPrefix, projectInfo.m_id, projectInfo.m_projectName);
}
QString GetExternalLinkWarningKey()
{
return QString("%1/SkipExternalLinkWarning").arg(ProjectManagerKeyPrefix);
}
QString GetProjectsBuiltSuccessfullyKey()
{
return QString("%1/SuccessfulBuildPaths").arg(ProjectManagerKeyPrefix);
}
bool GetBuiltSuccessfullyPaths(AZStd::set<AZStd::string>& result)
{
bool success = false;
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
QString builtKey = GetProjectsBuiltSuccessfullyKey();
success = settingsRegistry->GetObject<AZStd::set<AZStd::string>>(result, builtKey.toStdString().c_str());
}
return success;
}
bool GetProjectBuiltSuccessfully(bool& result, const ProjectInfo& projectInfo)
{
AZStd::set<AZStd::string> builtPathsResult;
bool success = GetBuiltSuccessfullyPaths(builtPathsResult);
if (success)
{
// 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 SetProjectBuiltSuccessfully(const ProjectInfo& projectInfo, bool successfullyBuilt, bool saveToDisk)
{
AZStd::set<AZStd::string> 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);
}
auto settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
QString builtKey = GetProjectsBuiltSuccessfullyKey();
success = settingsRegistry->SetObject<AZStd::set<AZStd::string>>(builtKey.toStdString().c_str(), builtPathsResult);
if (saveToDisk)
{
SaveProjectManagerSettings();
}
}
else
{
success = false;
}
return success;
}
} // namespace PMSettings
} // namespace O3DE::ProjectManager
@@ -9,14 +9,29 @@
#pragma once
#if !defined(Q_MOC_RUN)
#include <QString>
#include <QStringList>
#include <ProjectInfo.h>
#endif
namespace O3DE::ProjectManager
{
static constexpr char ProjectManagerKeyPrefix[] = "/O3DE/ProjectManager";
namespace PMSettings
{
static constexpr char ProjectManagerKeyPrefix[] = "/O3DE/ProjectManager";
void SaveProjectManagerSettings();
QString GetProjectBuiltSuccessfullyKey(const QString& projectName);
QString GetExternalLinkWarningKey();
}
bool SaveProjectManagerSettings();
bool GetProjectManagerKey(QString& result, const QString& settingsKey);
bool GetProjectManagerKey(bool& result, const QString& settingsKey);
bool SetProjectManagerKey(const QString& settingsKey, const QString& settingsValue, bool saveToDisk = true);
bool SetProjectManagerKey(const QString& settingsKey, bool settingsValue, bool saveToDisk = true);
bool RemoveProjectManagerKey(const QString& settingsKey, bool saveToDisk = true);
bool CopyProjectManagerKeyString(
const QString& settingsKeyOrig, const QString& settingsKeyDest, bool removeOrig = false, bool saveToDisk = true);
QString GetProjectKey(const ProjectInfo& projectInfo);
QString GetExternalLinkWarningKey();
bool GetProjectBuiltSuccessfully(bool& result, const ProjectInfo& projectInfo);
bool SetProjectBuiltSuccessfully(const ProjectInfo& projectInfo, bool successfullyBuilt, bool saveToDisk = true);
} // namespace PMSettings
} // namespace O3DE::ProjectManager
@@ -60,6 +60,10 @@ namespace O3DE::ProjectManager
connect(m_projectPath->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::OnProjectPathUpdated);
m_verticalLayout->addWidget(m_projectPath);
m_projectId = new FormLineEditWidget(tr("Project ID"), "", this);
connect(m_projectId->lineEdit(), &QLineEdit::textChanged, this, &ProjectSettingsScreen::OnProjectIdUpdated);
m_verticalLayout->addWidget(m_projectId);
projectSettingsFrame->setLayout(m_verticalLayout);
m_horizontalLayout->addWidget(projectSettingsFrame);
@@ -94,6 +98,7 @@ namespace O3DE::ProjectManager
// currently we don't have separate fields for changing the project name and display name
projectInfo.m_displayName = projectInfo.m_projectName;
projectInfo.m_path = m_projectPath->lineEdit()->text();
projectInfo.m_id = m_projectId->lineEdit()->text();
return projectInfo;
}
@@ -142,6 +147,19 @@ namespace O3DE::ProjectManager
return projectPathIsValid;
}
bool ProjectSettingsScreen::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 ProjectSettingsScreen::OnProjectNameUpdated()
{
ValidateProjectName();
@@ -149,11 +167,16 @@ namespace O3DE::ProjectManager
void ProjectSettingsScreen::OnProjectPathUpdated()
{
Validate();
ValidateProjectName() && ValidateProjectPath();
}
void ProjectSettingsScreen::OnProjectIdUpdated()
{
ValidateProjectId();
}
bool ProjectSettingsScreen::Validate()
{
return ValidateProjectName() && ValidateProjectPath();
return ValidateProjectName() && ValidateProjectPath() && ValidateProjectId();
}
} // namespace O3DE::ProjectManager
@@ -35,10 +35,12 @@ namespace O3DE::ProjectManager
protected slots:
virtual void OnProjectNameUpdated();
virtual void OnProjectPathUpdated();
virtual void OnProjectIdUpdated();
protected:
bool ValidateProjectName();
virtual bool ValidateProjectPath();
bool ValidateProjectId();
QString GetDefaultProjectPath();
@@ -46,6 +48,7 @@ namespace O3DE::ProjectManager
QVBoxLayout* m_verticalLayout;
FormLineEditWidget* m_projectName;
FormBrowseEditWidget* m_projectPath;
FormLineEditWidget* m_projectId;
};
} // namespace O3DE::ProjectManager
@@ -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());
}
PMSettings::GetProjectBuiltSuccessfully(projectBuiltSuccessfully, project);
if (!projectBuiltSuccessfully)
{
currentButton->ShowBuildRequired();
@@ -687,8 +687,24 @@ 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
pybind11::none(), // template_name
pybind11::none(), // project_restricted_path
pybind11::none(), // project_restricted_name
pybind11::none(), // template_restricted_path
pybind11::none(), // template_restricted_name
pybind11::none(), // project_restricted_platform_relative_path
pybind11::none(), // template_restricted_platform_relative_path
pybind11::none(), // keep_restricted_in_project
pybind11::none(), // keep_license_text
pybind11::none(), // replace
pybind11::none(), // force
pybind11::none(), // no_register
pybind11::none(), // system_component_class_id
pybind11::none(), // editor_system_component_class_id
pybind11::none(), // module_id
QString_To_Py_String(projectInfo.m_id) // project_id
);
if (createProjectResult.cast<int>() == 0)
{
@@ -822,6 +838,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);
@@ -926,6 +943,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),
@@ -17,7 +17,6 @@
#include <ProjectUtils.h>
#include <DownloadController.h>
#include <ProjectManagerSettings.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <QDialogButtonBox>
#include <QMessageBox>
@@ -306,17 +305,10 @@ 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
PMSettings::SetProjectBuiltSuccessfully(m_projectInfo, false);
PMSettings::SetProjectBuiltSuccessfully(newProjectSettings, false);
}
if (!newProjectSettings.m_newPreviewImagePath.isEmpty())
@@ -56,6 +56,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 +71,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();
}
@@ -10,8 +10,9 @@ set(FILES
Resources/ProjectManager.qrc
Resources/ProjectManager.qss
tests/ApplicationTests.cpp
tests/PythonBindingsTests.cpp
tests/GemCatalogTests.cpp
tests/ProjectManagerSettingsTests.cpp
tests/PythonBindingsTests.cpp
tests/main.cpp
tests/UtilsTests.cpp
)
@@ -0,0 +1,176 @@
/*
* 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 <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/Settings/SettingsRegistryImpl.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzTest/Utils.h>
#include <ProjectManagerSettings.h>
namespace O3DE::ProjectManager
{
class ProjectManagerSettingsTests
: public ::UnitTest::ScopedAllocatorSetupFixture
{
public:
~ProjectManagerSettingsTests() override = default;
void SetUp() override
{
UnitTest::ScopedAllocatorSetupFixture::SetUp();
m_registry = AZStd::make_unique<AZ::SettingsRegistryImpl>();
// 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<AZ::SerializeContext>();
m_registrationContext = AZStd::make_unique<AZ::JsonRegistrationContext>();
m_registry->SetContext(m_serializeContext.get());
m_registry->SetContext(m_registrationContext.get());
m_projectInfo.m_path = "Z:/ProjectTestPath";
}
void TearDown() override
{
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:
const QString m_settingsPath = "/Testing/TestKey";
const QString m_newSettingsPath = "/Testing/NewTestKey";
ProjectInfo m_projectInfo;
private:
AZ::SettingsRegistryInterface* m_oldSettingsRegistry = nullptr;
AZStd::unique_ptr<AZ::SettingsRegistryImpl> m_registry;
AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
AZStd::unique_ptr<AZ::JsonRegistrationContext> m_registrationContext;
};
TEST_F(ProjectManagerSettingsTests, PMSettings_GetUnsetPathBool_ReturnsFalse)
{
bool settingsResult = false;
EXPECT_FALSE(PMSettings::GetProjectManagerKey(settingsResult, m_settingsPath));
EXPECT_FALSE(settingsResult);
}
TEST_F(ProjectManagerSettingsTests, PMSettings_SetAndGetValueBool_Success)
{
bool settingsResult = false;
EXPECT_FALSE(PMSettings::GetProjectManagerKey(settingsResult, m_settingsPath));
// Don't save to disk in test
EXPECT_TRUE(PMSettings::SetProjectManagerKey(m_settingsPath, true, /*saveToDisk*/ false));
settingsResult = false;
EXPECT_TRUE(PMSettings::GetProjectManagerKey(settingsResult, m_settingsPath));
EXPECT_TRUE(settingsResult);
}
TEST_F(ProjectManagerSettingsTests, PMSettings_GetUnsetPathString_ReturnsFalse)
{
QString settingsResult;
EXPECT_FALSE(PMSettings::GetProjectManagerKey(settingsResult, m_settingsPath));
EXPECT_TRUE(settingsResult.isEmpty());
}
TEST_F(ProjectManagerSettingsTests, PMSettings_SetAndGetValueString_Success)
{
QString settingsResult;
EXPECT_FALSE(PMSettings::GetProjectManagerKey(settingsResult, m_settingsPath));
QString settingsValue = "TestValue";
// Don't save to disk in test
EXPECT_TRUE(PMSettings::SetProjectManagerKey(m_settingsPath, settingsValue, /*saveToDisk*/ false));
EXPECT_TRUE(PMSettings::GetProjectManagerKey(settingsResult, m_settingsPath));
EXPECT_TRUE(settingsResult == settingsValue);
}
TEST_F(ProjectManagerSettingsTests, PMSettings_CopyStringRemoveOriginal_SuccessAndRemovesOriginal)
{
QString settingsResult;
EXPECT_FALSE(PMSettings::GetProjectManagerKey(settingsResult, m_newSettingsPath));
QString settingsValue = "TestValue";
// Don't save to disk in test
EXPECT_TRUE(PMSettings::SetProjectManagerKey(m_settingsPath, settingsValue, /*saveToDisk*/ false));
EXPECT_TRUE(PMSettings::CopyProjectManagerKeyString(m_settingsPath, m_newSettingsPath, /*removeOrig*/ true, /*saveToDisk*/ false));
// Check that old path value is removed
EXPECT_FALSE(PMSettings::GetProjectManagerKey(settingsResult, m_settingsPath));
EXPECT_TRUE(PMSettings::GetProjectManagerKey(settingsResult, m_newSettingsPath));
EXPECT_TRUE(settingsResult == settingsValue);
}
TEST_F(ProjectManagerSettingsTests, PMSettings_RemoveProjectManagerKey_RemovesKey)
{
QString settingsResult;
EXPECT_FALSE(PMSettings::GetProjectManagerKey(settingsResult, m_settingsPath));
QString settingsValue = "TestValue";
// Don't save to disk in test
EXPECT_TRUE(PMSettings::SetProjectManagerKey(m_settingsPath, settingsValue, /*saveToDisk*/ false));
EXPECT_TRUE(PMSettings::GetProjectManagerKey(settingsResult, m_settingsPath));
EXPECT_TRUE(PMSettings::RemoveProjectManagerKey(m_settingsPath, /*saveToDisk*/ false));
EXPECT_FALSE(PMSettings::GetProjectManagerKey(settingsResult, m_settingsPath));
}
TEST_F(ProjectManagerSettingsTests, PMSettings_GetUnsetBuildPath_ReturnsFalse)
{
bool buildResult = true;
EXPECT_FALSE(PMSettings::GetProjectBuiltSuccessfully(buildResult, m_projectInfo));
EXPECT_FALSE(buildResult);
}
TEST_F(ProjectManagerSettingsTests, PMSettings_SetProjectBuiltSuccessfully_ReturnsTrue)
{
// Don't save to disk in test
EXPECT_TRUE(PMSettings::SetProjectBuiltSuccessfully(m_projectInfo, true, /*saveToDisk*/ false));
bool buildResult = false;
EXPECT_TRUE(PMSettings::GetProjectBuiltSuccessfully(buildResult, m_projectInfo));
EXPECT_TRUE(buildResult);
}
TEST_F(ProjectManagerSettingsTests, PMSettings_SetProjectBuiltUnsuccessfully_ReturnsFalse)
{
// Don't save to disk in test
EXPECT_TRUE(PMSettings::SetProjectBuiltSuccessfully(m_projectInfo, true, /*saveToDisk*/ false));
bool buildResult = true;
EXPECT_TRUE(PMSettings::GetProjectBuiltSuccessfully(buildResult, m_projectInfo));
EXPECT_FALSE(buildResult);
}
}
@@ -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}",
@@ -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}",
+48
View File
@@ -0,0 +1,48 @@
Prism/AddEditGemsButton
Prism/AddLicenseToGemCatalog
Prism/AddRemoveRepoWorkingUI
Prism/AddRepoDialog
* Prism/CmakeVariablesPerProject
Prism/CreateRepositoryScreen
Prism/CustomBuildParameters
Prism/CustomTabWidgetForEngine
Prism/DeleteUpdateGemsUI
Prism/DisplayAddGemInfo
Prism/EnterInFileDialog
Prism/ExternalLinkWarning
Prism/FasterBuildTimes
Prism/FixGemCart
Prism/FixGemCatalogHyperlinks
Prism/FixGemSelectedFilter
Prism/FixRemovedProjectStillShown
Prism/FixTabOverlayOnStartup
Prism/GemCatalogMenu
Prism/GemCatalogTests
Prism/GemCatalogWithoutProject
Prism/GemRepoInspector
Prism/RefreshAfterDownload
Prism/RefreshGemRepos
Prism/RefreshOnDownload
Prism/RefreshRepoButtonDisablesRepo
Prism/RemoteGemWarning
Prism/RemoveTabFocus
Prism/ResetBuildAndCacheAfterProjectMove
Prism/SearchForGemsFromTags
Prism/ShowGemDownloadStatus
Prism/ShowGemPreview
Prism/ShowGemRepoGems
Prism/ShowGemsInRepo
Prism/ShowRepoGems
Prism/ShowRepoList
Prism/UpdateTemplatePics
Prism/UseGemDisplayNames
Prism/VS2019InstallInstructions
Prism/WarnExternalLink
Prism/WarnVSNotInstalled
Prism/cherry-pick-gem-catalog-fix
Prism/gitRepos
development
new-feature
racoonteurs
stabilization/2110
stabilization/2111RTE
+17 -5
View File
@@ -1113,7 +1113,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
@@ -1338,7 +1338,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
@@ -1366,6 +1367,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:
@@ -1405,7 +1407,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
@@ -1577,6 +1579,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))
@@ -1784,7 +1792,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:
@@ -2404,6 +2413,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,
+9 -4
View File
@@ -29,6 +29,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,
@@ -40,14 +41,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:
@@ -78,6 +80,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,
@@ -95,6 +98,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,
+1 -1
View File
@@ -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.
+16 -1
View File
@@ -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:
return key in json_data
@@ -44,7 +45,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
@@ -53,8 +54,22 @@ def valid_o3de_project_json(file_name: str or pathlib.Path) -> bool:
try:
json_data = json.load(f)
test = json_data['project_name']
if not generate_uuid:
test = json_data['project_id']
else:
test = json_data.get('project_id', 'No ID')
generate_new_id = test == 'No ID'
except (json.JSONDecodeError, KeyError) as e:
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
@@ -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_origin, project_id,
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