Parcourir la source

Remote templates and repo script fixes

Signed-off-by: AMZN-Phil <[email protected]>
AMZN-Phil il y a 3 ans
Parent
commit
9da65f5b2f

+ 9 - 1
Code/Tools/ProjectManager/Source/AddRemoteTemplateDialog.cpp

@@ -48,6 +48,8 @@ namespace O3DE::ProjectManager
 
         m_repoPath = new FormLineEditWidget(tr("Remote URL"), "", this);
         m_repoPath->setMinimumSize(QSize(600, 0));
+        m_repoPath->setErrorLabelText(tr("Not a valid remote template source."));
+        m_repoPath->lineEdit()->setPlaceholderText("http://github.com/o3de/example.git");
         vLayout->addWidget(m_repoPath);
 
         vLayout->addSpacing(10);
@@ -84,6 +86,7 @@ namespace O3DE::ProjectManager
             {
                 // wait for a second before attempting to validate so we're less likely to do it per keypress
                 m_inputTimer->start(1000);
+                m_repoPath->SetValidationState(FormLineEditWidget::ValidationState::Validating);
                 
             });
 
@@ -93,7 +96,12 @@ namespace O3DE::ProjectManager
     void AddRemoteTemplateDialog::ValidateURI()
     {
         // validate URI, if it's a valid repository set the add button as active
-        SetDialogReady(PythonBindingsInterface::Get()->ValidateRepository(m_repoPath->lineEdit()->text()));
+        bool validRepository = PythonBindingsInterface::Get()->ValidateRepository(m_repoPath->lineEdit()->text());
+        SetDialogReady(validRepository);
+        m_repoPath->SetValidationState(
+            validRepository ? FormLineEditWidget::ValidationState::ValidationSuccess
+                            : FormLineEditWidget::ValidationState::ValidationFailed);
+        m_repoPath->setErrorLabelVisible(!validRepository);
     }
 
     void AddRemoteTemplateDialog::AddTemplateSource()

+ 13 - 4
Code/Tools/ProjectManager/Source/CreateProjectCtrl.cpp

@@ -233,7 +233,7 @@ namespace O3DE::ProjectManager
     {
         if (m_stack->currentWidget() == m_newProjectSettingsScreen)
         {
-            return m_newProjectSettingsScreen->Validate();
+            return m_newProjectSettingsScreen->Validate().IsSuccess();
         }
 
         return true;
@@ -241,7 +241,8 @@ namespace O3DE::ProjectManager
 
     void CreateProjectCtrl::CreateProject()
     {
-        if (m_newProjectSettingsScreen->Validate())
+        AZ::Outcome<void, QString> settingsValidation = m_newProjectSettingsScreen->Validate();
+        if (settingsValidation.IsSuccess())
         {
             if (!m_projectGemCatalogScreen->GetDownloadController()->IsDownloadQueueEmpty())
             {
@@ -279,8 +280,16 @@ namespace O3DE::ProjectManager
         }
         else
         {
-            QMessageBox::warning(
-                this, tr("Invalid project settings"), tr("Please correct the indicated project settings and try again."));
+            QString& errorMessage = settingsValidation.GetError();
+            if (errorMessage.isEmpty())
+            {
+                QMessageBox::warning(
+                    this, tr("Invalid project settings"), tr("Please correct the indicated project settings and try again."));
+            }
+            else
+            {
+                QMessageBox::warning(this, tr("Invalid project settings"), errorMessage);
+            }
         }
     }
 

+ 54 - 5
Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.cpp

@@ -104,7 +104,45 @@ namespace O3DE::ProjectManager
                     else if (button && button->property(k_addRemoteTemplateProperty).isValid())
                     {
                         AddRemoteTemplateDialog* addRemoteTemplateDialog = new AddRemoteTemplateDialog(this);
-                        addRemoteTemplateDialog->exec();
+                        if (addRemoteTemplateDialog->exec() == QDialog::DialogCode::Accepted)
+                        {
+                            auto remoteTemplatesResult =
+                                PythonBindingsInterface::Get()->GetProjectTemplatesForRepo(addRemoteTemplateDialog->GetRepoPath());
+                            if (remoteTemplatesResult.IsSuccess() && !remoteTemplatesResult.GetValue().isEmpty())
+                            {
+                                // remove remote template button from layout so we can insert the new templates before it
+                                m_templateFlowLayout->removeWidget(m_remoteTemplateButton);
+
+                                int currentTemplateIndex = m_templates.size();
+                                const QVector<ProjectTemplateInfo>& remoteTemplates = remoteTemplatesResult.GetValue();
+                                for (const ProjectTemplateInfo& remoteTemplate : remoteTemplates)
+                                {
+                                    m_templates.push_back(remoteTemplate);
+
+                                    // create template button
+                                    QString projectPreviewPath = QDir(remoteTemplate.m_path).filePath(ProjectPreviewImagePath);
+                                    QFileInfo doesPreviewExist(projectPreviewPath);
+                                    if (!doesPreviewExist.exists() || !doesPreviewExist.isFile())
+                                    {
+                                        projectPreviewPath = ":/DefaultTemplate.png";
+                                    }
+                                    TemplateButton* templateButton =
+                                        new TemplateButton(projectPreviewPath, remoteTemplate.m_displayName, this);
+                                    templateButton->SetIsRemote(remoteTemplate.m_isRemote);
+                                    templateButton->setCheckable(true);
+                                    templateButton->setProperty(k_templateIndexProperty, currentTemplateIndex);
+                                    templateButton->setProperty(k_templateNameProperty, remoteTemplate.m_name);
+
+                                    m_projectTemplateButtonGroup->addButton(templateButton);
+                                    m_templateFlowLayout->addWidget(templateButton);
+                                    m_templateButtons.append(templateButton);
+                                    ++currentTemplateIndex;
+                                }
+
+                                // add remote template button back to layout
+                                m_templateFlowLayout->addWidget(m_remoteTemplateButton);
+                            }
+                        }
                     }
                 });
 
