Ver Fonte

Display error when unable to start Python

Added AzFramework Application, logging, unit tests
Alex Peterson há 4 anos atrás
pai
commit
ecded991b5
31 ficheiros alterados com 605 adições e 121 exclusões
  1. 7 0
      Code/Framework/AzCore/AzCore/Utils/Utils.cpp
  2. 3 0
      Code/Framework/AzCore/AzCore/Utils/Utils.h
  3. 1 1
      Code/Sandbox/Editor/CryEdit.cpp
  4. 58 5
      Code/Tools/ProjectManager/CMakeLists.txt
  5. 15 0
      Code/Tools/ProjectManager/Platform/Linux/PAL_linux_tests_files.cmake
  6. 15 0
      Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Test_Traits_Linux.h
  7. 15 0
      Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Test_Traits_Platform.h
  8. 15 0
      Code/Tools/ProjectManager/Platform/Mac/PAL_mac_tests_files.cmake
  9. 15 0
      Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Test_Traits_Mac.h
  10. 15 0
      Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Test_Traits_Platform.h
  11. 15 0
      Code/Tools/ProjectManager/Platform/Windows/PAL_windows_tests_files.cmake
  12. 15 0
      Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Test_Traits_Platform.h
  13. 15 0
      Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Test_Traits_Windows.h
  14. 5 0
      Code/Tools/ProjectManager/Resources/ProjectManager.qss
  15. 186 0
      Code/Tools/ProjectManager/Source/Application.cpp
  16. 48 0
      Code/Tools/ProjectManager/Source/Application.h
  17. 1 1
      Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp
  18. 1 1
      Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp
  19. 1 26
      Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp
  20. 2 6
      Code/Tools/ProjectManager/Source/ProjectManagerWindow.h
  21. 1 1
      Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp
  22. 2 2
      Code/Tools/ProjectManager/Source/ProjectsScreen.cpp
  23. 12 2
      Code/Tools/ProjectManager/Source/PythonBindings.cpp
  24. 4 0
      Code/Tools/ProjectManager/Source/PythonBindings.h
  25. 6 0
      Code/Tools/ProjectManager/Source/PythonBindingsInterface.h
  26. 13 72
      Code/Tools/ProjectManager/Source/main.cpp
  27. 17 0
      Code/Tools/ProjectManager/project_manager_app_files.cmake
  28. 3 4
      Code/Tools/ProjectManager/project_manager_files.cmake
  29. 17 0
      Code/Tools/ProjectManager/project_manager_tests_files.cmake
  30. 47 0
      Code/Tools/ProjectManager/tests/ApplicationTests.cpp
  31. 35 0
      Code/Tools/ProjectManager/tests/main.cpp

+ 7 - 0
Code/Framework/AzCore/AzCore/Utils/Utils.cpp

@@ -175,4 +175,11 @@ namespace AZ::Utils
         path /= ".o3de";
         return path.Native();
     }
+
+    AZ::IO::FixedMaxPathString GetO3deLogsDirectory()
+    {
+        AZ::IO::FixedMaxPath path = GetO3deManifestDirectory();
+        path /= "Logs";
+        return path.Native();
+    }
 }

+ 3 - 0
Code/Framework/AzCore/AzCore/Utils/Utils.h

@@ -97,6 +97,9 @@ namespace AZ
         //! Retrieves the full path where the manifest file lives, i.e. "<userhome>/.o3de/o3de_manifest.json"
         AZ::IO::FixedMaxPathString GetEngineManifestPath();
 
+        //! Retrieves the full directory to the O3DE logs directory, i.e. "<userhome>/.o3de/Logs"
+        AZ::IO::FixedMaxPathString GetO3deLogsDirectory();
+
         //! Retrieves the App root path to use on the current platform
         //! If the optional is not engaged the AppRootPath should be calculated based
         //! on the location of the bootstrap.cfg file

+ 1 - 1
Code/Sandbox/Editor/CryEdit.cpp

