Display error when unable to start Python

Added AzFramework Application, logging, unit tests
This commit is contained in:
Alex Peterson
2021-06-14 17:02:22 -07:00
committed by GitHub
parent 364187a87b
commit ecded991b5
31 changed files with 606 additions and 122 deletions
@@ -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();
}
}
@@ -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;
};
}
@@ -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");
@@ -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);
@@ -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
@@ -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
@@ -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);
@@ -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);
@@ -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;
@@ -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;
@@ -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
+14 -73
View File
@@ -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;
// Call before using any Qt, or the app may not be able to locate Qt libs
AzQtComponents::PrepareQtPaths();
O3DE::ProjectManager::Application application(&argc, &argv);
if (!application.Init())
{
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);
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();
AZ_Error("ProjectManager", false, "Failed to initialize");
runSuccess = 1;
}
else
{
runSuccess = application.Run() ? 0 : 1;
}
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
return runSuccess;
}