3
0

start.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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. #
  9. """! O3DE DCCsi Wing Pro 8+ IDE start module
  10. There are severl ways this can be used:
  11. 1. From cli
  12. - open a cmd
  13. - change directory > cd c:\\path\\to\\DccScriptingInterface
  14. - run command > python Tools\\IDE\\Wing\\start.py
  15. 2. The O3DE editor uses this module to launch from menu and editor systems
  16. :file: DccScriptingInterface\\Tools\\IDE\\Wing\\start.py
  17. :Status: Prototype
  18. :Version: 0.0.1
  19. :Entrypoint: entrypoint, configures logging, includes cli
  20. :Notice:
  21. Currently windows only (not tested on other platforms)
  22. Currently only tested with Wing Pro 8 in Windows
  23. """
  24. # -------------------------------------------------------------------------
  25. import timeit
  26. _MODULE_START = timeit.default_timer() # start tracking
  27. # standard imports
  28. import sys
  29. import os
  30. import subprocess
  31. from pathlib import Path
  32. import logging as _logging
  33. # -------------------------------------------------------------------------
  34. # -------------------------------------------------------------------------
  35. # this is an entry point, we must self bootstrap
  36. _MODULE_PATH = Path(__file__)
  37. PATH_O3DE_TECHART_GEMS = _MODULE_PATH.parents[4].resolve()
  38. os.chdir(PATH_O3DE_TECHART_GEMS.as_posix())
  39. sys.path.insert(0, PATH_O3DE_TECHART_GEMS.as_posix())
  40. from DccScriptingInterface import add_site_dir
  41. add_site_dir(PATH_O3DE_TECHART_GEMS) # cleaner add
  42. # -------------------------------------------------------------------------
  43. # -------------------------------------------------------------------------
  44. # global scope
  45. from DccScriptingInterface.Tools.IDE.Wing import _PACKAGENAME
  46. _MODULENAME = f'{_PACKAGENAME}.start'
  47. # get the global dccsi state
  48. from DccScriptingInterface.globals import *
  49. from azpy.constants import FRMT_LOG_LONG
  50. _logging.basicConfig(level=_logging.DEBUG,
  51. format=FRMT_LOG_LONG,
  52. datefmt='%m-%d %H:%M')
  53. _LOGGER = _logging.getLogger(_MODULENAME)
  54. # auto-attach ide debugging at the earliest possible point in module
  55. if DCCSI_DEV_MODE:
  56. if DCCSI_GDEBUGGER == 'WING':
  57. import DccScriptingInterface.azpy.test.entry_test
  58. DccScriptingInterface.azpy.test.entry_test.connect_wing()
  59. elif DCCSI_GDEBUGGER == 'PYCHARM':
  60. _LOGGER.warning(f'{DCCSI_GDEBUGGER} debugger auto-attach not yet implemented')
  61. else:
  62. _LOGGER.warning(f'{DCCSI_GDEBUGGER} not a supported debugger')
  63. _LOGGER.debug(f'Initializing: {_MODULENAME}')
  64. _LOGGER.debug(f'_MODULE_PATH: {_MODULE_PATH.as_posix()}')
  65. # retreive the wing_config class object and it's settings
  66. from DccScriptingInterface.Tools.IDE.Wing.config import wing_config
  67. wing_config.settings.setenv() # ensure env is set
  68. # alternatively, you could get the settings via this wrapped method
  69. # settings = wing_config.get_settings(set_env=True)
  70. # or the standard dynaconf way, this will walk to find and exec config.py
  71. #from dynaconf import settings
  72. # if you want to directly work with settings
  73. # foo = wing_config.settings.WING_PROJ
  74. from DccScriptingInterface.azpy.config_utils import check_is_ascii
  75. # the subprocess call needs an environment
  76. # if we try to pass this, we get a failure to launch
  77. # env = wing_config.settings.as_dict()
  78. # error: "Fatal Python error: _Py_HashRandomization_Init: failed to get random numbers to initialize Python\nPython runtime state: preinitialized"
  79. #https://stackoverflow.com/questions/58997105/fatal-python-error-failed-to-get-random-numbers-to-initialize-python
  80. # another encountered problem is that the dynaconf settings dict may have
  81. # non-string Key:value pairs, which the subprocess env chokes on so we prune below
  82. # store a copy, so we can inspect/compare later
  83. orig_env = os.environ.copy()
  84. # we are going to pass the system environ
  85. wing_env = os.environ.copy()
  86. # generate a pruned env from dict that subprocess will not complain about
  87. # hopefully we don't loose anything vital (that isn't procedurally derived later)
  88. wing_env = {key: value for key, value in wing_env.items() if check_is_ascii(key) and check_is_ascii(value)}
  89. # if we are trying to start an app via script or from an app like o3de,
  90. # the environ will propogate Qt related envars like 'QT_PLUGIN_PATH'
  91. # Wing is a Qt5 application (as are some DCC tools) and this can cause a boot failure
  92. # in this case, the most straightforward approach is to just prune them
  93. wing_env = {key: value for key, value in wing_env.items() if not key.startswith("QT_")}
  94. if DCCSI_GDEBUG:
  95. # we can see what was pruned
  96. pruned = {k: wing_env[k] for k in set(wing_env) - set(orig_env)}
  97. if len(pruned.items()):
  98. _LOGGER.debug(f'prune diff is ...')
  99. for p in pruned.items():
  100. _LOGGER.debug(f'{p}')
  101. def popen(exe = wing_config.settings.WING_EXE,
  102. project_file = wing_config.settings.WING_PROJ):
  103. """Method to call, to start Wing IDE"""
  104. _LOGGER.debug(f'Attempting to start wing ...')
  105. wing_proc = subprocess.Popen([exe, project_file],
  106. env = wing_env,
  107. shell=True,
  108. stdout = subprocess.PIPE,
  109. stderr = subprocess.PIPE,
  110. close_fds=True)
  111. out, err = wing_proc.communicate()
  112. if wing_proc.returncode != 0:
  113. _LOGGER.error(f'Wing did not start ...')
  114. _LOGGER.error(f'{out}')
  115. _LOGGER.error(f'{err}')
  116. return None
  117. else:
  118. _LOGGER.info(f'Success: Wing started correctly!')
  119. return wing_proc
  120. # the above method works, so will deprecate this call version
  121. # should find out if a run variant is the prefered way to go py3?
  122. # def call(exe = wing_config.settings.WING_EXE,
  123. # project_file = wing_config.settings.WING_PROJ):
  124. # """Method to call, to start Wing IDE
  125. # """
  126. #
  127. # _LOGGER.debug(f'Attempting to start wing ...')
  128. #
  129. # try:
  130. # wing_proc = subprocess.call([exe, project_file],
  131. # env = wing_env,
  132. # shell=True,
  133. # close_fds=True)
  134. # except Exception as e:
  135. # _LOGGER.error(f'Wing did not start ...')
  136. # _LOGGER.error(f'{err}')
  137. # return None
  138. #
  139. # else:
  140. # _LOGGER.info(f'Success: Wing started correctly!')
  141. #
  142. # return wing_proc
  143. # --- END -----------------------------------------------------------------
  144. ###########################################################################
  145. # Main Code Block, runs this script as main (testing)
  146. # -------------------------------------------------------------------------
  147. if __name__ == '__main__':
  148. """Run this file as main (external commandline)"""
  149. _MODULENAME = f'{_MODULENAME}.cli'
  150. from DccScriptingInterface.globals import *
  151. from DccScriptingInterface.constants import STR_CROSSBAR
  152. from DccScriptingInterface.constants import FRMT_LOG_LONG
  153. # configure basic logger
  154. # note: not using a common logger to reduce cyclical imports
  155. _logging.basicConfig(level=DCCSI_LOGLEVEL,
  156. format=FRMT_LOG_LONG,
  157. datefmt='%m-%d %H:%M')
  158. _LOGGER = _logging.getLogger(_MODULENAME)
  159. # log global state to cli
  160. _LOGGER.debug(STR_CROSSBAR)
  161. _LOGGER.debug(f'_MODULENAME: {_MODULENAME}')
  162. _LOGGER.debug(f'{ENVAR_DCCSI_GDEBUG}: {wing_config.settings.DCCSI_GDEBUG}')
  163. _LOGGER.debug(f'{ENVAR_DCCSI_DEV_MODE}: {wing_config.settings.DCCSI_DEV_MODE}')
  164. _LOGGER.debug(f'{ENVAR_DCCSI_LOGLEVEL}: {wing_config.settings.DCCSI_LOGLEVEL}')
  165. # commandline interface
  166. import argparse
  167. parser = argparse.ArgumentParser(
  168. description=f'O3DE {_MODULENAME}',
  169. epilog=(f"Attempts to start Wing Pro {wing_config.settings.DCCSI_WING_VERSION_MAJOR}"
  170. "with the DCCsi and O3DE bootstrapping"))
  171. parser.add_argument('-gd', '--global-debug',
  172. type=bool,
  173. required=False,
  174. default=False,
  175. help='Enables global debug flag.')
  176. parser.add_argument('-ex', '--exit',
  177. type=bool,
  178. required=False,
  179. default=False,
  180. help='Exits python. Do not exit if you want to be in interactive interpreter after config')
  181. args = parser.parse_args()
  182. # easy overrides
  183. if args.global_debug:
  184. # you can modify/oerrive any setting simply by re-adding it with new values
  185. wing_config.add_setting(ENVAR_DCCSI_GDEBUG, True)
  186. # fetch modified settings and set the env
  187. settings = wing_config.get_settings(set_env=True)
  188. try:
  189. wing_proc = popen(wing_config.settings.WING_EXE, wing_config.settings.WING_PROJ)
  190. except Exception as e:
  191. _LOGGER.warning(f'Could not start Wing')
  192. _LOGGER.error(f'{e} , traceback =', exc_info=True)
  193. if DCCSI_STRICT:
  194. _LOGGER.exception(f'{e} , traceback =', exc_info=True)
  195. raise e
  196. # -- DONE ----
  197. _LOGGER.info(STR_CROSSBAR)
  198. _LOGGER.debug('{0} took: {1} sec'.format(_MODULENAME, timeit.default_timer() - _MODULE_START))
  199. if args.exit:
  200. import sys
  201. # return
  202. sys.exit()
  203. else:
  204. # custom prompt
  205. sys.ps1 = "[{}]>>".format(_MODULENAME)
  206. # --- END -----------------------------------------------------------------