@@ -2891,7 +2891,7 @@ void CCryEditApp::OpenProjectManager(const AZStd::string& screen)
 {
     // provide the current project path for in case we want to update the project
     AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath();
-    const AZStd::string commandLineOptions = AZStd::string::format(" --screen %s --project_path %s", screen.c_str(), projectPath.c_str());
+    const AZStd::string commandLineOptions = AZStd::string::format(" --screen %s --project-path %s", screen.c_str(), projectPath.c_str());
     bool launchSuccess = AzFramework::ProjectManager::LaunchProjectManager(commandLineOptions);
     if (!launchSuccess)
     {

+ 58 - 5
Code/Tools/ProjectManager/CMakeLists.txt

@@ -20,12 +20,11 @@ if (NOT python_package_name)
     message(WARNING "Python was not found in the package assocation list.  Did someone call ly_associate_package(xxxxxxx Python) ?")
 endif()
 
+
 ly_add_target(
-    NAME ProjectManager APPLICATION
-    OUTPUT_NAME o3de
+    NAME ProjectManager.Static STATIC
     NAMESPACE AZ
     AUTOMOC
-    AUTORCC
     FILES_CMAKE
         project_manager_files.cmake
         Platform/${PAL_PLATFORM_NAME}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
@@ -47,6 +46,60 @@ ly_add_target(
             3rdParty::pybind11
             AZ::AzCore
             AZ::AzFramework
-            AZ::AzToolsFramework
             AZ::AzQtComponents
-)
+)
+
+ly_add_target(
+    NAME ProjectManager APPLICATION
+    OUTPUT_NAME o3de
+    NAMESPACE AZ
+    AUTORCC
+    FILES_CMAKE
+        project_manager_app_files.cmake
+    INCLUDE_DIRECTORIES
+        PRIVATE
+            Source
+    BUILD_DEPENDENCIES
+        PRIVATE
+            3rdParty::Qt::Core
+            3rdParty::Qt::Concurrent
+            3rdParty::Qt::Widgets
+            3rdParty::Python
+            3rdParty::pybind11
+            AZ::AzCore
+            AZ::AzFramework
+            AZ::AzQtComponents
+            AZ::ProjectManager.Static
+)
+
+if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
+    ly_add_target(
+        NAME ProjectManager.Tests EXECUTABLE
+        NAMESPACE AZ
+        AUTORCC
+        FILES_CMAKE
+            project_manager_tests_files.cmake
+            Platform/${PAL_PLATFORM_NAME}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}_tests_files.cmake
+        INCLUDE_DIRECTORIES
+            PRIVATE
+                Source
+                Platform/${PAL_PLATFORM_NAME}
+        BUILD_DEPENDENCIES
+            PRIVATE
+                3rdParty::Qt::Core
+                3rdParty::Qt::Concurrent
+                3rdParty::Qt::Widgets
+                3rdParty::Python
+                3rdParty::pybind11
+                AZ::AzTest
+                AZ::AzFramework
+                AZ::AzFrameworkTestShared
+                AZ::ProjectManager.Static
+    )
+
+    ly_add_googletest(
+        NAME AZ::ProjectManager.Tests
+        TEST_COMMAND $<TARGET_FILE:AZ::ProjectManager.Tests> --unittest
+    )
+
+endif()

+ 15 - 0
Code/Tools/ProjectManager/Platform/Linux/PAL_linux_tests_files.cmake

@@ -0,0 +1,15 @@
+#
+# 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.
+#
+
+set(FILES
+    ProjectManager_Test_Traits_Platform.h
+    ProjectManager_Test_Traits_Linux.h
+)

+ 15 - 0
Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Test_Traits_Linux.h

@@ -0,0 +1,15 @@
+/*
+ * 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
+
+#define AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS true

+ 15 - 0
Code/Tools/ProjectManager/Platform/Linux/ProjectManager_Test_Traits_Platform.h

@@ -0,0 +1,15 @@
+/*
+ * 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
+
+#include <ProjectManager_Test_Traits_Linux.h>

+ 15 - 0
Code/Tools/ProjectManager/Platform/Mac/PAL_mac_tests_files.cmake

@@ -0,0 +1,15 @@
+#
+# 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.
+#
+
+set(FILES
+    ProjectManager_Test_Traits_Platform.h
+    ProjectManager_Test_Traits_Mac.h
+)

+ 15 - 0
Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Test_Traits_Mac.h

@@ -0,0 +1,15 @@
+/*
+ * 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
+
+#define AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS false

+ 15 - 0
Code/Tools/ProjectManager/Platform/Mac/ProjectManager_Test_Traits_Platform.h

@@ -0,0 +1,15 @@
+/*
+ * 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
+
+#include <ProjectManager_Test_Traits_Mac.h>

+ 15 - 0
Code/Tools/ProjectManager/Platform/Windows/PAL_windows_tests_files.cmake

@@ -0,0 +1,15 @@
+#
+# 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.
+#
+
+set(FILES
+    ProjectManager_Test_Traits_Platform.h
+    ProjectManager_Test_Traits_Windows.h
+)

+ 15 - 0
Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Test_Traits_Platform.h

@@ -0,0 +1,15 @@
+/*
+ * 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
+
+#include <ProjectManager_Test_Traits_Windows.h>

+ 15 - 0
Code/Tools/ProjectManager/Platform/Windows/ProjectManager_Test_Traits_Windows.h

@@ -0,0 +1,15 @@
+/*
+ * 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
+
+#define AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS false

+ 5 - 0
Code/Tools/ProjectManager/Resources/ProjectManager.qss

@@ -7,6 +7,11 @@ QMainWindow {
     margin:0;
 }
 
+#ScreensCtrl {
+    min-width:1200px;
+    min-height:800px;
+}
+
 QPushButton:focus {
     outline: none;
     border:1px solid #1e70eb;

+ 186 - 0
Code/Tools/ProjectManager/Source/Application.cpp

@@ -0,0 +1,186 @@
+/*
+ * 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 <Application.h>
+#include <ProjectUtils.h>
+
+#include <AzCore/IO/FileIO.h>
+#include <AzCore/Utils/Utils.h>
+#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
+#include <AzFramework/Logging/LoggingComponent.h>
+#include <AzQtComponents/Utilities/HandleDpiAwareness.h>
+#include <AzQtComponents/Components/StyleManager.h>
+#include <AzQtComponents/Components/WindowDecorationWrapper.h>
+
+#include <QApplication>
+#include <QDir>
+#include <QMessageBox>
+
+namespace O3DE::ProjectManager
+{
+    Application::~Application()
+    {
+        TearDown();
+    }
+
+    bool Application::Init(bool interactive)
+    {
+        constexpr const char* applicationName { "O3DE" };
+
+        QApplication::setOrganizationName(applicationName);
+        QApplication::setOrganizationDomain("o3de.org");
+
+        QCoreApplication::setApplicationName(applicationName);
+        QCoreApplication::setApplicationVersion("1.0");
+
+        // Use the LogComponent for non-dev logging log
+        RegisterComponentDescriptor(AzFramework::LogComponent::CreateDescriptor());
+
+        // set the log alias to .o3de/Logs instead of the default user/logs
+        AZ::IO::FixedMaxPath path = AZ::Utils::GetO3deLogsDirectory();
+
+        // DevWriteStorage is where the event log is written during development
+        m_settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_DevWriteStorage, path.LexicallyNormal().Native());
+
+        // Save event logs to .o3de/Logs/eventlogger/EventLogO3DE.azsl
+        m_settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::BuildTargetNameKey, applicationName);
+
+        Start(AzFramework::Application::Descriptor());
+
+        QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+        QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+        QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
+
+        QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates));
+
+        QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
+        AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::SystemDpiAware);
+
+        // Create the actual Qt Application - this needs to happen before using QMessageBox
+        m_app.reset(new QApplication(*GetArgC(), *GetArgV()));
+
+        if(!InitLog(applicationName))
+        {
+            AZ_Warning("ProjectManager", false, "Failed to init logging");
+        }
+
+        m_pythonBindings = AZStd::make_unique<PythonBindings>(GetEngineRoot());
+        if (!m_pythonBindings || !m_pythonBindings->PythonStarted())
+        {
+            if (interactive)
+            {
+                QMessageBox::critical(nullptr, QObject::tr("Failed to start Python"),
+                    QObject::tr("This tool requires an O3DE engine with a Python runtime, "
+                        "but either Python is missing or mis-configured. Please rename "
+                        "your python/runtime folder to python/runtime_bak, then run "
+                        "python/get_python.bat to restore the Python runtime folder."));
+            }
+            return false;
+        }
+
+        const AZ::CommandLine* commandLine = GetCommandLine();
+        AZ_Assert(commandLine, "Failed to get command line");
+
+        ProjectManagerScreen startScreen = ProjectManagerScreen::Projects;
+        if (size_t screenSwitchCount = commandLine->GetNumSwitchValues("screen"); screenSwitchCount > 0)
+        {
+            QString screenOption = commandLine->GetSwitchValue("screen", screenSwitchCount - 1).c_str();
+            ProjectManagerScreen screen = ProjectUtils::GetProjectManagerScreen(screenOption);
+            if (screen != ProjectManagerScreen::Invalid)
+            {
+                startScreen = screen;
+            }
+        }
+
+        AZ::IO::FixedMaxPath projectPath;
+        if (size_t projectSwitchCount = commandLine->GetNumSwitchValues("project-path"); projectSwitchCount > 0)
+        {
+            projectPath = commandLine->GetSwitchValue("project-path", projectSwitchCount - 1).c_str();
+        }
+
+        m_mainWindow.reset(new ProjectManagerWindow(nullptr, projectPath, startScreen));
+
+        return true;
+    }
+
+    bool Application::InitLog(const char* logName)
+    {
+        if (!m_entity)
+        {
+            // override the log alias to the O3de Logs directory instead of the default project user/Logs folder
+            AZ::IO::FixedMaxPath path = AZ::Utils::GetO3deLogsDirectory();
+            AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
+            AZ_Assert(fileIO, "Failed to get FileIOBase instance");
+
+            fileIO->SetAlias("@log@", path.LexicallyNormal().Native().c_str());
+
+            // this entity exists because we need a home for LogComponent
+            // and cannot use the system entity because we need to be able to call SetLogFileBaseName 
+            // so the log will be named O3DE.log
+            m_entity = aznew AZ::Entity("Application Entity");
+            if (m_entity)
+            {
+                AzFramework::LogComponent* logger = aznew AzFramework::LogComponent();
+                AZ_Assert(logger, "Failed to create LogComponent");
+                logger->SetLogFileBaseName(logName);
+                m_entity->AddComponent(logger);
+                m_entity->Init();
+                m_entity->Activate();
+            }
+        }
+
+        return m_entity != nullptr;
+    }
+
+    void Application::TearDown()
+    {
+        if (m_entity)
+        {
+            m_entity->Deactivate();
+            delete m_entity;
+            m_entity = nullptr;
+        }
+
+        m_pythonBindings.reset();
+        m_mainWindow.reset();
+        m_app.reset();
+    }
+
+    bool Application::Run()
+    {
+        // Set up the Style Manager
+        AzQtComponents::StyleManager styleManager(qApp);
+        styleManager.initialize(qApp, GetEngineRoot());
+
+        // setup stylesheets and hot reloading 
+        AZ::IO::FixedMaxPath engineRoot(GetEngineRoot());
+        QDir rootDir(engineRoot.c_str());
+        const auto pathOnDisk = rootDir.absoluteFilePath("Code/Tools/ProjectManager/Resources");
+        const auto qrcPath = QStringLiteral(":/ProjectManager/style");
+        AzQtComponents::StyleManager::addSearchPaths("style", pathOnDisk, qrcPath, engineRoot);
+
+        // set stylesheet after creating the main window or their styles won't get updated
+        AzQtComponents::StyleManager::setStyleSheet(m_mainWindow.data(), QStringLiteral("style:ProjectManager.qss"));
+
+        // the decoration wrapper is intended to remember window positioning and sizing 
+        auto wrapper = new AzQtComponents::WindowDecorationWrapper();
+        wrapper->setGuest(m_mainWindow.data());
+        wrapper->show();
+        m_mainWindow->show();
+
+        qApp->setQuitOnLastWindowClosed(true);
+
+        // Run the application
+        return qApp->exec();
+    }
+
+}

+ 48 - 0
Code/Tools/ProjectManager/Source/Application.h

@@ -0,0 +1,48 @@
+/*
+ * 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 <AzFramework/Application/Application.h>
+#include <QCoreApplication>
+#include <PythonBindings.h>
+#include <ProjectManagerWindow.h>
+#endif
+
+namespace AZ
+{
+    class Entity;
+}
+
+namespace O3DE::ProjectManager
+{
+    class Application
+        : public AzFramework::Application
+    {
+    public:
+        using AzFramework::Application::Application;
+        virtual ~Application();
+
+        bool Init(bool interactive = true);
+        bool Run();
+        void TearDown();
+
+    private:
+        bool InitLog(const char* logName);
+
+        AZStd::unique_ptr<PythonBindings> m_pythonBindings;
+        QSharedPointer<QCoreApplication> m_app;
+        QSharedPointer<ProjectManagerWindow> m_mainWindow;
+
+        AZ::Entity* m_entity = nullptr;
+    };
+}

+ 1 - 1
Code/Tools/ProjectManager/Source/EngineSettingsScreen.cpp

@@ -25,7 +25,7 @@ namespace O3DE::ProjectManager
     EngineSettingsScreen::EngineSettingsScreen(QWidget* parent)
         : ScreenWidget(parent)
     {
-        auto* layout = new QVBoxLayout(this);
+        auto* layout = new QVBoxLayout();
         layout->setAlignment(Qt::AlignTop);
 
         setObjectName("engineSettingsScreen");

+ 1 - 1
Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp

@@ -33,7 +33,7 @@ namespace O3DE::ProjectManager
     {
         setObjectName("labelButton");
 
-        QVBoxLayout* vLayout = new QVBoxLayout(this);
+        QVBoxLayout* vLayout = new QVBoxLayout();
         vLayout->setContentsMargins(0, 0, 0, 0);
         vLayout->setSpacing(5);
 

+ 1 - 26
Code/Tools/ProjectManager/Source/ProjectManagerWindow.cpp

@@ -13,21 +13,11 @@
 #include <ProjectManagerWindow.h>
 #include <ScreensCtrl.h>
 
-#include <AzQtComponents/Components/StyleManager.h>
-#include <AzCore/IO/FileIO.h>
-#include <AzCore/IO/Path/Path.h>
-#include <AzFramework/CommandLine/CommandLine.h>
-#include <AzFramework/Application/Application.h>
-
-#include <QDir>
-
 namespace O3DE::ProjectManager
 {
-    ProjectManagerWindow::ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& engineRootPath, const AZ::IO::PathView& projectPath, ProjectManagerScreen startScreen)
+    ProjectManagerWindow::ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& projectPath, ProjectManagerScreen startScreen)
         : QMainWindow(parent)
     {
-        m_pythonBindings = AZStd::make_unique<PythonBindings>(engineRootPath);
-
         setWindowTitle(tr("O3DE Project Manager"));
 
         ScreensCtrl* screensCtrl = new ScreensCtrl();
@@ -44,15 +34,6 @@ namespace O3DE::ProjectManager
 
         setCentralWidget(screensCtrl);
 
-        // setup stylesheets and hot reloading 
-        QDir rootDir = QString::fromUtf8(engineRootPath.Native().data(), aznumeric_cast<int>(engineRootPath.Native().size()));
-        const auto pathOnDisk = rootDir.absoluteFilePath("Code/Tools/ProjectManager/Resources");
-        const auto qrcPath = QStringLiteral(":/ProjectManager/style");
-        AzQtComponents::StyleManager::addSearchPaths("style", pathOnDisk, qrcPath, engineRootPath);
-
-        // set stylesheet after creating the screens or their styles won't get updated
-        AzQtComponents::StyleManager::setStyleSheet(this, QStringLiteral("style:ProjectManager.qss"));
-
         // always push the projects screen first so we have something to come back to
         if (startScreen != ProjectManagerScreen::Projects)
         {
@@ -66,10 +47,4 @@ namespace O3DE::ProjectManager
             emit screensCtrl->NotifyCurrentProject(path);
         }
     }
-
-    ProjectManagerWindow::~ProjectManagerWindow()
-    {
-        m_pythonBindings.reset();
-    }
-
 } // namespace O3DE::ProjectManager

+ 2 - 6
Code/Tools/ProjectManager/Source/ProjectManagerWindow.h

@@ -13,7 +13,7 @@
 
 #if !defined(Q_MOC_RUN)
 #include <QMainWindow>
-#include <PythonBindings.h>
+#include <AzCore/IO/Path/Path.h>
 #include <ScreenDefs.h>
 #endif
 
@@ -25,12 +25,8 @@ namespace O3DE::ProjectManager
         Q_OBJECT
 
     public:
-        explicit ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& engineRootPath, const AZ::IO::PathView& projectPath,
+        explicit ProjectManagerWindow(QWidget* parent, const AZ::IO::PathView& projectPath,
             ProjectManagerScreen startScreen = ProjectManagerScreen::Projects);
-        ~ProjectManagerWindow();
-
-    private:
-        AZStd::unique_ptr<PythonBindings> m_pythonBindings;
     };
 
 } // namespace O3DE::ProjectManager

+ 1 - 1
Code/Tools/ProjectManager/Source/ProjectSettingsScreen.cpp

@@ -37,7 +37,7 @@ namespace O3DE::ProjectManager
         // if we don't set this in a frame (just use a sub-layout) all the content will align incorrectly horizontally
         QFrame* projectSettingsFrame = new QFrame(this);
         projectSettingsFrame->setObjectName("projectSettings");
-        m_verticalLayout = new QVBoxLayout(this);
+        m_verticalLayout = new QVBoxLayout();
 
         // you cannot remove content margins in qss
         m_verticalLayout->setContentsMargins(0, 0, 0, 0);

+ 2 - 2
Code/Tools/ProjectManager/Source/ProjectsScreen.cpp

@@ -85,7 +85,7 @@ namespace O3DE::ProjectManager
         QFrame* frame = new QFrame(this);
         frame->setObjectName("firstTimeContent");
         {
-            QVBoxLayout* layout = new QVBoxLayout(this);
+            QVBoxLayout* layout = new QVBoxLayout();
             layout->setContentsMargins(0, 0, 0, 0);
             layout->setAlignment(Qt::AlignTop);
             frame->setLayout(layout);
@@ -100,7 +100,7 @@ namespace O3DE::ProjectManager
                                    "available by downloading our sample project."));
             layout->addWidget(introLabel);
 
-            QHBoxLayout* buttonLayout = new QHBoxLayout(this);
+            QHBoxLayout* buttonLayout = new QHBoxLayout();
             buttonLayout->setAlignment(Qt::AlignLeft);
             buttonLayout->setSpacing(s_spacerSize);
 

+ 12 - 2
Code/Tools/ProjectManager/Source/PythonBindings.cpp

@@ -226,7 +226,7 @@ namespace O3DE::ProjectManager
     PythonBindings::PythonBindings(const AZ::IO::PathView& enginePath)
         : m_enginePath(enginePath)
     {
-        StartPython();
+        m_pythonStarted = StartPython();
     }
 
     PythonBindings::~PythonBindings()
@@ -234,6 +234,11 @@ namespace O3DE::ProjectManager
         StopPython();
     }
 
+    bool PythonBindings::PythonStarted()
+    {
+        return m_pythonStarted && Py_IsInitialized();
+    }
+
     bool PythonBindings::StartPython()
     {
         if (Py_IsInitialized())
@@ -246,7 +251,7 @@ namespace O3DE::ProjectManager
         AZStd::string pyBasePath = Platform::GetPythonHomePath(PY_PACKAGE, m_enginePath.c_str());
         if (!AZ::IO::SystemFile::Exists(pyBasePath.c_str()))
         {
-            AZ_Assert(false, "Python home path must exist. path:%s", pyBasePath.c_str());
+            AZ_Error("python", false, "Python home path does not exist: %s", pyBasePath.c_str());
             return false;
         }
 
@@ -351,6 +356,11 @@ namespace O3DE::ProjectManager
 
     AZ::Outcome<void, AZStd::string> PythonBindings::ExecuteWithLockErrorHandling(AZStd::function<void()> executionCallback)
     {
+        if (!Py_IsInitialized())
+        {
+            return AZ::Failure<AZStd::string>("Python is not initialized");
+        }
+
         AZStd::lock_guard<decltype(m_lock)> lock(m_lock);
         pybind11::gil_scoped_release release;
         pybind11::gil_scoped_acquire acquire;

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

@@ -34,6 +34,8 @@ namespace O3DE::ProjectManager
         ~PythonBindings() override;
 
         // PythonBindings overrides
+        bool PythonStarted() override;
+
         // Engine
         AZ::Outcome<EngineInfo> GetEngineInfo() override;
         bool SetEngineInfo(const EngineInfo& engineInfo) override;
@@ -70,6 +72,8 @@ namespace O3DE::ProjectManager
         bool StopPython();
 
 
+        bool m_pythonStarted = false;
+
         AZ::IO::FixedMaxPath m_enginePath;
         pybind11::handle m_engineTemplate;
         AZStd::recursive_mutex m_lock;

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

@@ -34,6 +34,12 @@ namespace O3DE::ProjectManager
         IPythonBindings() = default;
         virtual ~IPythonBindings() = default;
 
+        /**
+         * Get whether Python was started or not.  All Python functionality will fail if Python
+         * failed to start. 
+         * @return true if Python was started successfully, false on failure 
+         */
+        virtual bool PythonStarted() = 0;
 
         // Engine
 

+ 13 - 72
Code/Tools/ProjectManager/Source/main.cpp

@@ -10,85 +10,26 @@
 *
 */
 
-#include <AzQtComponents/Utilities/HandleDpiAwareness.h>
-#include <AzQtComponents/Components/StyleManager.h>
-#include <AzCore/Component/ComponentApplication.h>
-#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
-#include <AzCore/IO/Path/Path.h>
-#include <AzFramework/CommandLine/CommandLine.h>
-
-#include <ProjectManagerWindow.h>
-#include <ProjectUtils.h>
-
-#include <QApplication>
-#include <QCoreApplication>
-#include <QGuiApplication>
-
-using namespace O3DE::ProjectManager;
+#include <AzQtComponents/Utilities/QtPluginPaths.h>
+#include <Application.h>
 
 int main(int argc, char* argv[])
 {
-    QApplication::setOrganizationName("O3DE");
-    QApplication::setOrganizationDomain("o3de.org");
-    QCoreApplication::setApplicationName("ProjectManager");
-    QCoreApplication::setApplicationVersion("1.0");
-
-    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
-    QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
-    QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings);
-    QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
-    AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::SystemDpiAware);
-
-    AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
     int runSuccess = 0;
