diff --git a/Code/Tools/ProjectManager/Source/EngineInfo.cpp b/Code/Tools/ProjectManager/Source/EngineInfo.cpp new file mode 100644 index 0000000000..8043a498ff --- /dev/null +++ b/Code/Tools/ProjectManager/Source/EngineInfo.cpp @@ -0,0 +1,21 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include "EngineInfo.h" + +namespace O3DE::ProjectManager +{ + EngineInfo::EngineInfo(const QString& path) + : m_path(path) + { + } +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/EngineInfo.h b/Code/Tools/ProjectManager/Source/EngineInfo.h new file mode 100644 index 0000000000..ada6e73a15 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/EngineInfo.h @@ -0,0 +1,29 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#endif + +namespace O3DE::ProjectManager +{ + class EngineInfo + { + public: + EngineInfo() = default; + EngineInfo(const QString& path); + + QString m_path; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 9f3d3b6e23..c62674122f 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -14,6 +14,7 @@ #include #include #include +#include namespace O3DE::ProjectManager { @@ -78,6 +79,14 @@ namespace O3DE::ProjectManager false)); } // End: Temporary gem test data + auto result = PythonBindingsInterface::Get()->GetGems(); + if (result.IsSuccess()) + { + for (auto gemInfo : result.GetValue()) + { + m_gemModel->AddGem(gemInfo); + } + } } ProjectManagerScreen GemCatalogScreen::GetScreenEnum() diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp index b11c7a7a2c..7ba4021205 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.cpp @@ -22,4 +22,10 @@ namespace O3DE::ProjectManager , m_isAdded(isAdded) { } + + bool GemInfo::IsValid() const + { + return !m_path.isEmpty() && !m_uuid.IsNull(); + } + } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h index 4766187d2a..098b67dbf5 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h @@ -35,8 +35,11 @@ namespace O3DE::ProjectManager }; Q_DECLARE_FLAGS(Platforms, Platform) + GemInfo() = default; GemInfo(const QString& name, const QString& creator, const QString& summary, Platforms platforms, bool isAdded); + bool IsValid() const; + QString m_path; QString m_name; QString m_displayName; diff --git a/Code/Tools/ProjectManager/Source/ProjectInfo.cpp b/Code/Tools/ProjectManager/Source/ProjectInfo.cpp index 77bcb6f1b2..4dc6eaa38b 100644 --- a/Code/Tools/ProjectManager/Source/ProjectInfo.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectInfo.cpp @@ -25,4 +25,9 @@ namespace O3DE::ProjectManager , m_isNew(isNew) { } + + bool ProjectInfo::IsValid() const + { + return !m_path.isEmpty() && !m_projectId.IsNull(); + } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectInfo.h b/Code/Tools/ProjectManager/Source/ProjectInfo.h index 04ae358c14..d86991e76e 100644 --- a/Code/Tools/ProjectManager/Source/ProjectInfo.h +++ b/Code/Tools/ProjectManager/Source/ProjectInfo.h @@ -26,7 +26,9 @@ namespace O3DE::ProjectManager ProjectInfo(const QString& path, const QString& projectName, const QString& productName, const AZ::Uuid projectId, const QString& imagePath, const QString& backgroundImagePath, bool isNew); - // From o3de_manifest.json and o3de_projects.json + bool IsValid() const; + + // from o3de_manifest.json and o3de_projects.json QString m_path; // From project.json diff --git a/Code/Tools/ProjectManager/Source/ProjectTemplateInfo.cpp b/Code/Tools/ProjectManager/Source/ProjectTemplateInfo.cpp new file mode 100644 index 0000000000..32c3146510 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/ProjectTemplateInfo.cpp @@ -0,0 +1,26 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include "ProjectTemplateInfo.h" + +namespace O3DE::ProjectManager +{ + ProjectTemplateInfo::ProjectTemplateInfo(const QString& path) + : m_path(path) + { + } + + bool ProjectTemplateInfo::IsValid() const + { + return !m_path.isEmpty() && !m_name.isEmpty(); + } +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectTemplateInfo.h b/Code/Tools/ProjectManager/Source/ProjectTemplateInfo.h new file mode 100644 index 0000000000..0477968050 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/ProjectTemplateInfo.h @@ -0,0 +1,37 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#include +#endif + +namespace O3DE::ProjectManager +{ + class ProjectTemplateInfo + { + public: + ProjectTemplateInfo() = default; + ProjectTemplateInfo(const QString& path); + + bool IsValid() const; + + QString m_displayName; + QString m_name; + QString m_path; + QString m_summary; + QStringList m_canonicalTags; + QStringList m_userTags; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index cc14e9e4de..cc5348fd22 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -12,12 +12,11 @@ #include + // Qt defines slots, which interferes with the use here. #pragma push_macro("slots") #undef slots -#include #include -#include #include #include #pragma pop_macro("slots") @@ -51,6 +50,9 @@ namespace Platform } // namespace Platform +#define Py_To_String(obj) obj.cast().c_str() +#define Py_To_String_Optional(dict, key, default_string) dict.contains(key) ? Py_To_String(dict[key]) : default_string + namespace O3DE::ProjectManager { PythonBindings::PythonBindings(const AZ::IO::PathView& enginePath) @@ -109,10 +111,13 @@ namespace O3DE::ProjectManager result = PyRun_SimpleString(AZStd::string::format("sys.path.append('%s')", m_enginePath.c_str()).c_str()); AZ_Warning("ProjectManagerWindow", result != -1, "Append to sys path failed"); + // import required modules + m_registration = pybind11::module::import("cmake.Tools.registration"); + return result == 0 && !PyErr_Occurred(); } catch ([[maybe_unused]] const std::exception& e) { - AZ_Warning("python", false, "Py_Initialize() failed with %s", e.what()); + AZ_Warning("ProjectManagerWindow", false, "Py_Initialize() failed with %s", e.what()); return false; } } @@ -125,31 +130,256 @@ namespace O3DE::ProjectManager } else { - AZ_Warning("python", false, "Did not finalize since Py_IsInitialized() was false"); + AZ_Warning("ProjectManagerWindow", false, "Did not finalize since Py_IsInitialized() was false"); } return !PyErr_Occurred(); } - void PythonBindings::ExecuteWithLock(AZStd::function executionCallback) + bool PythonBindings::ExecuteWithLock(AZStd::function executionCallback) { AZStd::lock_guard lock(m_lock); pybind11::gil_scoped_release release; pybind11::gil_scoped_acquire acquire; - executionCallback(); + + try + { + executionCallback(); + return true; + } + catch ([[maybe_unused]] const std::exception& e) + { + AZ_Warning("PythonBindings", false, "Python exception %s", e.what()); + return false; + } } - ProjectInfo PythonBindings::GetCurrentProject() + AZ::Outcome PythonBindings::GetEngineInfo() { - ProjectInfo project; + return AZ::Failure(); + } - ExecuteWithLock([&] { - auto currentProjectTool = pybind11::module::import("cmake.Tools.current_project"); - auto getCurrentProject = currentProjectTool.attr("get_current_project"); - auto currentProject = getCurrentProject(m_enginePath.c_str()); + bool PythonBindings::SetEngineInfo([[maybe_unused]] const EngineInfo& engineInfo) + { + return false; + } - project.m_path = currentProject.cast().c_str(); + AZ::Outcome PythonBindings::GetGem(const QString& path) + { + GemInfo gemInfo = GemInfoFromPath(pybind11::str(path.toStdString())); + if (gemInfo.IsValid()) + { + return AZ::Success(AZStd::move(gemInfo)); + } + else + { + return AZ::Failure(); + } + } + + AZ::Outcome> PythonBindings::GetGems() + { + QVector gems; + + bool result = ExecuteWithLock([&] { + // external gems + for (auto path : m_registration.attr("get_gems")()) + { + gems.push_back(GemInfoFromPath(path)); + } + + // gems from the engine + for (auto path : m_registration.attr("get_engine_gems")()) + { + gems.push_back(GemInfoFromPath(path)); + } }); - return project; + if (!result) + { + return AZ::Failure(); + } + else + { + return AZ::Success(AZStd::move(gems)); + } + } + + AZ::Outcome PythonBindings::CreateProject([[maybe_unused]] const ProjectTemplateInfo& projectTemplate,[[maybe_unused]] const ProjectInfo& projectInfo) + { + return AZ::Failure(); + } + + AZ::Outcome PythonBindings::GetProject(const QString& path) + { + ProjectInfo projectInfo = ProjectInfoFromPath(pybind11::str(path.toStdString())); + if (projectInfo.IsValid()) + { + return AZ::Success(AZStd::move(projectInfo)); + } + else + { + return AZ::Failure(); + } + } + + GemInfo PythonBindings::GemInfoFromPath(pybind11::handle path) + { + GemInfo gemInfo; + gemInfo.m_path = Py_To_String(path); + + auto data = m_registration.attr("get_gem_data")(pybind11::none(), path); + if (pybind11::isinstance(data)) + { + try + { + // required + gemInfo.m_name = Py_To_String(data["Name"]); + gemInfo.m_uuid = AZ::Uuid(Py_To_String(data["Uuid"])); + + // optional + gemInfo.m_displayName = Py_To_String_Optional(data, "DisplayName", gemInfo.m_name); + gemInfo.m_summary = Py_To_String_Optional(data, "Summary", ""); + gemInfo.m_version = Py_To_String_Optional(data, "Version", ""); + + if (data.contains("Dependencies")) + { + for (auto dependency : data["Dependencies"]) + { + gemInfo.m_dependingGemUuids.push_back(AZ::Uuid(Py_To_String(dependency["Uuid"]))); + } + } + if (data.contains("Tags")) + { + for (auto tag : data["Tags"]) + { + gemInfo.m_features.push_back(Py_To_String(tag)); + } + } + } + catch ([[maybe_unused]] const std::exception& e) + { + AZ_Warning("PythonBindings", false, "Failed to get GemInfo for gem %s", Py_To_String(path)); + } + } + + return gemInfo; + } + + ProjectInfo PythonBindings::ProjectInfoFromPath(pybind11::handle path) + { + ProjectInfo projectInfo; + projectInfo.m_path = Py_To_String(path); + + auto projectData = m_registration.attr("get_project_data")(pybind11::none(), path); + if (pybind11::isinstance(projectData)) + { + try + { + // required fields + projectInfo.m_productName = Py_To_String(projectData["product_name"]); + projectInfo.m_projectName = Py_To_String(projectData["project_name"]); + projectInfo.m_projectId = AZ::Uuid(Py_To_String(projectData["project_id"])); + } + catch ([[maybe_unused]] const std::exception& e) + { + AZ_Warning("PythonBindings", false, "Failed to get ProjectInfo for project %s", Py_To_String(path)); + } + } + + return projectInfo; + } + + AZ::Outcome> PythonBindings::GetProjects() + { + QVector projects; + + bool result = ExecuteWithLock([&] { + // external projects + for (auto path : m_registration.attr("get_projects")()) + { + projects.push_back(ProjectInfoFromPath(path)); + } + + // projects from the engine + for (auto path : m_registration.attr("get_engine_projects")()) + { + projects.push_back(ProjectInfoFromPath(path)); + } + }); + + if (!result) + { + return AZ::Failure(); + } + else + { + return AZ::Success(AZStd::move(projects)); + } + } + + bool PythonBindings::UpdateProject([[maybe_unused]] const ProjectInfo& projectInfo) + { + return false; + } + + ProjectTemplateInfo PythonBindings::ProjectTemplateInfoFromPath(pybind11::handle path) + { + ProjectTemplateInfo templateInfo; + templateInfo.m_path = Py_To_String(path); + + auto data = m_registration.attr("get_template_data")(pybind11::none(), path); + if (pybind11::isinstance(data)) + { + try + { + // required + templateInfo.m_displayName = Py_To_String(data["display_name"]); + templateInfo.m_name = Py_To_String(data["template_name"]); + templateInfo.m_summary = Py_To_String(data["summary"]); + + // optional + if (data.contains("canonical_tags")) + { + for (auto tag : data["canonical_tags"]) + { + templateInfo.m_canonicalTags.push_back(Py_To_String(tag)); + } + } + if (data.contains("user_tags")) + { + for (auto tag : data["user_tags"]) + { + templateInfo.m_canonicalTags.push_back(Py_To_String(tag)); + } + } + } + catch ([[maybe_unused]] const std::exception& e) + { + AZ_Warning("PythonBindings", false, "Failed to get ProjectTemplateInfo for %s", Py_To_String(path)); + } + } + + return templateInfo; + } + + AZ::Outcome> PythonBindings::GetProjectTemplates() + { + QVector templates; + + bool result = ExecuteWithLock([&] { + for (auto path : m_registration.attr("get_project_templates")()) + { + templates.push_back(ProjectTemplateInfoFromPath(path)); + } + }); + + if (!result) + { + return AZ::Failure(); + } + else + { + return AZ::Success(AZStd::move(templates)); + } } } diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index ac55fffe80..9183ca2424 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -15,6 +15,14 @@ #include #include +// Qt defines slots, which interferes with the use here. +#pragma push_macro("slots") +#undef slots +#include +#include +#pragma pop_macro("slots") + + namespace O3DE::ProjectManager { class PythonBindings @@ -26,16 +34,35 @@ namespace O3DE::ProjectManager ~PythonBindings() override; // PythonBindings overrides - ProjectInfo GetCurrentProject() override; + // Engine + AZ::Outcome GetEngineInfo() override; + bool SetEngineInfo(const EngineInfo& engineInfo) override; + + // Gem + AZ::Outcome GetGem(const QString& path) override; + AZ::Outcome> GetGems() override; + + // Project + AZ::Outcome CreateProject(const ProjectTemplateInfo& projectTemplate, const ProjectInfo& projectInfo) override; + AZ::Outcome GetProject(const QString& path) override; + AZ::Outcome> GetProjects() override; + bool UpdateProject(const ProjectInfo& projectInfo) override; + + // ProjectTemplate + AZ::Outcome> GetProjectTemplates() override; private: AZ_DISABLE_COPY_MOVE(PythonBindings); - void ExecuteWithLock(AZStd::function executionCallback); + bool ExecuteWithLock(AZStd::function executionCallback); + GemInfo GemInfoFromPath(pybind11::handle path); + ProjectInfo ProjectInfoFromPath(pybind11::handle path); + ProjectTemplateInfo ProjectTemplateInfoFromPath(pybind11::handle path); bool StartPython(); bool StopPython(); AZ::IO::FixedMaxPath m_enginePath; AZStd::recursive_mutex m_lock; + pybind11::handle m_registration; }; } diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 78c3625415..f696ea958d 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -15,9 +15,12 @@ #include #include #include +#include +#include #include #include +#include namespace O3DE::ProjectManager { @@ -31,8 +34,76 @@ namespace O3DE::ProjectManager IPythonBindings() = default; virtual ~IPythonBindings() = default; - //! Get the current project - virtual ProjectInfo GetCurrentProject() = 0; + + // Engine + + /** + * Get info about the engine + * @return an outcome with EngineInfo on success + */ + virtual AZ::Outcome GetEngineInfo() = 0; + + /** + * Set info about the engine + * @param engineInfo an EngineInfo object + */ + virtual bool SetEngineInfo(const EngineInfo& engineInfo) = 0; + + + // Gems + + /** + * Get info about a Gem + * @param path the absolute path to the Gem + * @return an outcome with GemInfo on success + */ + virtual AZ::Outcome GetGem(const QString& path) = 0; + + /** + * Get info about all known Gems + * @return an outcome with GemInfos on success + */ + virtual AZ::Outcome> GetGems() = 0; + + + // Projects + + /** + * Create a project + * @param projectTemplate the project template to use + * @param projectInfo the project info to use + * @return an outcome with ProjectInfo on success + */ + virtual AZ::Outcome CreateProject(const ProjectTemplateInfo& projectTemplate, const ProjectInfo& projectInfo) = 0; + + /** + * Get info about a project + * @param path the absolute path to the project + * @return an outcome with ProjectInfo on success + */ + virtual AZ::Outcome GetProject(const QString& path) = 0; + + /** + * Get info about all known projects + * @return an outcome with ProjectInfos on success + */ + virtual AZ::Outcome> GetProjects() = 0; + + /** + * Update a project + * @param projectInfo the info to use to update the project + * @return true on success, false on failure + */ + virtual bool UpdateProject(const ProjectInfo& projectInfo) = 0; + + + // Project Templates + + /** + * Get info about all known project templates + * @return an outcome with ProjectTemplateInfos on success + */ + virtual AZ::Outcome> GetProjectTemplates() = 0; }; using PythonBindingsInterface = AZ::Interface; diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index 86a538f9db..3249d293ad 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -18,11 +18,15 @@ set(FILES Source/ScreensCtrl.h Source/ScreensCtrl.cpp Source/ScreenWidget.h + Source/EngineInfo.h + Source/EngineInfo.cpp Source/FirstTimeUseScreen.h Source/FirstTimeUseScreen.cpp Source/FirstTimeUseScreen.ui Source/ProjectManagerWindow.h Source/ProjectManagerWindow.cpp + Source/ProjectTemplateInfo.h + Source/ProjectTemplateInfo.cpp Source/ProjectManagerWindow.ui Source/PythonBindings.h Source/PythonBindings.cpp