Browse Source

Update Prism Python bindings interface

 Add GetProjects, GetProjectTemplates, GetGems, GetProject and GetGem.
CreateProject and UpdateProject and the engine functions are not implemented yet
Alex Peterson 4 năm trước cách đây
mục cha
commit
9b9ae22d23

+ 21 - 0
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

+ 29 - 0
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 <QString>
+#endif
+
+namespace O3DE::ProjectManager
+{
+    class EngineInfo
+    {
+    public:
+        EngineInfo() = default;
+        EngineInfo(const QString& path);
+
+        QString m_path;
+    };
+} // namespace O3DE::ProjectManager

+ 9 - 0
Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp

@@ -14,6 +14,7 @@
 #include <QVBoxLayout>
 #include <QHBoxLayout>
 #include <QPushButton>
+#include <PythonBindingsInterface.h>
 
 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()

+ 6 - 0
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

+ 3 - 0
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;

+ 5 - 0
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

+ 3 - 1
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

+ 26 - 0
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

+ 37 - 0
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 <QString>
+#include <QStringList>
+#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

+ 244 - 14
Code/Tools/ProjectManager/Source/PythonBindings.cpp

@@ -12,12 +12,11 @@
 
 #include <PythonBindings.h>
 
+
 // Qt defines slots, which interferes with the use here.
 #pragma push_macro("slots")
 #undef slots
-#include <Python.h>
 #include <pybind11/functional.h>
-#include <pybind11/pybind11.h>
 #include <pybind11/embed.h>
 #include <pybind11/eval.h>
 #pragma pop_macro("slots")
@@ -51,6 +50,9 @@ namespace Platform
 
 } // namespace Platform
 
+#define Py_To_String(obj) obj.cast<std::string>().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<void()> executionCallback)
+    bool PythonBindings::ExecuteWithLock(AZStd::function<void()> executionCallback)
     {
         AZStd::lock_guard<decltype(m_lock)> 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;
+        }
+    }
+
+    AZ::Outcome<EngineInfo> PythonBindings::GetEngineInfo()  
+    {
+        return AZ::Failure();
+    }
+
+    bool PythonBindings::SetEngineInfo([[maybe_unused]] const EngineInfo& engineInfo)  
+    {
+        return false;
+    }
+
+    AZ::Outcome<GemInfo> 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<QVector<GemInfo>> PythonBindings::GetGems()  
+    {
+        QVector<GemInfo> 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));
+            }
+        });
+
+        if (!result)
+        {
+            return AZ::Failure();
+        }
+        else
+        {
+            return AZ::Success(AZStd::move(gems)); 
+        }
+    }
+
+    AZ::Outcome<ProjectInfo> PythonBindings::CreateProject([[maybe_unused]] const ProjectTemplateInfo& projectTemplate,[[maybe_unused]]  const ProjectInfo& projectInfo)  
+    {
+        return AZ::Failure();
+    }
+
+    AZ::Outcome<ProjectInfo> 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<pybind11::dict>(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<pybind11::dict>(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<QVector<ProjectInfo>> PythonBindings::GetProjects()  
+    {
+        QVector<ProjectInfo> 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)); 
+        }
     }
 
-    ProjectInfo PythonBindings::GetCurrentProject()
+    bool PythonBindings::UpdateProject([[maybe_unused]] const ProjectInfo& projectInfo)  
     {
-        ProjectInfo project;
+        return false;
+    }
 
-        ExecuteWithLock([&] {
-            auto currentProjectTool = pybind11::module::import("cmake.Tools.current_project");
-            auto getCurrentProject = currentProjectTool.attr("get_current_project");
-            auto currentProject = getCurrentProject(m_enginePath.c_str());
+    ProjectTemplateInfo PythonBindings::ProjectTemplateInfoFromPath(pybind11::handle path)
+    {
+        ProjectTemplateInfo templateInfo;
+        templateInfo.m_path = Py_To_String(path); 
 
-            project.m_path = currentProject.cast<std::string>().c_str();
+        auto data = m_registration.attr("get_template_data")(pybind11::none(), path);
+        if (pybind11::isinstance<pybind11::dict>(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<QVector<ProjectTemplateInfo>> PythonBindings::GetProjectTemplates()  
+    {
+        QVector<ProjectTemplateInfo> templates;
+
+        bool result = ExecuteWithLock([&] {
+            for (auto path : m_registration.attr("get_project_templates")())
+            {
+                templates.push_back(ProjectTemplateInfoFromPath(path));
+            }
         });
 
-        return project; 
+        if (!result)
+        {
+            return AZ::Failure();
+        }
+        else
+        {
+            return AZ::Success(AZStd::move(templates)); 
+        }
     }
 }

+ 29 - 2
Code/Tools/ProjectManager/Source/PythonBindings.h

@@ -15,6 +15,14 @@
 #include <AzCore/IO/Path/Path.h> 
 #include <AzCore/std/parallel/semaphore.h>
 
+// Qt defines slots, which interferes with the use here.
+#pragma push_macro("slots")
+#undef slots
+#include <Python.h>
+#include <pybind11/pybind11.h>
+#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<EngineInfo> GetEngineInfo() override;
+        bool SetEngineInfo(const EngineInfo& engineInfo) override;
+
+        // Gem
+        AZ::Outcome<GemInfo> GetGem(const QString& path) override;
+        AZ::Outcome<QVector<GemInfo>> GetGems() override;
+
+        // Project
+        AZ::Outcome<ProjectInfo> CreateProject(const ProjectTemplateInfo& projectTemplate, const ProjectInfo& projectInfo) override;
+        AZ::Outcome<ProjectInfo> GetProject(const QString& path) override;
+        AZ::Outcome<QVector<ProjectInfo>> GetProjects() override;
+        bool UpdateProject(const ProjectInfo& projectInfo) override;
+
+        // ProjectTemplate
+        AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates() override;
 
     private:
         AZ_DISABLE_COPY_MOVE(PythonBindings);
 
-        void ExecuteWithLock(AZStd::function<void()> executionCallback);
+        bool ExecuteWithLock(AZStd::function<void()> 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;
     };
 }

+ 73 - 2
Code/Tools/ProjectManager/Source/PythonBindingsInterface.h

@@ -15,9 +15,12 @@
 #include <AzCore/Interface/Interface.h>
 #include <AzCore/std/string/string.h>
 #include <AzCore/std/containers/vector.h>
+#include <AzCore/Outcome/Outcome.h>
 
+#include <EngineInfo.h>
 #include <GemCatalog/GemInfo.h>
 #include <ProjectInfo.h>
+#include <ProjectTemplateInfo.h>
 
 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<EngineInfo> 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<GemInfo> GetGem(const QString& path) = 0;
+
+        /**
+         * Get info about all known Gems
+         * @return an outcome with GemInfos on success 
+         */
+        virtual AZ::Outcome<QVector<GemInfo>> 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<ProjectInfo> 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<ProjectInfo> GetProject(const QString& path) = 0;
+
+        /**
+         * Get info about all known projects
+         * @return an outcome with ProjectInfos on success 
+         */
+        virtual AZ::Outcome<QVector<ProjectInfo>> 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<QVector<ProjectTemplateInfo>> GetProjectTemplates() = 0;
     };
 
     using PythonBindingsInterface = AZ::Interface<IPythonBindings>;

+ 4 - 0
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