@@ -259,10 +297,10 @@ namespace O3DE::ProjectManager
             }
 
             // Insert the add a remote template button
-            TemplateButton* remoteTemplateButton = new TemplateButton(":/DefaultTemplate.png", tr("Add remote Template"), this);
-            remoteTemplateButton->setProperty(k_addRemoteTemplateProperty, true);
-            m_projectTemplateButtonGroup->addButton(remoteTemplateButton);
-            m_templateFlowLayout->addWidget(remoteTemplateButton);
+            m_remoteTemplateButton = new TemplateButton(":/DefaultTemplate.png", tr("Add remote Template"), this);
+            m_remoteTemplateButton->setProperty(k_addRemoteTemplateProperty, true);
+            m_projectTemplateButtonGroup->addButton(m_remoteTemplateButton);
+            m_templateFlowLayout->addWidget(m_remoteTemplateButton);
 
             // Select the first project template (default selection).
             SelectProjectTemplate(0, /*blockSignals=*/true);
@@ -397,6 +435,17 @@ namespace O3DE::ProjectManager
             m_projectTemplateButtonGroup->blockSignals(false);
         }
     }
+
+    AZ::Outcome<void, QString> NewProjectSettingsScreen::Validate()
+    {
+        if (m_selectedTemplateIndex != -1 && m_templates[m_selectedTemplateIndex].m_isRemote)
+        {
+            return AZ::Failure<QString>(tr("You can not create a project with a template that has not been downloaded."));
+        }
+
+        return ProjectSettingsScreen::Validate();
+    }
+
     void NewProjectSettingsScreen::OnProjectNameUpdated()
     {
         if (ValidateProjectName() && !m_userChangedProjectPath)

+ 3 - 0
Code/Tools/ProjectManager/Source/NewProjectSettingsScreen.h

@@ -41,6 +41,8 @@ namespace O3DE::ProjectManager
 
         void SelectProjectTemplate(int index, bool blockSignals = false);
 
+        AZ::Outcome<void, QString> Validate() override;
+
     signals:
         void OnTemplateSelectionChanged(int oldIndex, int newIndex);
 
@@ -64,6 +66,7 @@ namespace O3DE::ProjectManager
         QLabel* m_templateDisplayName;
         QLabel* m_templateSummary;
         QPushButton* m_downloadTemplateButton;
+        TemplateButton* m_remoteTemplateButton = nullptr;
         TagContainerWidget* m_templateIncludedGems;
         QVector<ProjectTemplateInfo> m_templates;
         QVector<TemplateButton*> m_templateButtons;

+ 7 - 2
Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp

@@ -145,8 +145,13 @@ namespace O3DE::ProjectManager
         ValidateProjectName() && ValidateProjectPath();
     }
 