-    {
-        QApplication app(argc, argv);
-
-        // Need to use settings registry to get EngineRootFolder
-        AZ::IO::FixedMaxPath engineRootPath;
-        {
-            AZ::ComponentApplication componentApplication;
-            auto settingsRegistry = AZ::SettingsRegistry::Get();
-            settingsRegistry->Get(engineRootPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
-        }
-
-        AzQtComponents::StyleManager styleManager(&app);
-        styleManager.initialize(&app, engineRootPath);
 
-        // Get the initial start screen if one is provided via command line
-        constexpr char optionPrefix[] = "--";
-        AZ::CommandLine commandLine(optionPrefix);
-        commandLine.Parse(argc, argv);
+    // Call before using any Qt, or the app may not be able to locate Qt libs
+    AzQtComponents::PrepareQtPaths();
 
-        ProjectManagerScreen startScreen = ProjectManagerScreen::Projects;
-        if(commandLine.HasSwitch("screen"))
-        {
-            QString screenOption = commandLine.GetSwitchValue("screen", 0).c_str();
-            ProjectManagerScreen screen = ProjectUtils::GetProjectManagerScreen(screenOption);
-            if (screen != ProjectManagerScreen::Invalid)
-            {
-                startScreen = screen;
-            }
-        }
-
-        AZ::IO::FixedMaxPath projectPath;
-        if (commandLine.HasSwitch("project-path"))
-        {
-            projectPath = commandLine.GetSwitchValue("project-path", 0).c_str();
-        }
-
-        ProjectManagerWindow window(nullptr, engineRootPath, projectPath, startScreen);
-        window.show();
-
-        // somethings is preventing us from moving the window to the center of the
-        // primary screen - likely an Az style or component helper
-        constexpr int width = 1200;
-        constexpr int height = 800;
-        window.resize(width, height);
-
-        runSuccess = app.exec();
+    O3DE::ProjectManager::Application application(&argc, &argv);
+    if (!application.Init())
+    {
+        AZ_Error("ProjectManager", false, "Failed to initialize");
+        runSuccess = 1;
+    }
+    else
+    {
+        runSuccess = application.Run() ? 0 : 1;
     }
-    AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
 
     return runSuccess;
 }

+ 17 - 0
Code/Tools/ProjectManager/project_manager_app_files.cmake

@@ -0,0 +1,17 @@
+#
+# 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.
+#
+
+set(FILES
+    Resources/ProjectManager.rc
+    Resources/ProjectManager.qrc
+    Resources/ProjectManager.qss
+    Source/main.cpp
+)

+ 3 - 4
Code/Tools/ProjectManager/project_manager_files.cmake

@@ -1,4 +1,5 @@
 #
+#
 # All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
 # its licensors.
 #
@@ -10,10 +11,8 @@
 #
 
 set(FILES
-    Resources/ProjectManager.rc
-    Resources/ProjectManager.qrc
-    Resources/ProjectManager.qss
-    Source/main.cpp
+    Source/Application.h
+    Source/Application.cpp
     Source/ScreenDefs.h
     Source/ScreenFactory.h
     Source/ScreenFactory.cpp

+ 17 - 0
Code/Tools/ProjectManager/project_manager_tests_files.cmake

@@ -0,0 +1,17 @@
+#
+# 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.
+#
+
+set(FILES
+    Resources/ProjectManager.qrc
+    Resources/ProjectManager.qss
+    tests/ApplicationTests.cpp
+    tests/main.cpp
+)

+ 47 - 0
Code/Tools/ProjectManager/tests/ApplicationTests.cpp

@@ -0,0 +1,47 @@
+/*
+* 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 <AzCore/UnitTest/TestTypes.h>
+#include <AzCore/std/smart_ptr/make_shared.h>
+#include <Application.h>
+#include <ProjectManager_Test_Traits_Platform.h>
+
+namespace O3DE::ProjectManager
+{
+    class ProjectManagerApplicationTests 
+        : public ::UnitTest::ScopedAllocatorSetupFixture
+    {
+    public:
+
+        ProjectManagerApplicationTests()
+        {
+            m_application = AZStd::make_unique<ProjectManager::Application>();
+        }
+
+        ~ProjectManagerApplicationTests()
+        {
+            m_application.reset();
+        }
+
+        AZStd::unique_ptr<ProjectManager::Application> m_application;
+    };
+
+#if AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
+    TEST_F(ProjectManagerApplicationTests, DISABLED_Application_Init_Succeeds)
+#else
+    TEST_F(ProjectManagerApplicationTests, Application_Init_Succeeds)
+#endif // !AZ_TRAIT_DISABLE_FAILED_PROJECT_MANAGER_TESTS
+    {
+        // we don't want to interact with actual GUI or display it
+        EXPECT_TRUE(m_application->Init(/*interactive=*/false));
+    }
+}

+ 35 - 0
Code/Tools/ProjectManager/tests/main.cpp

@@ -0,0 +1,35 @@
+/*
+* 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 <AzTest/AzTest.h>
+
+DECLARE_AZ_UNIT_TEST_MAIN();
+
+int runDefaultRunner(int argc, char* argv[])
+{
+    INVOKE_AZ_UNIT_TEST_MAIN(nullptr)
+    return 0;
+}
+
+int main(int argc, char* argv[])
+{
+    if (argc == 1)
+    {
+        // if no parameters are provided, add the --unittests parameter
+        constexpr int defaultArgc = 2;
+        char unittest_arg[] = "--unittests"; // Conversion from string literal to char* is not allowed per ISO C++11
+        char* defaultArgv[defaultArgc] = { argv[0], unittest_arg };
+        return runDefaultRunner(defaultArgc, defaultArgv);
+    }
+    INVOKE_AZ_UNIT_TEST_MAIN(nullptr); 
+    return 0;
+}