ProjectUtils_windows.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. /*
  2. * 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.
  3. *
  4. * SPDX-License-Identifier: Apache-2.0 OR MIT
  5. *
  6. */
  7. #include <ProjectUtils.h>
  8. #include <PythonBindingsInterface.h>
  9. #include <QDir>
  10. #include <QFileInfo>
  11. #include <QProcess>
  12. #include <QProcessEnvironment>
  13. #include <QStandardPaths>
  14. #include <AzCore/Settings/SettingsRegistryImpl.h>
  15. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  16. #include <AzCore/Utils/Utils.h>
  17. namespace O3DE::ProjectManager
  18. {
  19. namespace ProjectUtils
  20. {
  21. AZ::Outcome<void, QString> SetupCommandLineProcessEnvironment()
  22. {
  23. // Use the engine path to insert a path for cmake
  24. auto engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo();
  25. if (!engineInfoResult.IsSuccess())
  26. {
  27. return AZ::Failure(QObject::tr("Failed to get engine info"));
  28. }
  29. auto engineInfo = engineInfoResult.GetValue();
  30. // Append cmake path to the current environment PATH incase it is missing, since if
  31. // we are starting CMake itself the current application needs to find it using Path
  32. // This also takes affect for all child processes.
  33. QDir cmakePath(engineInfo.m_path);
  34. cmakePath.cd("cmake/runtime/bin");
  35. QString pathEnv = qEnvironmentVariable("Path");
  36. QStringList pathEnvList = pathEnv.split(";");
  37. if (!pathEnvList.contains(cmakePath.path()))
  38. {
  39. pathEnv += ";" + cmakePath.path();
  40. if (!qputenv("Path", pathEnv.toStdString().c_str()))
  41. {
  42. return AZ::Failure(QObject::tr("Failed to set Path environment variable"));
  43. }
  44. }
  45. return AZ::Success();
  46. }
  47. AZ::Outcome<QString, QString> FindSupportedCMake()
  48. {
  49. // Validate that cmake is installed and is in the path
  50. auto cmakeVersionQueryResult = ExecuteCommandResult("cmake", QStringList{ "--version" });
  51. if (!cmakeVersionQueryResult.IsSuccess())
  52. {
  53. return AZ::Failure(
  54. QObject::tr("CMake not found. \n\n"
  55. "Make sure that the minimum version of CMake is installed and available from the command prompt. "
  56. "Refer to the <a href='https://o3de.org/docs/welcome-guide/setup/requirements/#cmake'>O3DE "
  57. "requirements</a> for more information."));
  58. }
  59. return AZ::Success(QString{ ProjectCMakeCommand });
  60. }
  61. AZ::Outcome<QString, QString> FindSupportedCompilerForPlatform()
  62. {
  63. // Validate that cmake is installed
  64. auto cmakeProcessEnvResult = SetupCommandLineProcessEnvironment();
  65. if (!cmakeProcessEnvResult.IsSuccess())
  66. {
  67. return AZ::Failure(cmakeProcessEnvResult.GetError());
  68. }
  69. if (auto cmakeVersionQueryResult = FindSupportedCMake(); !cmakeVersionQueryResult.IsSuccess())
  70. {
  71. return cmakeVersionQueryResult;
  72. }
  73. // Validate that the minimal version of visual studio is installed
  74. QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
  75. QString programFilesPath = environment.value("ProgramFiles(x86)");
  76. QString vsWherePath = QDir(programFilesPath).filePath("Microsoft Visual Studio/Installer/vswhere.exe");
  77. QFileInfo vsWhereFile(vsWherePath);
  78. if (vsWhereFile.exists() && vsWhereFile.isFile())
  79. {
  80. QStringList vsWhereBaseArguments =
  81. QStringList{ "-version", "[16.11,18)", "-latest", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64" };
  82. QProcess vsWhereIsCompleteProcess;
  83. vsWhereIsCompleteProcess.setProcessChannelMode(QProcess::MergedChannels);
  84. vsWhereIsCompleteProcess.start(vsWherePath, vsWhereBaseArguments + QStringList{ "-property", "isComplete" });
  85. if (vsWhereIsCompleteProcess.waitForStarted() && vsWhereIsCompleteProcess.waitForFinished())
  86. {
  87. QString vsWhereIsCompleteOutput(vsWhereIsCompleteProcess.readAllStandardOutput());
  88. if (vsWhereIsCompleteOutput.startsWith("1"))
  89. {
  90. QProcess vsWhereCompilerVersionProcess;
  91. vsWhereCompilerVersionProcess.setProcessChannelMode(QProcess::MergedChannels);
  92. vsWhereCompilerVersionProcess.start(
  93. vsWherePath, vsWhereBaseArguments + QStringList{ "-property", "catalog_productDisplayVersion" });
  94. if (vsWhereCompilerVersionProcess.waitForStarted() && vsWhereCompilerVersionProcess.waitForFinished())
  95. {
  96. QString vsWhereCompilerVersionOutput(vsWhereCompilerVersionProcess.readAllStandardOutput());
  97. return AZ::Success(vsWhereCompilerVersionOutput);
  98. }
  99. }
  100. }
  101. }
  102. return AZ::Failure(
  103. QObject::tr("Visual Studio 2019 version 16.11 or higher or Visual Studio 2022 version 17.0 or higher not found.<br><br>"
  104. "A compatible version of Visual Studio is required to build this project.<br>"
  105. "Refer to the <a href='https://o3de.org/docs/welcome-guide/requirements/#microsoft-visual-studio'>Visual "
  106. "Studio requirements</a> for more information."));
  107. }
  108. AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath)
  109. {
  110. AZ::Outcome processEnvResult = SetupCommandLineProcessEnvironment();
  111. if (!processEnvResult.IsSuccess())
  112. {
  113. return AZ::Failure(processEnvResult.GetError());
  114. }
  115. QString projectBuildPath = QDir(projectPath).filePath(ProjectBuildPathPostfix);
  116. AZ::Outcome projectBuildPathResult = GetProjectBuildPath(projectPath);
  117. if (projectBuildPathResult.IsSuccess())
  118. {
  119. projectBuildPath = projectBuildPathResult.GetValue();
  120. }
  121. QProcess process;
  122. // if the project build path is relative, it should be relative to the project path
  123. process.setWorkingDirectory(projectPath);
  124. process.setProgram("cmake-gui");
  125. process.setArguments({ "-S", projectPath, "-B", projectBuildPath });
  126. if(!process.startDetached())
  127. {
  128. return AZ::Failure(QObject::tr("Failed to start CMake GUI"));
  129. }
  130. return AZ::Success();
  131. }
  132. AZ::Outcome<QString, QString> RunGetPythonScript(const QString& engineRoot)
  133. {
  134. const QString batPath = QString("%1/python/get_python.bat").arg(engineRoot);
  135. return ExecuteCommandResultModalDialog(
  136. "cmd.exe",
  137. QStringList{"/c", batPath},
  138. QObject::tr("Running get_python script..."));
  139. }
  140. AZ::IO::FixedMaxPath GetEditorExecutablePath(const AZ::IO::PathView& projectPath)
  141. {
  142. AZ::IO::FixedMaxPath editorPath;
  143. AZ::IO::FixedMaxPath fixedProjectPath{ projectPath };
  144. // First attempt to launch the Editor.exe within the project build directory if it exists
  145. AZ::IO::FixedMaxPath buildPathSetregPath = fixedProjectPath
  146. / AZ::SettingsRegistryInterface::DevUserRegistryFolder
  147. / "Platform" / AZ_TRAIT_OS_PLATFORM_CODENAME / "build_path.setreg";
  148. if (AZ::IO::SystemFile::Exists(buildPathSetregPath.c_str()))
  149. {
  150. AZ::SettingsRegistryImpl settingsRegistry;
  151. // Merge the build_path.setreg into the local SettingsRegistry instance
  152. if (AZ::IO::FixedMaxPath projectBuildPath;
  153. settingsRegistry.MergeSettingsFile(buildPathSetregPath.Native(),
  154. AZ::SettingsRegistryInterface::Format::JsonMergePatch)
  155. && settingsRegistry.Get(projectBuildPath.Native(), AZ::SettingsRegistryMergeUtils::ProjectBuildPath))
  156. {
  157. // local Settings Registry will be used to merge the build_path.setreg for the supplied projectPath
  158. AZ::IO::FixedMaxPath buildConfigurationPath = (fixedProjectPath / projectBuildPath).LexicallyNormal();
  159. // First try <project-build-path>/bin/$<CONFIG> and if that path doesn't exist
  160. // try <project-build-path>/bin/$<PLATFORM>/$<CONFIG>
  161. buildConfigurationPath /= "bin";
  162. AZStd::fixed_vector<AZ::IO::FixedMaxPath, 4> paths = {
  163. buildConfigurationPath / AZ_BUILD_CONFIGURATION_TYPE / "Editor",
  164. buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / AZ_BUILD_CONFIGURATION_TYPE / "Editor"
  165. };
  166. // always try profile config because that is the default
  167. if (strcmp(AZ_BUILD_CONFIGURATION_TYPE, "profile") != 0)
  168. {
  169. paths.emplace_back(buildConfigurationPath / "profile" / "Editor");
  170. paths.emplace_back(buildConfigurationPath / AZ_TRAIT_OS_PLATFORM_CODENAME / "profile" / "Editor");
  171. }
  172. for (auto& path : paths)
  173. {
  174. if(AZ::IO::SystemFile::Exists(path.ReplaceExtension(AZ_TRAIT_OS_EXECUTABLE_EXTENSION).c_str()))
  175. {
  176. return path;
  177. }
  178. }
  179. }
  180. }
  181. // No Editor executable was found in the project build folder so if this project uses a
  182. // different engine we must find the Editor executable for that engine
  183. if(auto engineResult = PythonBindingsInterface::Get()->GetProjectEngine(projectPath.Native().data()); engineResult)
  184. {
  185. auto engineInfo = engineResult.GetValue<EngineInfo>();
  186. if (!engineInfo.m_thisEngine)
  187. {
  188. AZ::IO::FixedMaxPath fixedEnginePath{ engineInfo.m_path.toUtf8().constData() };
  189. // try the default sdk path
  190. // in the future we may be able to use additional .setreg entries to locate an alternate binary path
  191. if (editorPath = (fixedEnginePath / "bin" / AZ_TRAIT_OS_PLATFORM_CODENAME / "profile" / "Default" / "Editor").
  192. ReplaceExtension(AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
  193. AZ::IO::SystemFile::Exists(editorPath.c_str()))
  194. {
  195. return editorPath;
  196. }
  197. return {};
  198. }
  199. }
  200. // Fall back to checking if an Editor exists in O3DE executable directory
  201. editorPath = AZ::IO::FixedMaxPath(AZ::Utils::GetExecutableDirectory()) / "Editor";
  202. editorPath.ReplaceExtension(AZ_TRAIT_OS_EXECUTABLE_EXTENSION);
  203. if (AZ::IO::SystemFile::Exists(editorPath.c_str()))
  204. {
  205. return editorPath;
  206. }
  207. return {};
  208. }
  209. AZ::Outcome<QString, QString> CreateDesktopShortcut(const QString& filename, const QString& targetPath, const QStringList& arguments)
  210. {
  211. const QString cmd{"powershell.exe"};
  212. const QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
  213. const QString shortcutPath = QString("%1/%2.lnk").arg(desktopPath).arg(filename);
  214. const QString arg = QString("$s=(New-Object -COM WScript.Shell).CreateShortcut('%1');$s.TargetPath='%2';$s.Arguments='%3';$s.Save();")
  215. .arg(shortcutPath)
  216. .arg(targetPath)
  217. .arg(arguments.join(' '));
  218. auto createShortcutResult = ExecuteCommandResult(cmd, QStringList{"-Command", arg});
  219. if (!createShortcutResult.IsSuccess())
  220. {
  221. return AZ::Failure(QObject::tr("Failed to create desktop shortcut %1 <br><br>"
  222. "Please verify you have permission to create files at the specified location.<br><br> %2")
  223. .arg(shortcutPath)
  224. .arg(createShortcutResult.GetError()));
  225. }
  226. return AZ::Success(QObject::tr("A desktop shortcut has been successfully created.<br>You can view the file <a href=\"%1\">here</a>.").arg(desktopPath));
  227. }
  228. } // namespace ProjectUtils
  229. } // namespace O3DE::ProjectManager