-    bool ProjectSettingsScreen::Validate()
+    AZ::Outcome<void, QString> ProjectSettingsScreen::Validate()
     {
-        return ValidateProjectName() && ValidateProjectPath();
+        if (ValidateProjectName() && ValidateProjectPath())
+        {
+            return AZ::Success();
+        }
+
+        return AZ::Failure<QString>("");
     }
 } // namespace O3DE::ProjectManager

+ 3 - 1
Code/Tools/ProjectManager/Source/ProjectSettingsScreen.h

@@ -10,6 +10,8 @@
 #if !defined(Q_MOC_RUN)
 #include <ProjectInfo.h>
 #include <ScreenWidget.h>
+
+#include <AzCore/Outcome/Outcome.h>
 #endif
 
 QT_FORWARD_DECLARE_CLASS(QHBoxLayout)
@@ -30,7 +32,7 @@ namespace O3DE::ProjectManager
 
         virtual ProjectInfo GetProjectInfo();
 
-        virtual bool Validate();
+        virtual AZ::Outcome<void, QString> Validate();
 
     protected slots:
         virtual void OnProjectNameUpdated();

+ 34 - 1
Code/Tools/ProjectManager/Source/PythonBindings.cpp

@@ -1294,6 +1294,40 @@ namespace O3DE::ProjectManager
         }
     }
 
