diff --git a/Code/Tools/ProjectManager/Platform/Linux/PAL_linux_files.cmake b/Code/Tools/ProjectManager/Platform/Linux/PAL_linux_files.cmake index 4c94567793..e8085de555 100644 --- a/Code/Tools/ProjectManager/Platform/Linux/PAL_linux_files.cmake +++ b/Code/Tools/ProjectManager/Platform/Linux/PAL_linux_files.cmake @@ -7,4 +7,5 @@ set(FILES Python_linux.cpp + ProjectBuilderWorker_linux.cpp ) diff --git a/Code/Tools/ProjectManager/Platform/Linux/ProjectBuilderWorker_linux.cpp b/Code/Tools/ProjectManager/Platform/Linux/ProjectBuilderWorker_linux.cpp new file mode 100644 index 0000000000..71ade6a009 --- /dev/null +++ b/Code/Tools/ProjectManager/Platform/Linux/ProjectBuilderWorker_linux.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +namespace O3DE::ProjectManager +{ + AZ::Outcome ProjectBuilderWorker::BuildProjectForPlatform() + { + QString error = tr("Automatic building on Linux not currently supported!"); + QStringToAZTracePrint(error); + return AZ::Failure(error); + } + +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Platform/Mac/PAL_mac_files.cmake b/Code/Tools/ProjectManager/Platform/Mac/PAL_mac_files.cmake index f3ecbd9678..01d6b5f112 100644 --- a/Code/Tools/ProjectManager/Platform/Mac/PAL_mac_files.cmake +++ b/Code/Tools/ProjectManager/Platform/Mac/PAL_mac_files.cmake @@ -7,4 +7,5 @@ set(FILES Python_mac.cpp + ProjectBuilderWorker_mac.cpp ) diff --git a/Code/Tools/ProjectManager/Platform/Mac/ProjectBuilderWorker_mac.cpp b/Code/Tools/ProjectManager/Platform/Mac/ProjectBuilderWorker_mac.cpp new file mode 100644 index 0000000000..bb14bfea6b --- /dev/null +++ b/Code/Tools/ProjectManager/Platform/Mac/ProjectBuilderWorker_mac.cpp @@ -0,0 +1,19 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +namespace O3DE::ProjectManager +{ + AZ::Outcome ProjectBuilderWorker::BuildProjectForPlatform() + { + QString error = tr("Automatic building on MacOS not currently supported!"); + QStringToAZTracePrint(error); + return AZ::Failure(error); + } + +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Platform/Windows/PAL_windows_files.cmake b/Code/Tools/ProjectManager/Platform/Windows/PAL_windows_files.cmake index 44023b791b..eb45c61807 100644 --- a/Code/Tools/ProjectManager/Platform/Windows/PAL_windows_files.cmake +++ b/Code/Tools/ProjectManager/Platform/Windows/PAL_windows_files.cmake @@ -7,4 +7,5 @@ set(FILES Python_windows.cpp + ProjectBuilderWorker_windows.cpp ) diff --git a/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp b/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp new file mode 100644 index 0000000000..87a8a6c0ec --- /dev/null +++ b/Code/Tools/ProjectManager/Platform/Windows/ProjectBuilderWorker_windows.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace O3DE::ProjectManager +{ + AZ::Outcome ProjectBuilderWorker::BuildProjectForPlatform() + { + // Check if we are trying to cancel task + if (QThread::currentThread()->isInterruptionRequested()) + { + QStringToAZTracePrint(BuildCancelled); + return AZ::Failure(BuildCancelled); + } + + QFile logFile(GetLogFilePath()); + if (!logFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) + { + QString error = tr("Failed to open log file."); + QStringToAZTracePrint(error); + return AZ::Failure(error); + } + + EngineInfo engineInfo; + + AZ::Outcome engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo(); + if (engineInfoResult.IsSuccess()) + { + engineInfo = engineInfoResult.GetValue(); + } + else + { + QString error = tr("Failed to get engine info."); + QStringToAZTracePrint(error); + return AZ::Failure(error); + } + + QTextStream logStream(&logFile); + if (QThread::currentThread()->isInterruptionRequested()) + { + logFile.close(); + QStringToAZTracePrint(BuildCancelled); + return AZ::Failure(BuildCancelled); + } + + // Show some kind of progress with very approximate estimates + UpdateProgress(++m_progressEstimate); + + QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment()); + // Append cmake path to PATH incase it is missing + QDir cmakePath(engineInfo.m_path); + cmakePath.cd("cmake/runtime/bin"); + QString pathValue = currentEnvironment.value("PATH"); + pathValue += ";" + cmakePath.path(); + currentEnvironment.insert("PATH", pathValue); + + m_configProjectProcess = new QProcess(this); + m_configProjectProcess->setProcessChannelMode(QProcess::MergedChannels); + m_configProjectProcess->setWorkingDirectory(m_projectInfo.m_path); + m_configProjectProcess->setProcessEnvironment(currentEnvironment); + + m_configProjectProcess->start( + "cmake", + QStringList{ "-B", QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix), "-S", m_projectInfo.m_path, "-G", + "Visual Studio 16", "-DLY_3RDPARTY_PATH=" + engineInfo.m_thirdPartyPath }); + + if (!m_configProjectProcess->waitForStarted()) + { + QString error = tr("Configuring project failed to start."); + QStringToAZTracePrint(error); + return AZ::Failure(error); + } + bool containsGeneratingDone = false; + while (m_configProjectProcess->waitForReadyRead(MaxBuildTimeMSecs)) + { + QString configOutput = m_configProjectProcess->readAllStandardOutput(); + + if (configOutput.contains("Generating done")) + { + containsGeneratingDone = true; + } + + logStream << configOutput; + logStream.flush(); + + UpdateProgress(qMin(++m_progressEstimate, 19)); + + if (QThread::currentThread()->isInterruptionRequested()) + { + logFile.close(); + m_configProjectProcess->close(); + QStringToAZTracePrint(BuildCancelled); + return AZ::Failure(BuildCancelled); + } + } + + if (m_configProjectProcess->exitCode() != 0 || !containsGeneratingDone) + { + QString error = tr("Configuring project failed. See log for details."); + QStringToAZTracePrint(error); + return AZ::Failure(error); + } + + UpdateProgress(++m_progressEstimate); + + m_buildProjectProcess = new QProcess(this); + m_buildProjectProcess->setProcessChannelMode(QProcess::MergedChannels); + m_buildProjectProcess->setWorkingDirectory(m_projectInfo.m_path); + m_buildProjectProcess->setProcessEnvironment(currentEnvironment); + + m_buildProjectProcess->start( + "cmake", + QStringList{ "--build", QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix), "--target", + m_projectInfo.m_projectName + ".GameLauncher", "Editor", "--config", "profile" }); + + if (!m_buildProjectProcess->waitForStarted()) + { + QString error = tr("Building project failed to start."); + QStringToAZTracePrint(error); + return AZ::Failure(error); + } + + // There are a lot of steps when building so estimate around 800 more steps ((100 - 20) * 10) remaining + m_progressEstimate = 200; + while (m_buildProjectProcess->waitForReadyRead(MaxBuildTimeMSecs)) + { + logStream << m_buildProjectProcess->readAllStandardOutput(); + logStream.flush(); + + // Show 1% progress for every 10 steps completed + UpdateProgress(qMin(++m_progressEstimate / 10, 99)); + + if (QThread::currentThread()->isInterruptionRequested()) + { + // QProcess is unable to kill its child processes so we need to ask the operating system to do that for us + QProcess killBuildProcess; + killBuildProcess.setProcessChannelMode(QProcess::MergedChannels); + killBuildProcess.start( + "cmd.exe", QStringList{ "/C", "taskkill", "/pid", QString::number(m_buildProjectProcess->processId()), "/f", "/t" }); + killBuildProcess.waitForFinished(); + + logStream << "Killing Project Build."; + logStream << killBuildProcess.readAllStandardOutput(); + m_buildProjectProcess->kill(); + logFile.close(); + QStringToAZTracePrint(BuildCancelled); + return AZ::Failure(BuildCancelled); + } + } + + if (m_configProjectProcess->exitCode() != 0) + { + QString error = tr("Building project failed. See log for details."); + QStringToAZTracePrint(error); + return AZ::Failure(error); + } + + return AZ::Success(); + } + +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectBuilder.cpp b/Code/Tools/ProjectManager/Source/ProjectBuilder.cpp deleted file mode 100644 index a1dddf818c..0000000000 --- a/Code/Tools/ProjectManager/Source/ProjectBuilder.cpp +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - - -//#define MOCK_BUILD_PROJECT true - -namespace O3DE::ProjectManager -{ - // QProcess::waitForFinished uses -1 to indicate that the process should not timeout - constexpr int MaxBuildTimeMSecs = -1; - - ProjectBuilderWorker::ProjectBuilderWorker(const ProjectInfo& projectInfo) - : QObject() - , m_projectInfo(projectInfo) - { - } - - void ProjectBuilderWorker::BuildProject() - { -#ifdef MOCK_BUILD_PROJECT - for (int i = 0; i < 10; ++i) - { - QThread::sleep(1); - UpdateProgress(i * 10); - } - Done(m_projectPath); -#else - EngineInfo engineInfo; - - AZ::Outcome engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo(); - if (engineInfoResult.IsSuccess()) - { - engineInfo = engineInfoResult.GetValue(); - } - else - { - emit Done(tr("Failed to get engine info.")); - return; - } - - // Show some kind of progress with very approximate estimates - UpdateProgress(1); - - QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment()); - // Append cmake path to PATH incase it is missing - QDir cmakePath(engineInfo.m_path); - cmakePath.cd("cmake/runtime/bin"); - QString pathValue = currentEnvironment.value("PATH"); - pathValue += ";" + cmakePath.path(); - currentEnvironment.insert("PATH", pathValue); - - QProcess configProjectProcess; - configProjectProcess.setProcessChannelMode(QProcess::MergedChannels); - configProjectProcess.setWorkingDirectory(m_projectInfo.m_path); - configProjectProcess.setProcessEnvironment(currentEnvironment); - - configProjectProcess.start( - "cmake", - QStringList - { - "-B", - QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix), - "-S", - m_projectInfo.m_path, - "-G", - "Visual Studio 16", - "-DLY_3RDPARTY_PATH=" + engineInfo.m_thirdPartyPath - }); - - if (!configProjectProcess.waitForStarted()) - { - emit Done(tr("Configuring project failed to start.")); - return; - } - if (!configProjectProcess.waitForFinished(MaxBuildTimeMSecs)) - { - WriteErrorLog(configProjectProcess.readAllStandardOutput()); - emit Done(tr("Configuring project timed out. See log for details")); - return; - } - - QString configProjectOutput(configProjectProcess.readAllStandardOutput()); - if (configProjectProcess.exitCode() != 0 || !configProjectOutput.contains("Generating done")) - { - WriteErrorLog(configProjectOutput); - emit Done(tr("Configuring project failed. See log for details.")); - return; - } - - UpdateProgress(20); - - QProcess buildProjectProcess; - buildProjectProcess.setProcessChannelMode(QProcess::MergedChannels); - buildProjectProcess.setWorkingDirectory(m_projectInfo.m_path); - buildProjectProcess.setProcessEnvironment(currentEnvironment); - - buildProjectProcess.start( - "cmake", - QStringList - { - "--build", - QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix), - "--target", - m_projectInfo.m_projectName + ".GameLauncher", - "Editor", - "--config", - "profile" - }); - - if (!buildProjectProcess.waitForStarted()) - { - emit Done(tr("Building project failed to start.")); - return; - } - if (!buildProjectProcess.waitForFinished(MaxBuildTimeMSecs)) - { - WriteErrorLog(configProjectProcess.readAllStandardOutput()); - emit Done(tr("Building project timed out. See log for details")); - return; - } - - QString buildProjectOutput(buildProjectProcess.readAllStandardOutput()); - if (configProjectProcess.exitCode() != 0) - { - WriteErrorLog(buildProjectOutput); - emit Done(tr("Building project failed. See log for details.")); - } - else - { - emit Done(""); - } -#endif - } - - QString ProjectBuilderWorker::LogFilePath() const - { - QDir logFilePath(m_projectInfo.m_path); - logFilePath.cd(ProjectBuildPathPostfix); - return logFilePath.filePath(ProjectBuildErrorLogPathPostfix); - } - - void ProjectBuilderWorker::WriteErrorLog(const QString& log) - { - QFile logFile(LogFilePath()); - // Overwrite file with truncate - if (logFile.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) - { - QTextStream output(&logFile); - output << log; - logFile.close(); - } - } - - ProjectBuilderController::ProjectBuilderController(const ProjectInfo& projectInfo, ProjectButton* projectButton, QWidget* parent) - : QObject() - , m_projectInfo(projectInfo) - , m_projectButton(projectButton) - , m_parent(parent) - { - m_worker = new ProjectBuilderWorker(m_projectInfo); - m_worker->moveToThread(&m_workerThread); - - connect(&m_workerThread, &QThread::finished, m_worker, &ProjectBuilderWorker::deleteLater); - connect(&m_workerThread, &QThread::started, m_worker, &ProjectBuilderWorker::BuildProject); - connect(m_worker, &ProjectBuilderWorker::Done, this, &ProjectBuilderController::HandleResults); - connect(m_worker, &ProjectBuilderWorker::UpdateProgress, this, &ProjectBuilderController::UpdateUIProgress); - } - - ProjectBuilderController::~ProjectBuilderController() - { - m_workerThread.quit(); - m_workerThread.wait(); - } - - void ProjectBuilderController::Start() - { - m_workerThread.start(); - UpdateUIProgress(0); - } - - void ProjectBuilderController::SetProjectButton(ProjectButton* projectButton) - { - m_projectButton = projectButton; - } - - QString ProjectBuilderController::GetProjectPath() const - { - return m_projectInfo.m_path; - } - - void ProjectBuilderController::UpdateUIProgress(int progress) - { - if (m_projectButton) - { - m_projectButton->SetButtonOverlayText(QString("%1 (%2%)\n\n").arg(tr("Building Project..."), QString::number(progress))); - m_projectButton->SetProgressBarValue(progress); - } - } - - void ProjectBuilderController::HandleResults(const QString& result) - { - if (!result.isEmpty()) - { - if (result.contains(tr("log"))) - { - QMessageBox::StandardButton openLog = QMessageBox::critical( - m_parent, - tr("Project Failed to Build!"), - result + tr("\n\nWould you like to view log?"), - QMessageBox::No | QMessageBox::Yes); - - if (openLog == QMessageBox::Yes) - { - // Open application assigned to this file type - QDesktopServices::openUrl(QUrl("file:///" + m_worker->LogFilePath())); - } - } - else - { - QMessageBox::critical(m_parent, tr("Project Failed to Build!"), result); - } - } - - emit Done(); - } -} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectBuilderController.cpp b/Code/Tools/ProjectManager/Source/ProjectBuilderController.cpp new file mode 100644 index 0000000000..00e2e5d68d --- /dev/null +++ b/Code/Tools/ProjectManager/Source/ProjectBuilderController.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include + +#include +#include +#include + + +namespace O3DE::ProjectManager +{ + ProjectBuilderController::ProjectBuilderController(const ProjectInfo& projectInfo, ProjectButton* projectButton, QWidget* parent) + : QObject() + , m_projectInfo(projectInfo) + , m_projectButton(projectButton) + , m_lastProgress(0) + , m_parent(parent) + { + m_worker = new ProjectBuilderWorker(m_projectInfo); + m_worker->moveToThread(&m_workerThread); + + connect(&m_workerThread, &QThread::finished, m_worker, &ProjectBuilderWorker::deleteLater); + connect(&m_workerThread, &QThread::started, m_worker, &ProjectBuilderWorker::BuildProject); + connect(m_worker, &ProjectBuilderWorker::Done, this, &ProjectBuilderController::HandleResults); + connect(m_worker, &ProjectBuilderWorker::UpdateProgress, this, &ProjectBuilderController::UpdateUIProgress); + } + + ProjectBuilderController::~ProjectBuilderController() + { + m_workerThread.requestInterruption(); + m_workerThread.quit(); + m_workerThread.wait(); + } + + void ProjectBuilderController::Start() + { + m_workerThread.start(); + UpdateUIProgress(0); + } + + void ProjectBuilderController::SetProjectButton(ProjectButton* projectButton) + { + m_projectButton = projectButton; + + if (projectButton) + { + projectButton->SetProjectButtonAction(tr("Cancel Build"), [this] { HandleCancel(); }); + + if (m_lastProgress != 0) + { + UpdateUIProgress(m_lastProgress); + } + } + } + + const ProjectInfo& ProjectBuilderController::GetProjectInfo() const + { + return m_projectInfo; + } + + void ProjectBuilderController::UpdateUIProgress(int progress) + { + m_lastProgress = progress; + if (m_projectButton) + { + m_projectButton->SetButtonOverlayText(QString("%1 (%2%)\n\n").arg(tr("Building Project..."), QString::number(progress))); + m_projectButton->SetProgressBarValue(progress); + } + } + + void ProjectBuilderController::HandleResults(const QString& result) + { + if (!result.isEmpty()) + { + if (result.contains(tr("log"))) + { + QMessageBox::StandardButton openLog = QMessageBox::critical( + m_parent, + tr("Project Failed to Build!"), + result + tr("\n\nWould you like to view log?"), + QMessageBox::No | QMessageBox::Yes); + + if (openLog == QMessageBox::Yes) + { + // Open application assigned to this file type + QDesktopServices::openUrl(QUrl("file:///" + m_worker->GetLogFilePath())); + } + } + else + { + QMessageBox::critical(m_parent, tr("Project Failed to Build!"), result); + } + + emit Done(false); + return; + } + + emit Done(true); + } + + void ProjectBuilderController::HandleCancel() + { + m_workerThread.quit(); + emit Done(false); + } +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectBuilder.h b/Code/Tools/ProjectManager/Source/ProjectBuilderController.h similarity index 66% rename from Code/Tools/ProjectManager/Source/ProjectBuilder.h rename to Code/Tools/ProjectManager/Source/ProjectBuilderController.h index e6a539bb36..c3e3026b34 100644 --- a/Code/Tools/ProjectManager/Source/ProjectBuilder.h +++ b/Code/Tools/ProjectManager/Source/ProjectBuilderController.h @@ -12,32 +12,12 @@ #include #endif +QT_FORWARD_DECLARE_CLASS(QProcess) + namespace O3DE::ProjectManager { QT_FORWARD_DECLARE_CLASS(ProjectButton) - - class ProjectBuilderWorker : public QObject - { - Q_OBJECT - - public: - explicit ProjectBuilderWorker(const ProjectInfo& projectInfo); - ~ProjectBuilderWorker() = default; - - QString LogFilePath() const; - - public slots: - void BuildProject(); - - signals: - void UpdateProgress(int progress); - void Done(QString result); - - private: - void WriteErrorLog(const QString& log); - - ProjectInfo m_projectInfo; - }; + QT_FORWARD_DECLARE_CLASS(ProjectBuilderWorker) class ProjectBuilderController : public QObject { @@ -48,15 +28,16 @@ namespace O3DE::ProjectManager ~ProjectBuilderController(); void SetProjectButton(ProjectButton* projectButton); - QString GetProjectPath() const; + const ProjectInfo& GetProjectInfo() const; public slots: void Start(); void UpdateUIProgress(int progress); void HandleResults(const QString& result); + void HandleCancel(); signals: - void Done(); + void Done(bool success = true); private: ProjectInfo m_projectInfo; @@ -64,5 +45,7 @@ namespace O3DE::ProjectManager QThread m_workerThread; ProjectButton* m_projectButton; QWidget* m_parent; + + int m_lastProgress; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectBuilderWorker.cpp b/Code/Tools/ProjectManager/Source/ProjectBuilderWorker.cpp new file mode 100644 index 0000000000..7b255e1f46 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/ProjectBuilderWorker.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include + +#include + +//#define MOCK_BUILD_PROJECT true + +namespace O3DE::ProjectManager +{ + const QString ProjectBuilderWorker::BuildCancelled = ProjectBuilderWorker::tr("Build Cancelled."); + + ProjectBuilderWorker::ProjectBuilderWorker(const ProjectInfo& projectInfo) + : QObject() + , m_projectInfo(projectInfo) + , m_progressEstimate(0) + { + } + + void ProjectBuilderWorker::BuildProject() + { +#ifdef MOCK_BUILD_PROJECT + for (int i = 0; i < 10; ++i) + { + QThread::sleep(1); + UpdateProgress(i * 10); + } + Done(""); +#else + auto result = BuildProjectForPlatform(); + + if (result.IsSuccess()) + { + emit Done(); + } + else + { + emit Done(result.GetError()); + } +#endif + } + + QString ProjectBuilderWorker::GetLogFilePath() const + { + QDir logFilePath(m_projectInfo.m_path); + // Make directories if they aren't on disk + if (!logFilePath.cd(ProjectBuildPathPostfix)) + { + logFilePath.mkpath(ProjectBuildPathPostfix); + logFilePath.cd(ProjectBuildPathPostfix); + + } + if (!logFilePath.cd(ProjectBuildPathCmakeFiles)) + { + logFilePath.mkpath(ProjectBuildPathCmakeFiles); + logFilePath.cd(ProjectBuildPathCmakeFiles); + } + return logFilePath.filePath(ProjectBuildErrorLogName); + } + + void ProjectBuilderWorker::QStringToAZTracePrint(const QString& error) + { + AZ_TracePrintf("Project Manager", error.toStdString().c_str()); + } +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectBuilderWorker.h b/Code/Tools/ProjectManager/Source/ProjectBuilderWorker.h new file mode 100644 index 0000000000..0557b21831 --- /dev/null +++ b/Code/Tools/ProjectManager/Source/ProjectBuilderWorker.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ +#pragma once + +#if !defined(Q_MOC_RUN) +#include +#include + +#include +#endif + +QT_FORWARD_DECLARE_CLASS(QProcess) + +namespace O3DE::ProjectManager +{ + class ProjectBuilderWorker : public QObject + { + // QProcess::waitForFinished uses -1 to indicate that the process should not timeout + static constexpr int MaxBuildTimeMSecs = -1; + // Build was cancelled + static const QString BuildCancelled; + + Q_OBJECT + + public: + explicit ProjectBuilderWorker(const ProjectInfo& projectInfo); + ~ProjectBuilderWorker() = default; + + QString GetLogFilePath() const; + + public slots: + void BuildProject(); + + signals: + void UpdateProgress(int progress); + void Done(QString result = ""); + + private: + AZ::Outcome BuildProjectForPlatform(); + void QStringToAZTracePrint(const QString& error); + + QProcess* m_configProjectProcess = nullptr; + QProcess* m_buildProjectProcess = nullptr; + ProjectInfo m_projectInfo; + + int m_progressEstimate; + }; +} // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp index fef2639498..68a43b3b52 100644 --- a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.cpp @@ -40,8 +40,8 @@ namespace O3DE::ProjectManager m_overlayLabel->setVisible(false); vLayout->addWidget(m_overlayLabel); - m_buildButton = new QPushButton(tr("Build Project"), this); - m_buildButton->setVisible(false); + m_actionButton = new QPushButton(tr("Project Action"), this); + m_actionButton->setVisible(false); m_progressBar = new QProgressBar(this); m_progressBar->setObjectName("labelButtonProgressBar"); @@ -78,9 +78,9 @@ namespace O3DE::ProjectManager return m_progressBar; } - QPushButton* LabelButton::GetBuildButton() + QPushButton* LabelButton::GetActionButton() { - return m_buildButton; + return m_actionButton; } ProjectButton::ProjectButton(const ProjectInfo& projectInfo, QWidget* parent, bool processing) @@ -135,7 +135,6 @@ namespace O3DE::ProjectManager void ProjectButton::ProcessingSetup() { - m_projectImageLabel->GetOverlayLabel()->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); m_projectImageLabel->SetEnabled(false); m_projectImageLabel->SetOverlayText(tr("Processing...\n\n")); @@ -146,8 +145,6 @@ namespace O3DE::ProjectManager void ProjectButton::ReadySetup() { - connect(m_projectImageLabel->GetBuildButton(), &QPushButton::clicked, [this](){ emit BuildProject(m_projectInfo); }); - QMenu* menu = new QMenu(this); menu->addAction(tr("Edit Project Settings..."), this, [this]() { emit EditProject(m_projectInfo.m_path); }); menu->addAction(tr("Build"), this, [this]() { emit BuildProject(m_projectInfo); }); @@ -168,20 +165,40 @@ namespace O3DE::ProjectManager m_projectFooter->layout()->addWidget(projectMenuButton); } + void ProjectButton::SetProjectButtonAction(const QString& text, AZStd::function lambda) + { + QPushButton* projectActionButton = m_projectImageLabel->GetActionButton(); + if (!m_actionButtonConnection) + { + QSpacerItem* buttonSpacer = new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding); + m_projectImageLabel->layout()->addItem(buttonSpacer); + m_projectImageLabel->layout()->addWidget(projectActionButton); + projectActionButton->setVisible(true); + } + else + { + disconnect(m_actionButtonConnection); + } + + projectActionButton->setText(text); + m_actionButtonConnection = connect(projectActionButton, &QPushButton::clicked, lambda); + } + + void ProjectButton::SetProjectBuildButtonAction() + { + SetProjectButtonAction(tr("Build Project"), [this]() { emit BuildProject(m_projectInfo); }); + } + + void ProjectButton::BuildThisProject() + { + emit BuildProject(m_projectInfo); + } + void ProjectButton::SetLaunchButtonEnabled(bool enabled) { m_projectImageLabel->SetEnabled(enabled); } - void ProjectButton::ShowBuildButton(bool show) - { - QSpacerItem* buttonSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding); - - m_projectImageLabel->layout()->addItem(buttonSpacer); - m_projectImageLabel->layout()->addWidget(m_projectImageLabel->GetBuildButton()); - m_projectImageLabel->GetBuildButton()->setVisible(show); - } - void ProjectButton::SetButtonOverlayText(const QString& text) { m_projectImageLabel->SetOverlayText(text); @@ -191,4 +208,9 @@ namespace O3DE::ProjectManager { m_projectImageLabel->GetProgressBar()->setValue(progress); } + + LabelButton* ProjectButton::GetLabelButton() + { + return m_projectImageLabel; + } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h index fe2fd73324..b3b4b2bcf6 100644 --- a/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h +++ b/Code/Tools/ProjectManager/Source/ProjectButtonWidget.h @@ -9,12 +9,15 @@ #if !defined(Q_MOC_RUN) #include +#include #include +#include +#include +#include #endif QT_FORWARD_DECLARE_CLASS(QPixmap) -QT_FORWARD_DECLARE_CLASS(QPushButton) QT_FORWARD_DECLARE_CLASS(QAction) QT_FORWARD_DECLARE_CLASS(QProgressBar) @@ -34,7 +37,7 @@ namespace O3DE::ProjectManager QLabel* GetOverlayLabel(); QProgressBar* GetProgressBar(); - QPushButton* GetBuildButton(); + QPushButton* GetActionButton(); signals: void triggered(); @@ -45,7 +48,7 @@ namespace O3DE::ProjectManager private: QLabel* m_overlayLabel; QProgressBar* m_progressBar; - QPushButton* m_buildButton; + QPushButton* m_actionButton; bool m_enabled = true; }; @@ -58,10 +61,13 @@ namespace O3DE::ProjectManager explicit ProjectButton(const ProjectInfo& m_projectInfo, QWidget* parent = nullptr, bool processing = false); ~ProjectButton() = default; + void SetProjectButtonAction(const QString& text, AZStd::function lambda); + void SetProjectBuildButtonAction(); + void SetLaunchButtonEnabled(bool enabled); - void ShowBuildButton(bool show); void SetButtonOverlayText(const QString& text); void SetProgressBarValue(int progress); + LabelButton* GetLabelButton(); signals: void OpenProject(const QString& projectName); @@ -75,9 +81,12 @@ namespace O3DE::ProjectManager void BaseSetup(); void ProcessingSetup(); void ReadySetup(); + void BuildThisProject(); ProjectInfo m_projectInfo; LabelButton* m_projectImageLabel; QFrame* m_projectFooter; + + QMetaObject::Connection m_actionButtonConnection; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h b/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h index 0b1b3fba4e..f74233db51 100644 --- a/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h +++ b/Code/Tools/ProjectManager/Source/ProjectManagerDefs.h @@ -14,6 +14,7 @@ namespace O3DE::ProjectManager inline constexpr static int ProjectPreviewImageHeight = 280; static const QString ProjectBuildPathPostfix = "build/windows_vs2019"; - static const QString ProjectBuildErrorLogPathPostfix = "CMakeFiles/CMakeProjectBuildError.log"; + static const QString ProjectBuildPathCmakeFiles = "CMakeFiles"; + static const QString ProjectBuildErrorLogName = "CMakeProjectBuildError.log"; static const QString ProjectPreviewImagePath = "preview.png"; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp index 3639f786b4..cc3e386118 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include @@ -43,8 +43,6 @@ #include #include -//#define DISPLAY_PROJECT_DEV_DATA true - namespace O3DE::ProjectManager { ProjectsScreen::ProjectsScreen(QWidget* parent) @@ -164,43 +162,38 @@ namespace O3DE::ProjectManager projectsScrollArea->setWidget(scrollWidget); projectsScrollArea->setWidgetResizable(true); -#ifndef DISPLAY_PROJECT_DEV_DATA - // Iterate once to insert building project first - if (!buildProjectPath.isEmpty()) + QVector nonProcessingProjects; + buildProjectPath = QDir::fromNativeSeparators(buildProjectPath); + for (auto& project : projectsResult.GetValue()) { - buildProjectPath = QDir::fromNativeSeparators(buildProjectPath); - for (auto project : projectsResult.GetValue()) + if (projectButton && !*projectButton) { if (QDir::fromNativeSeparators(project.m_path) == buildProjectPath) { - ProjectButton* buildingProjectButton = CreateProjectButton(project, flowLayout, true); - - if (projectButton) - { - *projectButton = buildingProjectButton; - } - - break; + *projectButton = CreateProjectButton(project, flowLayout, true); + continue; } } + + nonProcessingProjects.append(project); } - for (auto project : projectsResult.GetValue()) -#else - ProjectInfo project = projectsResult.GetValue().at(0); - for (int i = 0; i < 15; i++) -#endif + for (auto& project : nonProcessingProjects) { - // Add all other projects skipping building project - // Safe if no building project because it is just an empty string - if (project.m_path != buildProjectPath) - { - ProjectButton* projectButtonWidget = CreateProjectButton(project, flowLayout); + ProjectButton* projectButtonWidget = CreateProjectButton(project, flowLayout); - if (RequiresBuildProjectIterator(project.m_path) != m_requiresBuild.end()) - { - projectButtonWidget->ShowBuildButton(true); - } + if (BuildQueueContainsProject(project.m_path)) + { + projectButtonWidget->SetProjectButtonAction(tr("Cancel Queued Build"), + [this, project] + { + UnqueueBuildProject(project); + SuggestBuildProjectMsg(project, false); + }); + } + else if (RequiresBuildProjectIterator(project.m_path) != m_requiresBuild.end()) + { + projectButtonWidget->SetProjectBuildButtonAction(); } } @@ -242,9 +235,9 @@ namespace O3DE::ProjectManager // Make sure to update builder with latest Project Button if (m_currentBuilder) { - ProjectButton* projectButtonPtr; + ProjectButton* projectButtonPtr = nullptr; - m_projectsContent = CreateProjectsContent(m_currentBuilder->GetProjectPath(), &projectButtonPtr); + m_projectsContent = CreateProjectsContent(m_currentBuilder->GetProjectInfo().m_path, &projectButtonPtr); m_currentBuilder->SetProjectButton(projectButtonPtr); } else @@ -412,17 +405,15 @@ namespace O3DE::ProjectManager } } - void ProjectsScreen::SuggestBuildProject(const ProjectInfo& projectInfo) + void ProjectsScreen::SuggestBuildProjectMsg(const ProjectInfo& projectInfo, bool showMessage) { - if (projectInfo.m_needsBuild) + if (RequiresBuildProjectIterator(projectInfo.m_path) == m_requiresBuild.end()) { - if (RequiresBuildProjectIterator(projectInfo.m_path) == m_requiresBuild.end()) - { - m_requiresBuild.append(projectInfo); - } - ResetProjectsContent(); + m_requiresBuild.append(projectInfo); } - else + ResetProjectsContent(); + + if (showMessage) { QMessageBox::information(this, tr("Project Should be rebuilt."), @@ -430,6 +421,11 @@ namespace O3DE::ProjectManager } } + void ProjectsScreen::SuggestBuildProject(const ProjectInfo& projectInfo) + { + SuggestBuildProjectMsg(projectInfo, true); + } + void ProjectsScreen::QueueBuildProject(const ProjectInfo& projectInfo) { auto requiredIter = RequiresBuildProjectIterator(projectInfo.m_path); @@ -443,14 +439,22 @@ namespace O3DE::ProjectManager if (m_buildQueue.empty() && !m_currentBuilder) { StartProjectBuild(projectInfo); + // Projects Content is already reset in fuction } else { m_buildQueue.append(projectInfo); + ResetProjectsContent(); } } } + void ProjectsScreen::UnqueueBuildProject(const ProjectInfo& projectInfo) + { + m_buildQueue.removeAll(projectInfo); + ResetProjectsContent(); + } + void ProjectsScreen::NotifyCurrentScreen() { if (ShouldDisplayFirstTimeContent()) @@ -481,7 +485,7 @@ namespace O3DE::ProjectManager return displayFirstTimeContent; } - void ProjectsScreen::StartProjectBuild(const ProjectInfo& projectInfo) + bool ProjectsScreen::StartProjectBuild(const ProjectInfo& projectInfo) { if (ProjectUtils::IsVS2019Installed()) { @@ -501,25 +505,42 @@ namespace O3DE::ProjectManager } else { - ProjectBuildDone(); + SuggestBuildProjectMsg(projectInfo, false); + return false; } + + return true; } + + return false; } - void ProjectsScreen::ProjectBuildDone() + void ProjectsScreen::ProjectBuildDone(bool success) { + ProjectInfo currentBuilderProject; + if (!success) + { + currentBuilderProject = m_currentBuilder->GetProjectInfo(); + } + delete m_currentBuilder; m_currentBuilder = nullptr; + if (!success) + { + SuggestBuildProjectMsg(currentBuilderProject, false); + } + if (!m_buildQueue.empty()) { - StartProjectBuild(m_buildQueue.front()); + while (!StartProjectBuild(m_buildQueue.front()) && m_buildQueue.size() > 1) + { + m_buildQueue.pop_front(); + } m_buildQueue.pop_front(); } - else - { - ResetProjectsContent(); - } + + ResetProjectsContent(); } QList::iterator ProjectsScreen::RequiresBuildProjectIterator(const QString& projectPath) diff --git a/Code/Tools/ProjectManager/Source/ProjectsScreen.h b/Code/Tools/ProjectManager/Source/ProjectsScreen.h index 12152e908b..1ca8448134 100644 --- a/Code/Tools/ProjectManager/Source/ProjectsScreen.h +++ b/Code/Tools/ProjectManager/Source/ProjectsScreen.h @@ -37,7 +37,7 @@ namespace O3DE::ProjectManager protected: void NotifyCurrentScreen() override; - void ProjectBuildDone(); + void SuggestBuildProjectMsg(const ProjectInfo& projectInfo, bool showMessage); protected slots: void HandleNewProjectButton(); @@ -50,6 +50,9 @@ namespace O3DE::ProjectManager void SuggestBuildProject(const ProjectInfo& projectInfo); void QueueBuildProject(const ProjectInfo& projectInfo); + void UnqueueBuildProject(const ProjectInfo& projectInfo); + + void ProjectBuildDone(bool success = true); void paintEvent(QPaintEvent* event) override; @@ -60,7 +63,7 @@ namespace O3DE::ProjectManager void ResetProjectsContent(); bool ShouldDisplayFirstTimeContent(); - void StartProjectBuild(const ProjectInfo& projectInfo); + bool StartProjectBuild(const ProjectInfo& projectInfo); QList::iterator RequiresBuildProjectIterator(const QString& projectPath); bool BuildQueueContainsProject(const QString& projectPath); bool WarnIfInBuildQueue(const QString& projectPath); diff --git a/Code/Tools/ProjectManager/project_manager_files.cmake b/Code/Tools/ProjectManager/project_manager_files.cmake index 0aa7a6c86c..a5632f082c 100644 --- a/Code/Tools/ProjectManager/project_manager_files.cmake +++ b/Code/Tools/ProjectManager/project_manager_files.cmake @@ -39,8 +39,10 @@ set(FILES Source/ProjectInfo.cpp Source/ProjectUtils.h Source/ProjectUtils.cpp - Source/ProjectBuilder.h - Source/ProjectBuilder.cpp + Source/ProjectBuilderWorker.h + Source/ProjectBuilderWorker.cpp + Source/ProjectBuilderController.h + Source/ProjectBuilderController.cpp Source/UpdateProjectSettingsScreen.h Source/UpdateProjectSettingsScreen.cpp Source/NewProjectSettingsScreen.h