launcher.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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. SPDX-License-Identifier: Apache-2.0 OR MIT
  5. Linux compatible launcher
  6. """
  7. import logging
  8. import os
  9. import subprocess
  10. import ly_test_tools.environment.waiter
  11. import ly_test_tools.launchers.exceptions
  12. import ly_test_tools.environment.process_utils as process_utils
  13. from ly_test_tools.launchers.platforms.base import Launcher
  14. from ly_test_tools.launchers.exceptions import TeardownError, ProcessNotStartedError
  15. from tempfile import TemporaryFile
  16. log = logging.getLogger(__name__)
  17. class LinuxLauncher(Launcher):
  18. def __init__(self, build, args):
  19. super(LinuxLauncher, self).__init__(build, args)
  20. self._proc = None
  21. self._ret_code = None
  22. self._tmpout = None
  23. log.debug("Initialized Linux Launcher")
  24. def binary_path(self):
  25. """
  26. Return full path to the launcher for this build's configuration and project
  27. :return: full path to <project>.GameLauncher
  28. """
  29. assert self.workspace.project is not None
  30. return os.path.join(self.workspace.paths.build_directory(), f"{self.workspace.project}.GameLauncher")
  31. def setup(self, backupFiles=True, launch_ap=True, configure_settings=True):
  32. """
  33. Perform setup of this launcher, must be called before launching.
  34. Subclasses should call its parent's setup() before calling its own code, unless it changes configuration files
  35. :param backupFiles: Bool to backup setup files
  36. :param lauch_ap: Bool to lauch the asset processor
  37. :return: None
  38. """
  39. # Backup
  40. if backupFiles:
  41. self.backup_settings()
  42. # None reverts to function default
  43. if launch_ap is None:
  44. launch_ap = True
  45. # Modify and re-configure
  46. if configure_settings:
  47. self.configure_settings()
  48. super(LinuxLauncher, self).setup(backupFiles, launch_ap)
  49. def launch(self):
  50. """
  51. Launch the executable and track the subprocess
  52. :return: None
  53. """
  54. command = [self.binary_path()] + self.args
  55. self._tmpout = TemporaryFile()
  56. self._proc = subprocess.Popen(command, stdout=self._tmpout, stderr=self._tmpout, universal_newlines=True, env=process_utils.get_display_env())
  57. log.debug(f"Started Linux Launcher with command: {command}")
  58. def get_output(self, encoding="utf-8"):
  59. if self._tmpout is None:
  60. raise ProcessNotStartedError("Process must be started before retrieving output")
  61. self._tmpout.seek(0)
  62. return self._tmpout.read().decode(encoding)
  63. def teardown(self):
  64. """
  65. Perform teardown of this launcher, undoing actions taken by calling setup()
  66. Subclasses should call its parent's teardown() after performing its own teardown.
  67. :return: None
  68. """
  69. self.restore_settings()
  70. super(LinuxLauncher, self).teardown()
  71. def _kill(self):
  72. """
  73. This is a hard kill, and then wait to make sure until it actually ended.
  74. :return: None
  75. """
  76. if self._proc is not None:
  77. self._proc.kill()
  78. ly_test_tools.environment.waiter.wait_for(
  79. lambda: not self.is_alive(),
  80. exc=ly_test_tools.launchers.exceptions.TeardownError(
  81. f"Unable to terminate active Linux Launcher with process ID {self._proc.pid}")
  82. )
  83. self._proc = None
  84. self._ret_code = None
  85. log.debug("Linux Launcher terminated successfully")
  86. def is_alive(self):
  87. """
  88. Check the process to verify activity. Side effect of setting self.proc to None if it has ended.
  89. :return: None
  90. """
  91. if self._proc is None:
  92. return False
  93. else:
  94. if self._proc.poll() is not None:
  95. self._ret_code = self._proc.poll()
  96. self._proc = None
  97. return False
  98. return True
  99. def get_pid(self):
  100. # type: () -> int or None
  101. """
  102. Returns the pid of the launcher process if it exists, else it returns None
  103. :return: process id or None
  104. """
  105. if self._proc:
  106. return self._proc.pid
  107. return None
  108. def get_returncode(self):
  109. # type: () -> int or None
  110. """
  111. Returns the returncode of the launcher process if it exists, else return None.
  112. The returncode attribute is set when the process is terminated.
  113. :return: The returncode of the launcher's process
  114. """
  115. if self._proc:
  116. return self._proc.poll()
  117. else:
  118. return self._ret_code
  119. def check_returncode(self):
  120. # type: () -> None
  121. """
  122. Checks the return code of the launcher if it exists. Raises a CrashError if the returncode is non-zero. Returns
  123. None otherwise. This function must be called after exiting the launcher properly and NOT using its provided
  124. teardown(). Provided teardown() will always return a non-zero returncode and should not be checked.
  125. :return: None
  126. """
  127. return_code = self.get_returncode()
  128. if return_code != 0:
  129. log.error(f"Launcher exited with non-zero return code: {return_code}")
  130. raise ly_test_tools.launchers.exceptions.CrashError()
  131. return None
  132. def configure_settings(self):
  133. """
  134. Configures system level settings
  135. :return: None
  136. """
  137. # Update settings via the settings registry to avoid modifying the bootstrap.cfg
  138. host_ip = '127.0.0.1'
  139. self.args.append(f'--regset="/Amazon/AzCore/Bootstrap/project_path={self.workspace.paths.project()}"')
  140. self.args.append(f'--regset="/Amazon/AzCore/Bootstrap/remote_ip={host_ip}"')
  141. self.args.append(f'--regset="/Amazon/AzCore/Bootstrap/allowed_list={host_ip}"')
  142. self.args.append(f'--log_RemoteConsoleAllowedAddresses={host_ip}')
  143. self.args.append("--log_IncludeTime=1")
  144. class DedicatedLinuxLauncher(LinuxLauncher):
  145. def setup(self, backupFiles=True, launch_ap=False, configure_settings=True):
  146. """
  147. Perform setup of this launcher, must be called before launching.
  148. Subclasses should call its parent's setup() before calling its own code, unless it changes configuration files
  149. :param backupFiles: Bool to backup setup files
  150. :param launch_ap: Bool to launch the asset processor
  151. :param configure_settings: Bool to update settings caches
  152. :return: None
  153. """
  154. # Backup
  155. if backupFiles:
  156. self.backup_settings()
  157. # None reverts to function default
  158. if launch_ap is None:
  159. launch_ap = False
  160. super(DedicatedLinuxLauncher, self).setup(backupFiles, launch_ap, configure_settings)
  161. def binary_path(self):
  162. """
  163. Return full path to the dedicated server launcher for the build directory.
  164. :return: full path to <project>Launcher_Server
  165. """
  166. assert self.workspace.project is not None, (
  167. 'Project cannot be NoneType - please specify a project name string.')
  168. return os.path.join(f"{self.workspace.paths.build_directory()}",
  169. f"{self.workspace.project}.ServerLauncher")
  170. class LinuxEditor(LinuxLauncher):
  171. def __init__(self, build, args):
  172. super(LinuxEditor, self).__init__(build, args)
  173. self.args.append('--regset="/Amazon/Settings/EnableSourceControl=false"')
  174. self.args.append('--regset="/Amazon/AWS/Preferences/AWSAttributionConsentShown=true"')
  175. self.args.append('--regset="/Amazon/AWS/Preferences/AWSAttributionEnabled=false"')
  176. def binary_path(self):
  177. """
  178. Return full path to the Editor for this build's configuration and project
  179. :return: full path to Editor
  180. """
  181. assert self.workspace.project is not None
  182. return os.path.join(self.workspace.paths.build_directory(), "Editor")
  183. class LinuxAtomToolsLauncher(LinuxLauncher):
  184. def __init__(self, build, app_file_name, args=None):
  185. super(LinuxAtomToolsLauncher, self).__init__(build, args)
  186. self.app_file_name = app_file_name
  187. self.expected_executable_path = ""
  188. def binary_path(self):
  189. """
  190. Return full path to the Atom Tools application file for this build's configuration and project
  191. Relies on the build_directory() in self.workspace.paths to be accurate
  192. :return: full path to the given exe file
  193. """
  194. assert self.workspace.project is not None, (
  195. 'Project cannot be NoneType - please specify a project name string.')
  196. self.expected_executable_path = os.path.join(
  197. self.workspace.paths.build_directory(), f"{self.app_file_name}")
  198. if not os.path.isfile(self.expected_executable_path):
  199. raise ly_test_tools.launchers.exceptions.SetupError(
  200. f'Invalid application path supplied: {self.expected_executable_path}')
  201. return self.expected_executable_path