ProjectExportWorker.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <ProjectExportWorker.h>
  9. #include <ProjectManagerDefs.h>
  10. #include <PythonBindingsInterface.h>
  11. #include <ProjectUtils.h>
  12. #include <QDir>
  13. #include <QFile>
  14. #include <QRegExp>
  15. #include <QProcess>
  16. #include <QProcessEnvironment>
  17. #include <QTextStream>
  18. #include <QThread>
  19. namespace O3DE::ProjectManager
  20. {
  21. ProjectExportWorker::ProjectExportWorker(const ProjectInfo& projectInfo)
  22. : QObject()
  23. , m_projectInfo(projectInfo)
  24. {
  25. }
  26. void ProjectExportWorker::ExportProject()
  27. {
  28. auto result = ExportProjectForPlatform();
  29. if (result.IsSuccess())
  30. {
  31. emit Done();
  32. }
  33. else
  34. {
  35. emit Done(result.GetError());
  36. }
  37. }
  38. AZ::Outcome<QString, QString> ProjectExportWorker::GetLogFilePath() const
  39. {
  40. QDir logFilePath(m_projectInfo.m_path);
  41. // Make directories if they aren't on disk
  42. if (!logFilePath.cd(ProjectBuildPathPostfix))
  43. {
  44. if (!logFilePath.mkpath(ProjectBuildPathPostfix))
  45. {
  46. QString errorMessage = QString("Unable to make log directory '%1' for project build path")
  47. .arg(logFilePath.absoluteFilePath(ProjectBuildPathPostfix).toUtf8().constData());
  48. return AZ::Failure(errorMessage);
  49. }
  50. logFilePath.cd(ProjectBuildPathPostfix);
  51. }
  52. if (!logFilePath.cd(ProjectBuildPathCmakeFiles))
  53. {
  54. if (!logFilePath.mkpath(ProjectBuildPathCmakeFiles))
  55. {
  56. QString errorMessage = QString("Unable to make log directory '%1' for cmake files path")
  57. .arg(logFilePath.absoluteFilePath(ProjectBuildPathCmakeFiles).toUtf8().constData());
  58. return AZ::Failure(errorMessage);
  59. }
  60. logFilePath.cd(ProjectBuildPathCmakeFiles);
  61. }
  62. return AZ::Success(logFilePath.filePath(ProjectExportErrorLogName));
  63. }
  64. AZ::Outcome<QString, QString> ProjectExportWorker::GetExpectedOutputPath() const
  65. {
  66. if (m_expectedOutputDir.isEmpty())
  67. {
  68. return AZ::Failure(tr("Project Export output folder not detected in the output logs."));
  69. }
  70. if (!QDir(m_expectedOutputDir).isAbsolute())
  71. {
  72. return AZ::Failure(tr("Project Export output folder %s is invalid.").arg(m_expectedOutputDir));
  73. }
  74. return AZ::Success(m_expectedOutputDir);
  75. }
  76. void ProjectExportWorker::QStringToAZTracePrint([[maybe_unused]] const QString& error)
  77. {
  78. AZ_Trace("Project Manager", error.toStdString().c_str());
  79. }
  80. AZ::Outcome<void, QString> ProjectExportWorker::ExportProjectForPlatform()
  81. {
  82. AZ::Outcome<QString, QString> logFilePathQuery = GetLogFilePath();
  83. if(!logFilePathQuery.IsSuccess())
  84. {
  85. QString errorMessage = QString("%1: %2").arg(tr(ErrorMessages::LogPathFailureMsg)).arg(logFilePathQuery.GetError());
  86. QStringToAZTracePrint(errorMessage);
  87. return AZ::Failure(errorMessage);
  88. }
  89. QString logFilePath = logFilePathQuery.GetValue();
  90. QFile logFile(logFilePath);
  91. if (!logFile.open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate))
  92. {
  93. QString errorMessage = QString("%1: %2").arg(tr(ErrorMessages::LogOpenFailureMsg)).arg(logFilePath);
  94. QStringToAZTracePrint(errorMessage);
  95. return AZ::Failure(errorMessage);
  96. }
  97. EngineInfo engineInfo;
  98. AZ::Outcome<EngineInfo> engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo();
  99. if (engineInfoResult.IsSuccess())
  100. {
  101. engineInfo = engineInfoResult.GetValue();
  102. }
  103. else
  104. {
  105. QString error = tr("Failed to get engine info.");
  106. QStringToAZTracePrint(error);
  107. return AZ::Failure(error);
  108. }
  109. QTextStream logStream(&logFile);
  110. UpdateProgress(tr("Setting Up Environment"));
  111. auto currentEnvironmentRequest = ProjectUtils::SetupCommandLineProcessEnvironment();
  112. if (!currentEnvironmentRequest.IsSuccess())
  113. {
  114. QStringToAZTracePrint(currentEnvironmentRequest.GetError());
  115. return currentEnvironmentRequest;
  116. }
  117. m_exportProjectProcess = new QProcess(this);
  118. m_exportProjectProcess->setProcessChannelMode(QProcess::MergedChannels);
  119. m_exportProjectProcess->setWorkingDirectory(m_projectInfo.m_path);
  120. QStringList exportArgs = {"export-project", "--export-script", m_projectInfo.m_currentExportScript,
  121. "--project-path", m_projectInfo.m_path};
  122. QString commandProgram = QDir(engineInfo.m_path).filePath(GetO3DECLIString());
  123. m_exportProjectProcess->start(commandProgram, exportArgs);
  124. if (!m_exportProjectProcess->waitForStarted())
  125. {
  126. QString error = tr("Exporting project failed to start.");
  127. QStringToAZTracePrint(error);
  128. return AZ::Failure(error);
  129. }
  130. QString exportOutput;
  131. while (m_exportProjectProcess->waitForReadyRead(MaxExportTimeMSecs))
  132. {
  133. exportOutput = m_exportProjectProcess->readAllStandardOutput();
  134. logStream << exportOutput;
  135. logStream.flush();
  136. // Show last line of output
  137. if (QStringList strs = exportOutput.split('\n', Qt::SkipEmptyParts);
  138. !strs.empty())
  139. {
  140. UpdateProgress(strs.last());
  141. }
  142. if (QThread::currentThread()->isInterruptionRequested())
  143. {
  144. // QProcess is unable to kill its child processes so we need to ask the operating system to do that for us
  145. AZ::Outcome<QStringList, QString> killProcessArgumentsResult = ConstructKillProcessCommandArguments(QString::number(m_exportProjectProcess->processId()));
  146. if (!killProcessArgumentsResult.IsSuccess())
  147. {
  148. return AZ::Failure(killProcessArgumentsResult.GetError());
  149. }
  150. auto killProcessArguments = killProcessArgumentsResult.GetValue();
  151. QProcess killExportProcess;
  152. killExportProcess.setProcessChannelMode(QProcess::MergedChannels);
  153. killExportProcess.start(killProcessArguments.front(), killProcessArguments.mid(1));
  154. bool finishedKilling = killExportProcess.waitForFinished();
  155. logStream << "Killing Project Export.";
  156. logStream << killExportProcess.readAllStandardOutput();
  157. if (!finishedKilling)
  158. {
  159. killExportProcess.kill();
  160. }
  161. m_exportProjectProcess->kill();
  162. logFile.close();
  163. QStringToAZTracePrint(ErrorMessages::ExportCancelled);
  164. return AZ::Failure(ErrorMessages::ExportCancelled);
  165. }
  166. }
  167. //check for edge case where a single wait cycle causes premature termination of the loop
  168. //force the interruption in this case
  169. if (m_exportProjectProcess->waitForReadyRead(MaxExportTimeMSecs))
  170. {
  171. // QProcess is unable to kill its child processes so we need to ask the operating system to do that for us
  172. auto killProcessArgumentsResult = ConstructKillProcessCommandArguments(QString::number(m_exportProjectProcess->processId()));
  173. if (!killProcessArgumentsResult.IsSuccess())
  174. {
  175. return AZ::Failure(killProcessArgumentsResult.GetError());
  176. }
  177. auto killProcessArguments = killProcessArgumentsResult.GetValue();
  178. QProcess killExportProcess;
  179. killExportProcess.setProcessChannelMode(QProcess::MergedChannels);
  180. killExportProcess.start(killProcessArguments.front(), killProcessArguments.mid(1));
  181. bool finishedKilling = killExportProcess.waitForFinished();
  182. if (!finishedKilling)
  183. {
  184. killExportProcess.kill();
  185. }
  186. logStream << "Project Export took too long to respond. Terminating...";
  187. logStream << killExportProcess.readAllStandardOutput();
  188. m_exportProjectProcess->kill();
  189. logFile.close();
  190. }
  191. if (m_exportProjectProcess->exitStatus() != QProcess::ExitStatus::NormalExit || m_exportProjectProcess->exitCode() != 0)
  192. {
  193. QString error = tr("Exporting project failed. See log for details. %1").arg(logFilePath);
  194. QStringToAZTracePrint(error);
  195. return AZ::Failure(error);
  196. }
  197. // Fetch the output directory from the logs if the process was successful
  198. // Use regex to collect that directory, and collect the slice of the word surrounded in quotes
  199. QRegExp quotedDirRegex("Project exported to '([^']*)'\\.");
  200. int pos = quotedDirRegex.indexIn(exportOutput);
  201. if (pos > -1)
  202. {
  203. m_expectedOutputDir = quotedDirRegex.cap(1);
  204. }
  205. return AZ::Success();
  206. }
  207. } // namespace O3DE::ProjectManager