+    AZ::Outcome<QVector<ProjectTemplateInfo>> PythonBindings::GetProjectTemplatesForRepo(const QString& repoUri)
+    {
+        QVector<ProjectTemplateInfo> templates;
+
+        bool result = ExecuteWithLock(
+            [&]
+            {
+                using namespace pybind11::literals;
+
+                auto templatePaths = m_repo.attr("get_template_json_paths_from_cached_repo")(
+                    "repo_uri"_a = QString_To_Py_String(repoUri)
+                    );
+
+                if (pybind11::isinstance<pybind11::set>(templatePaths))
+                {
+                    for (auto path : templatePaths)
+                    {
+                        ProjectTemplateInfo remoteTemplate = ProjectTemplateInfoFromPath(path);
+                        remoteTemplate.m_isRemote = true;
+                        templates.push_back(remoteTemplate);
+                    }
+                }
+            });
+
+        if (!result)
+        {
+            return AZ::Failure();
+        }
+        else
+        {
+            return AZ::Success(AZStd::move(templates));
+        }
+    }
+
     AZ::Outcome<QVector<ProjectTemplateInfo>> PythonBindings::GetProjectTemplatesForAllRepos()
     {
         QVector<ProjectTemplateInfo> templates;
@@ -1430,7 +1464,6 @@ namespace O3DE::ProjectManager
             try
             {
                 // required
-                gemRepoInfo.m_repoUri = Py_To_String(data["repo_uri"]);
                 gemRepoInfo.m_name = Py_To_String(data["repo_name"]);
                 gemRepoInfo.m_origin = Py_To_String(data["origin"]);
 

+ 1 - 0
Code/Tools/ProjectManager/Source/PythonBindings.h

@@ -84,6 +84,7 @@ namespace O3DE::ProjectManager
 
         // Templates
         AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates() override;
+        AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplatesForRepo(const QString& repoUri) override;
         AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplatesForAllRepos() override;
         AZ::Outcome<QVector<TemplateInfo>> GetGemTemplates() override;
 

+ 6 - 0
Code/Tools/ProjectManager/Source/PythonBindingsInterface.h

@@ -248,6 +248,12 @@ namespace O3DE::ProjectManager
          */
         virtual AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates() = 0;
 
+        /**
+         * Gathers all project templates for the given repo.
+         * @return An outcome with a list of all ProjectTemplateInfos from the given repo on success
+         */
+        virtual AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplatesForRepo(const QString& repoUri) = 0;
+
         /**
          * Gathers all project templates for all templates registered from repos.
          * @return An outcome with a list of all ProjectTemplateInfos on success

+ 7 - 2
Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.cpp

@@ -176,9 +176,14 @@ namespace O3DE::ProjectManager
         }
     }
 
-    bool UpdateProjectSettingsScreen::Validate()
+    AZ::Outcome<void, QString> UpdateProjectSettingsScreen::Validate()
     {
-        return ProjectSettingsScreen::Validate() && ValidateProjectPreview() && ValidateProjectId();
+        if (!(ValidateProjectPreview() && ValidateProjectId()))
+        {
+            return AZ::Failure<QString>("");
+        }
+
+        return ProjectSettingsScreen::Validate();
     }
 
     void UpdateProjectSettingsScreen::ResetProjectPreviewPath()

+ 1 - 1
Code/Tools/ProjectManager/Source/UpdateProjectSettingsScreen.h

@@ -29,7 +29,7 @@ namespace O3DE::ProjectManager
         ProjectInfo GetProjectInfo() override;
         void SetProjectInfo(const ProjectInfo& projectInfo);
 
-        bool Validate() override;
+        AZ::Outcome<void, QString> Validate() override;
 
         void ResetProjectPreviewPath();
 

+ 8 - 1
scripts/o3de/o3de/github_utils.py

@@ -65,6 +65,13 @@ class GitHubProvider(gitproviderinterface.GitProviderInterface):
 
 def get_github_provider(parsed_uri) -> GitHubProvider or None:
     if 'github.com' in parsed_uri.netloc:
-        return GitHubProvider
+        components = parsed_uri.path.split('/')
+        components = [ele for ele in components if ele.strip()]
+
+        if len(components) < 2:
+            return None
+
+        if components[1].endswith(".git"):
+            return GitHubProvider
 
     return None

+ 22 - 6
scripts/o3de/o3de/repo.py

@@ -295,28 +295,44 @@ def search_repo(manifest_json_data: dict,
                 gem_name: str = None,
                 template_name: str = None,
                 restricted_name: str = None) -> dict or None:
+    o3de_object_uris = []
     if isinstance(engine_name, str) or isinstance(engine_name, pathlib.PurePath):
-        o3de_object_uris = manifest_json_data['engines']
+        try:
+            o3de_object_uris = manifest_json_data['engines']
+        except KeyError:
+            pass
         manifest_json = 'engine.json'
         json_key = 'engine_name'
         search_func = lambda manifest_json_data: manifest_json_data if manifest_json_data.get(json_key, '') == engine_name else None
     elif isinstance(project_name, str) or isinstance(project_name, pathlib.PurePath):
-        o3de_object_uris = manifest_json_data['projects']
+        try:
+            o3de_object_uris = manifest_json_data['projects']
+        except KeyError:
+            pass
         manifest_json = 'project.json'
         json_key = 'project_name'
         search_func = lambda manifest_json_data: manifest_json_data if manifest_json_data.get(json_key, '') == project_name else None
     elif isinstance(gem_name, str) or isinstance(gem_name, pathlib.PurePath):
-        o3de_object_uris = manifest_json_data['gems']
+        try:
+            o3de_object_uris = manifest_json_data['gems']
+        except KeyError:
+            pass
         manifest_json = 'gem.json'
         json_key = 'gem_name'
         search_func = lambda manifest_json_data: manifest_json_data if manifest_json_data.get(json_key, '') == gem_name else None
     elif isinstance(template_name, str) or isinstance(template_name, pathlib.PurePath):
-        o3de_object_uris = manifest_json_data['templates']
+        try:
+            o3de_object_uris = manifest_json_data['templates']
+        except KeyError:
+            pass
         manifest_json = 'template.json'
         json_key = 'template_name'
         search_func = lambda manifest_json_data: manifest_json_data if manifest_json_data.get(json_key, '') == template_name else None
     elif isinstance(restricted_name, str) or isinstance(restricted_name, pathlib.PurePath):
-        o3de_object_uris = manifest_json_data['restricted']
+        try:
+            o3de_object_uris = manifest_json_data['restricted']
+        except KeyError:
+            pass
         manifest_json = 'restricted.json'
         json_key = 'restricted_name'
         search_func = lambda manifest_json_data: manifest_json_data if manifest_json_data.get(json_key, '') == restricted_name else None
@@ -345,7 +361,7 @@ def search_o3de_object(manifest_json, o3de_object_uris, search_func):
     for o3de_object_uri in o3de_object_uris:
         manifest_uri = f'{o3de_object_uri}/{manifest_json}'
         cache_file, _ = get_cache_file_uri(manifest_uri)
-        
+
         if cache_file.is_file():
             with cache_file.open('r') as f:
                 try: