config_utils.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. # coding:utf-8
  2. #!/usr/bin/python
  3. #
  4. # Copyright (c) Contributors to the Open 3D Engine Project.
  5. # For complete copyright and license terms please see the LICENSE at the root of this distribution.
  6. #
  7. # SPDX-License-Identifier: Apache-2.0 OR MIT
  8. #
  9. # -------------------------------------------------------------------------
  10. """! This module is part of the O3DE DccScriptingInterface Gem
  11. This module is a set of utils related to config.py, it hase several methods
  12. that can fullfil discovery of paths for use in standing up a synthetic env.
  13. This is particularly useful when the config is used outside of O3DE,
  14. in an external standalone tool with PySide2(Qt). For example, these paths
  15. are discoverable so that we can synthetically derive code access to various
  16. aspects of O3DE outside of the executables.
  17. :notice: In a future PR this module needs to be refactored
  18. - if can be written entirely for py3, using Pathlib, etc.
  19. return_stub_dir() :discover path by walking from module to file stub
  20. get_stub_check_path() :discover by walking from known path to file stub
  21. get_o3de_engine_root() :combines multiple methods to discover engine root
  22. get_o3de_build_path() :searches for the build path using file stub
  23. get_dccsi_config() :convenience method to get the dccsi config
  24. get_current_project_cfg() :will be depricated (don't use)
  25. get_check_global_project() :get global project path from user .o3de data
  26. get_o3de_project_path() :get the project path while within editor
  27. bootstrap_dccsi_py_libs() :extends code access (mainly used in Maya py27)
  28. """
  29. # --------------------------------------------------------------------------
  30. # Now that we no longer need to support py2.7 this module can be improved.
  31. import timeit
  32. _MODULE_START = timeit.default_timer() # start tracking
  33. import sys
  34. import os
  35. import re
  36. import site
  37. from os.path import expanduser
  38. import logging as _logging
  39. # note: this module originally took into account py2.7 not having pathlib
  40. # and other modules, we no longer intend to write code to support py2.7
  41. # to do: modernize this modules code to py3.7+ with pathlib
  42. # these are new imports since then, this note is left here as a reminder
  43. # for a future PR to refactor
  44. import pathlib
  45. from pathlib import Path
  46. import importlib.util
  47. # --------------------------------------------------------------------------
  48. # --------------------------------------------------------------------------
  49. # Global Scope
  50. from DccScriptingInterface.azpy import _PACKAGENAME
  51. _MODULENAME = f'{_PACKAGENAME}.config_utils'
  52. _LOGGER = _logging.getLogger(_MODULENAME)
  53. _LOGGER.debug('Initializing: {}.'.format({_MODULENAME}))
  54. __all__ = ['check_is_ascii',
  55. 'attach_debugger',
  56. 'get_os',
  57. 'return_stub_dir',
  58. 'get_stub_check_path',
  59. 'get_o3de_engine_root',
  60. 'get_o3de_build_path',
  61. 'get_dccsi_config',
  62. 'get_current_project_cfg',
  63. 'get_check_global_project',
  64. 'get_o3de_project_path',
  65. 'bootstrap_dccsi_py_libs']
  66. # dccsi site/code access
  67. _MODULE_PATH = Path(__file__) # To Do: what if frozen?
  68. # note: this module is called in other root modules
  69. # this module avoids cyclical imports by not importing azpy.constants
  70. # to do: cleanup and maybe remove these from azpy.constants?
  71. # CONSTANTS
  72. ENVAR_DCCSI_GDEBUG = 'DCCSI_GDEBUG'
  73. ENVAR_DCCSI_DEV_MODE = 'DCCSI_DEV_MODE'
  74. ENVAR_DCCSI_LOGLEVEL = 'DCCSI_LOGLEVEL'
  75. ENVAR_DCCSI_GDEBUGGER = 'DCCSI_GDEBUGGER'
  76. # turn all of these off/on for local testing
  77. _DCCSI_GDEBUG = False
  78. _DCCSI_DEV_MODE = False
  79. _DCCSI_LOGLEVEL = _logging.INFO
  80. _DCCSI_GDEBUGGER = 'WING'
  81. # Notice: The above ^ in a future refactor should probably attempt to use the following
  82. # # this accesses common global state, e.g. DCCSI_GDEBUG (is True or False)
  83. # from DccScriptingInterface.globals import *
  84. FRMT_LOG_LONG = "[%(name)s][%(levelname)s] >> %(message)s (%(asctime)s; %(filename)s:%(lineno)d)"
  85. # this is the DCCsi envar used for discovering the engine path (if set)
  86. ENVAR_O3DE_DEV = 'O3DE_DEV'
  87. # this is a known file at the root of the engine folder we can use as marker
  88. STUB_O3DE_DEV = 'engine.json'
  89. # this is the DCCsi envar used for discovering the project path (if set)
  90. ENVAR_PATH_O3DE_PROJECT = 'PATH_O3DE_PROJECT'
  91. # python related envars and paths
  92. STR_PATH_DCCSI_PYTHON_LIB = '{0}\\3rdParty\\Python\\Lib\\{1}.x\\{1}.{2}.x\\site-packages'
  93. PATH_USER_HOME = expanduser("~")
  94. STR_CROSSBAR = str('{0}'.format('-' * 74))
  95. # --------------------------------------------------------------------------
  96. # --------------------------------------------------------------------------
  97. # we don't have access yet to the DCCsi Lib\site-packages
  98. # (1) this will give us import access to azpy (always?)
  99. # we know where the dccsi root should be from here
  100. _PATH_DCCSIG = Path(_MODULE_PATH, '../../..').resolve()
  101. # it can be set or overrriden by dev with envar
  102. _PATH_DCCSIG = Path(os.getenv('PATH_DCCSIG', _PATH_DCCSIG)).resolve()
  103. # ^ we assume this config is in the root of the DCCsi
  104. # if it's not, be sure to set envar 'PATH_DCCSIG' to ensure it
  105. site.addsitedir(_PATH_DCCSIG.as_posix()) # must be done for azpy
  106. # -------------------------------------------------------------------------
  107. # -------------------------------------------------------------------------
  108. # notice: this can be removed in a future refactor, this was some early
  109. # test inspection when the framework was being stood up.
  110. # just a quick check to ensure what paths have code access
  111. if _DCCSI_GDEBUG:
  112. known_paths = list()
  113. for p in sys.path:
  114. known_paths.append(p)
  115. _LOGGER.debug(known_paths)
  116. # -------------------------------------------------------------------------
  117. # -------------------------------------------------------------------------
  118. # notice: this block can be removed in a future refactor
  119. # this import can fail in Maya 2020 (and earlier) stuck on py2.7
  120. # wrapped in a try, to trap and providing messaging to help user correct
  121. # to do: deprecate this code block when Maya boostrapping is refactored
  122. try:
  123. from pathlib import Path # note: we provide this in py2.7
  124. # so using it here suggests some boostrapping has occured before using azpy
  125. except Exception as e:
  126. _LOGGER.warning('Maya 2020 and below, use py2.7')
  127. _LOGGER.warning('py2.7 does not include pathlib')
  128. _LOGGER.warning('Try installing the O3DE DCCsi py2.7 requirements.txt')
  129. _LOGGER.warning("See instructions: 'C:\\< your o3de engine >\\Gems\\AtomLyIntegration\\TechnicalArt\\DccScriptingInterface\\SDK\Maya\\readme.txt'")
  130. _LOGGER.warning("Other code in this module with fail!!!")
  131. _LOGGER.error(e)
  132. pass # fail gracefully, note: code accesing Path will fail!
  133. # -------------------------------------------------------------------------
  134. # -------------------------------------------------------------------------
  135. def check_is_ascii(value):
  136. """checks that passed value is ascii str"""
  137. try:
  138. value.encode('ascii')
  139. return True
  140. except (AttributeError, UnicodeEncodeError):
  141. return False
  142. # -------------------------------------------------------------------------
  143. # -------------------------------------------------------------------------
  144. # to do: move to a azpy.dev module with plugins for multiple IDEs
  145. def attach_debugger(debugger_type=_DCCSI_GDEBUGGER):
  146. """! This method will attempt to attach the WING debugger
  147. :param debugger_type: set the debugger ide type (currently only WING)
  148. To Do: other IDEs for debugging not yet implemented.
  149. This method should be replaced with a plugin based dev package.
  150. """
  151. # notice: This is a temporary method, in a future refactor this
  152. # would be replaced with a azpy.dev.debugger module
  153. # using a pluggy pattern, to support plugins for multiple ides
  154. # we only support wing right now
  155. # the default version is Wing Pro 8 (others not tested)
  156. # WINGHOME defaults to Wing Pro 8.x (other versions not tested)
  157. # Assumes the default install path: "C:\Program Files (x86)\Wing Pro WING_VERSION_MAJOR"
  158. # WING_APPDATA defaults to:
  159. # "C:\Users\[Your User Name]\AppData\Roaming\Wing Pro WING_VERSION_MAJOR"
  160. # after installing wing, copy the WINGHOME\wingdbstub.py file to WING_APPDATA
  161. # Open the WING_APPDATA\wingdbstub.py file and modify line 96 to
  162. #kEmbedded = 1
  163. # or alternatively you can manually edit the copy in the ide install location.
  164. # such as: "WINGHOME": "C:\Program Files (x86)\Wing Pro 8\wingdbstub.py"
  165. if debugger_type == 'WING':
  166. from azpy.test.entry_test import connect_wing
  167. _debugger = connect_wing()
  168. else:
  169. _LOGGER.warning(f'Debugger type: {debugger_type}, is Not Implemented!')
  170. return _debugger
  171. # -------------------------------------------------------------------------
  172. # -------------------------------------------------------------------------
  173. # this block is related to .o3de data
  174. # os.path.expanduser("~") returns different values in py2.7 vs 3
  175. # Note: py27 support will be deprecated in the future
  176. _LOGGER.debug('user home: {}'.format(PATH_USER_HOME))
  177. # special case, make sure didn't return <user>\documents
  178. user_home_parts = os.path.split(PATH_USER_HOME)
  179. if str(user_home_parts[1].lower()) == 'documents':
  180. PATH_USER_HOME = user_home_parts[0]
  181. _LOGGER.debug('user home CORRECTED: {}'.format(PATH_USER_HOME))
  182. # the global project may be defined in the registry
  183. PATH_USER_O3DE = Path(PATH_USER_HOME, '.o3de')
  184. PATH_USER_O3DE_REGISTRY = Path(PATH_USER_O3DE, 'Registry')
  185. PATH_USER_O3DE_BOOTSTRAP = Path(PATH_USER_O3DE_REGISTRY, 'bootstrap.setreg')
  186. # -------------------------------------------------------------------------
  187. # -------------------------------------------------------------------------
  188. # first define all the methods for the module
  189. def get_os():
  190. """returns lumberyard dir names used in python path"""
  191. if sys.platform.startswith('win'):
  192. os_folder = "windows"
  193. elif sys.platform.startswith('darwin'):
  194. os_folder = "mac"
  195. elif sys.platform.startswith('linux'):
  196. os_folder = "linux_x64"
  197. else:
  198. message = str("DCCsi.azpy.config_utils.py: "
  199. "Unexpectedly executing on operating system '{}'"
  200. "".format(sys.platform))
  201. raise RuntimeError(message)
  202. return os_folder
  203. # -------------------------------------------------------------------------
  204. # -------------------------------------------------------------------------
  205. # from azpy.core import get_datadir
  206. # there was a method here refactored out to add py2.7 support for Maya 2020
  207. #"DccScriptingInterface\azpy\core\py2\utils.py get_datadir()"
  208. #"DccScriptingInterface\azpy\core\py3\utils.py get_datadir()"
  209. # Warning: planning to deprecate py2 support
  210. # -------------------------------------------------------------------------
  211. # -------------------------------------------------------------------------
  212. def return_stub_dir(stub_file='dccsi_stub'):
  213. '''discover and return path by walking from module to file stub
  214. Input: a file name (stub_file)
  215. Output: returns the directory of the file (stub_file)'''
  216. _dir_to_last_file = None
  217. # To Do: refactor to use pathlib object oriented Path
  218. if _dir_to_last_file is None:
  219. path = os.path.abspath(__file__)
  220. while 1:
  221. path, tail = os.path.split(path)
  222. if (os.path.isfile(os.path.join(path, stub_file))):
  223. break
  224. if (len(tail) == 0):
  225. path = ""
  226. _LOGGER.debug('I was not able to find the path to that file '
  227. '({}) in a walk-up from currnet path'
  228. ''.format(stub_file))
  229. break
  230. _dir_to_last_file = path
  231. return _dir_to_last_file
  232. # --------------------------------------------------------------------------
  233. # -------------------------------------------------------------------------
  234. def get_stub_check_path(in_path=os.getcwd(), check_stub=STUB_O3DE_DEV):
  235. '''
  236. Returns the branch root directory of the dev\\'engine.json'
  237. (... or you can pass it another known stub) so we can safely build
  238. relative filepaths within that branch.
  239. Input: a starting directory, default is os.getcwd()
  240. Input: a file name stub (to search for)
  241. Output: a path (the stubs parent directory)
  242. If the stub is not found, it returns None
  243. '''
  244. path = Path(in_path).absolute()
  245. while 1:
  246. test_path = Path(path, check_stub)
  247. if test_path.is_file():
  248. return Path(test_path).parent
  249. else:
  250. path, tail = (path.parent, path.name)
  251. if (len(tail) == 0):
  252. return None
  253. # -------------------------------------------------------------------------
  254. # -------------------------------------------------------------------------
  255. def get_o3de_engine_root(check_stub=STUB_O3DE_DEV):
  256. '''Discovers the engine root
  257. Input: a file name stub, default engine.json
  258. Output: engine root path (if found)
  259. Notice: This method will be deprecated
  260. '''
  261. # get the O3DE engine root folder
  262. # if we are running within O3DE we can ensure which engine is running
  263. _O3DE_DEV = None
  264. try:
  265. import azlmbr # this file will fail outside of O3DE
  266. except ImportError as e:
  267. # if that fails, we can search up
  268. # search up to get \dev
  269. _O3DE_DEV = get_stub_check_path(check_stub=STUB_O3DE_DEV)
  270. # To Do: What if engine.json doesn't exist?
  271. else:
  272. # execute if no exception, allow for external ENVAR override
  273. _O3DE_DEV = Path(os.getenv(ENVAR_O3DE_DEV, azlmbr.paths.engroot))
  274. finally:
  275. if _DCCSI_GDEBUG: # to verbose, used often
  276. # note: can't use fstrings as this module gets called with py2.7 in maya
  277. _LOGGER.info('O3DE engine root: {}'.format(_O3DE_DEV.resolve()))
  278. return _O3DE_DEV
  279. # -------------------------------------------------------------------------
  280. # -------------------------------------------------------------------------
  281. def get_o3de_build_path(root_directory=get_o3de_engine_root(),
  282. marker='CMakeCache.txt'):
  283. """Returns a path for the O3DE\build root if found. Searches down from a
  284. known engine root path.
  285. Input: a root directory, default is to discover the engine root
  286. Output: the path of the build folder (if found)
  287. """
  288. root_directory = Path(root_directory)
  289. import timeit
  290. start = timeit.default_timer()
  291. for root, dirs, files in os.walk(root_directory.as_posix()):
  292. if marker in files:
  293. if _DCCSI_GDEBUG:
  294. _LOGGER.debug('Find PATH_O3DE_BUILD took: {} sec'
  295. ''.format(timeit.default_timer() - start))
  296. return Path(root)
  297. else:
  298. if _DCCSI_GDEBUG:
  299. _LOGGER.debug('Not fidning PATH_O3DE_BUILD took: {} sec'
  300. ''.format(timeit.default_timer() - start))
  301. return None
  302. # note: if we use this method to find PATH_O3DE_BUILD
  303. # by searching for the 'CMakeCache.txt' it can take 1 or more seconds
  304. # this will slow down boot times!
  305. #
  306. # this works fine for a engine dev, but is not really suitable for end users
  307. # it assumes that the engine is being built and 'CMakeCache.txt' exists
  308. # but the engine could be pre-built or packaged somehow
  309. #
  310. # other ways to deal with it:
  311. # 1 - Use the running application .exe to discover the build path?
  312. # 2 - If developer set PATH_O3DE_BUILD envar in
  313. # "C:\Depot\o3de\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface\.env"
  314. # 3 - Set envar in commandline before executing script (or from .bat file)
  315. # 4 - To Do (maybe): Set in a dccsi_configuration.setreg?
  316. # -------------------------------------------------------------------------
  317. # # -------------------------------------------------------------------------
  318. # # DEPRECATION: this is a warning that this version will be deprecated.
  319. # # This version was written to support py2.7 as pre-Maya2020 was stuck on that
  320. # # We no longer intend to support py2.7
  321. # def get_dccsi_config(dccsi_dirpath=return_stub_dir()):
  322. # """Convenience method to set and retreive settings directly from module."""
  323. # # we can go ahead and just make sure the the DCCsi env is set
  324. # # config is SO generic this ensures we are importing a specific one
  325. # _module_tag = "dccsi.config"
  326. # _dccsi_path = Path(dccsi_dirpath, "config.py")
  327. # if _dccsi_path.exists():
  328. # if sys.version_info.major >= 3:
  329. # import importlib # note: py2.7 doesn't have importlib.util
  330. # import importlib.util
  331. # #from importlib import util
  332. # _spec_dccsi_config = importlib.util.spec_from_file_location(_module_tag,
  333. # str(_dccsi_path.as_posix()))
  334. # _dccsi_config = importlib.util.module_from_spec(_spec_dccsi_config)
  335. # _spec_dccsi_config.loader.exec_module(_dccsi_config)
  336. # _LOGGER.debug('Executed config: {}'.format(_spec_dccsi_config))
  337. # else: # py2.x
  338. # import imp
  339. # _dccsi_config = imp.load_source(_module_tag, str(_dccsi_path.as_posix()))
  340. # _LOGGER.debug('Imported config: {}'.format(_spec_dccsi_config))
  341. # return _dccsi_config
  342. # else:
  343. # return None
  344. # # After getting the config you can do the following.
  345. # # settings.setenv() # doing this will add the additional DYNACONF_ envars
  346. # # -------------------------------------------------------------------------
  347. # -------------------------------------------------------------------------
  348. def get_dccsi_config(PATH_DCCSIG=_PATH_DCCSIG.resolve()):
  349. """Convenience method to set and retreive settings directly from module."""
  350. try:
  351. Path(PATH_DCCSIG).exists()
  352. except FileNotFoundError as e:
  353. _LOGGER.debug(f"File does not exist: {PATH_DCCSIG}")
  354. return None
  355. # we can go ahead and just make sure the the DCCsi env is set
  356. # module name config.py is SO generic this ensures we are importing a specific one
  357. _spec_dccsi_config = importlib.util.spec_from_file_location("dccsi._DCCSI_CORE_CONFIG",
  358. Path(PATH_DCCSIG,
  359. "config.py"))
  360. _dccsi_config = importlib.util.module_from_spec(_spec_dccsi_config)
  361. _spec_dccsi_config.loader.exec_module(_dccsi_config)
  362. return _dccsi_config
  363. # After getting the config you can do the following.
  364. # settings.setenv() # doing this will add the additional DYNACONF_ envars
  365. # -------------------------------------------------------------------------
  366. # -------------------------------------------------------------------------
  367. def get_current_project_cfg(dev_folder=get_stub_check_path()):
  368. """Uses regex in lumberyard Dev\\bootstrap.cfg to retreive project tag str
  369. Note: boostrap.cfg will be deprecated. Don't use this method anymore."""
  370. boostrap_filepath = Path(dev_folder, "bootstrap.cfg")
  371. if boostrap_filepath.exists():
  372. bootstrap = open(str(boostrap_filepath), "r")
  373. regex_str = r"^project_path\s*=\s*(.*)"
  374. game_project_regex = re.compile(regex_str)
  375. for line in bootstrap:
  376. game_folder_match = game_project_regex.match(line)
  377. if game_folder_match:
  378. _LOGGER.debug('Project is: {}'.format(game_folder_match.group(1)))
  379. return game_folder_match.group(1)
  380. return None
  381. # -------------------------------------------------------------------------
  382. # -------------------------------------------------------------------------
  383. def get_check_global_project():
  384. """Gets o3de project via .o3de data in user directory"""
  385. from collections import OrderedDict
  386. import box
  387. bootstrap_box = None
  388. json_file_path = Path(PATH_USER_O3DE_BOOTSTRAP)
  389. if json_file_path.exists():
  390. try:
  391. bootstrap_box = box.Box.from_json(filename=str(json_file_path.as_posix()),
  392. encoding="utf-8",
  393. errors="strict",
  394. object_pairs_hook=OrderedDict)
  395. except IOError as e:
  396. # this file runs in py2.7 for Maya 2020, FileExistsError is not defined
  397. _LOGGER.error('Bad file interaction: {}'.format(json_file_path.as_posix()))
  398. _LOGGER.error('Exception is: {}'.format(e))
  399. pass
  400. if bootstrap_box:
  401. # this seems fairly hard coded - what if the data changes?
  402. project_path=Path(bootstrap_box.Amazon.AzCore.Bootstrap.project_path)
  403. return project_path
  404. else:
  405. return None
  406. # -------------------------------------------------------------------------
  407. # -------------------------------------------------------------------------
  408. def get_o3de_project_path():
  409. """figures out the o3de project path if not found defaults to the engine folder"""
  410. _PATH_O3DE_PROJECT = None
  411. try:
  412. import azlmbr # this file will fail outside of O3DE
  413. except ImportError as e:
  414. # (fallback 1) this checks if a global project is set
  415. # This check user home for .o3de data
  416. _PATH_O3DE_PROJECT = get_check_global_project()
  417. else:
  418. # execute if no exception, this would indicate we are in O3DE land
  419. # allow for external ENVAR override
  420. _PATH_O3DE_PROJECT = Path(os.getenv(ENVAR_PATH_O3DE_PROJECT, azlmbr.paths.projectroot))
  421. finally:
  422. # (fallback 2) if None, fallback to engine folder
  423. if not _PATH_O3DE_PROJECT:
  424. _PATH_O3DE_PROJECT = get_o3de_engine_root()
  425. if _DCCSI_GDEBUG: # to verbose, used often
  426. # note: can't use fstrings as this module gets called with py2.7 in maya
  427. _LOGGER.debug('O3DE project root: {}'.format(_PATH_O3DE_PROJECT.as_posix()))
  428. return _PATH_O3DE_PROJECT
  429. # -------------------------------------------------------------------------
  430. # -------------------------------------------------------------------------
  431. def bootstrap_dccsi_py_libs(dccsi_dirpath=return_stub_dir()):
  432. """Builds and adds local site dir libs based on py version"""
  433. _PATH_DCCSI_PYTHON_LIB = Path(STR_PATH_DCCSI_PYTHON_LIB.format(dccsi_dirpath,
  434. sys.version_info[0],
  435. sys.version_info[1]))
  436. if _PATH_DCCSI_PYTHON_LIB.exists():
  437. site.addsitedir(_PATH_DCCSI_PYTHON_LIB.as_posix()) # PYTHONPATH
  438. _LOGGER.debug('Performed site.addsitedir({})'
  439. ''.format(_PATH_DCCSI_PYTHON_LIB.as_posix()))
  440. return _PATH_DCCSI_PYTHON_LIB
  441. else:
  442. message = "Doesn't exist: {}".format(_PATH_DCCSI_PYTHON_LIB)
  443. _LOGGER.error(message)
  444. raise NotADirectoryError(message)
  445. # -------------------------------------------------------------------------
  446. # -------------------------------------------------------------------------
  447. _MODULE_END = timeit.default_timer() - _MODULE_START
  448. _LOGGER.debug(f'Module {_MODULENAME} took: {_MODULE_END} sec')
  449. # -------------------------------------------------------------------------
  450. ###########################################################################
  451. # Main Code Block, runs this script as main (testing)
  452. # -------------------------------------------------------------------------
  453. if __name__ == '__main__':
  454. """Run this file as a standalone cli script for testing/debugging"""
  455. _MODULENAME = 'DCCsi.azpy.config_utils.cli'
  456. # default loglevel to info unless set
  457. _DCCSI_LOGLEVEL = _logging.INFO
  458. if _DCCSI_GDEBUG:
  459. # override loglevel if runnign debug
  460. _DCCSI_LOGLEVEL = _logging.DEBUG
  461. # configure basic logger
  462. # note: not using a common logger to reduce cyclical imports
  463. _logging.basicConfig(level=_DCCSI_LOGLEVEL,
  464. format=FRMT_LOG_LONG,
  465. datefmt='%m-%d %H:%M')
  466. _LOGGER = _logging.getLogger(_MODULENAME)
  467. _LOGGER.info("# {0} #".format('-' * 72))
  468. _LOGGER.info(f'~ {_MODULENAME} ... Running module as __main__')
  469. _LOGGER.info("# {0} #".format('-' * 72))
  470. # parse the command line args
  471. import argparse
  472. parser = argparse.ArgumentParser(
  473. description=f'O3DE DCCsi: {_MODULENAME}',
  474. epilog="Coomandline args enable deeper testing and info from commandline")
  475. parser.add_argument('-gd', '--global-debug',
  476. type=bool,
  477. required=False,
  478. default=True,
  479. help='Enables global debug flag.')
  480. parser.add_argument('-dm', '--developer-mode',
  481. type=bool,
  482. required=False,
  483. default=False,
  484. help='Enables dev mode for early auto attaching debugger.')
  485. parser.add_argument('-sd', '--set-debugger',
  486. type=str,
  487. required=False,
  488. default='WING',
  489. help='(NOT IMPLEMENTED) Default debugger: WING, thers: PYCHARM and VSCODE.')
  490. parser.add_argument('-rt', '--run-tests',
  491. type=bool,
  492. required=False,
  493. default=True,
  494. help='Runs local module tests from cli (default=True).')
  495. parser.add_argument('-ex', '--exit',
  496. type=bool,
  497. required=False,
  498. help='(NOT IMPLEMENTED) Exits python. Do not exit if you want to be in interactive interpreter after config')
  499. args = parser.parse_args()
  500. # easy overrides
  501. if args.global_debug:
  502. _DCCSI_GDEBUG = True
  503. _DCCSI_LOGLEVEL = _logging.DEBUG
  504. _LOGGER.setLevel(_DCCSI_LOGLEVEL)
  505. if args.set_debugger:
  506. _LOGGER.info('Setting and switching debugger type not implemented (default=WING)')
  507. # To Do: implement debugger plugin pattern
  508. if args.developer_mode or _DCCSI_DEV_MODE:
  509. _DCCSI_DEV_MODE = True
  510. attach_debugger() # attempts to start debugger
  511. # built in simple tests and info from commandline
  512. _LOGGER.info('Current Work dir: {0}'.format(os.getcwd()))
  513. if args.run_tests:
  514. _LOGGER.info(f'Running local module tests ...')
  515. _LOGGER.info('Active OS: {}'.format(get_os()))
  516. # attempts to retreiv the global o3de project
  517. _PATH_O3DE_PROJECT = Path(get_check_global_project()).resolve()
  518. _LOGGER.info(f'PATH_O3DE_PROJECT: {_PATH_O3DE_PROJECT.as_posix()}')
  519. # used a stub file name to walk to a location
  520. _PATH_DCCSIG = Path(return_stub_dir('dccsi_stub')).resolve()
  521. _LOGGER.info(f'PATH_DCCSIG: {_PATH_DCCSIG.as_posix()}')
  522. # retreives the core dccsi config module
  523. _DCCSI_CONFIG = get_dccsi_config(_PATH_DCCSIG)
  524. _LOGGER.info(f'PATH_DCCSI_CONFIG: {_DCCSI_CONFIG}')
  525. # bootstraps site lib access to dccsi installed package dependancies
  526. _PATH_DCCSI_PYTHON_LIB = Path(bootstrap_dccsi_py_libs(_PATH_DCCSIG)).resolve()
  527. _LOGGER.info(f'PATH_DCCSI_PYTHON_LIB: {_PATH_DCCSI_PYTHON_LIB.resolve()}')
  528. # uses a known file to walk and discover the engine root
  529. _O3DE_DEV = Path(get_o3de_engine_root(check_stub='engine.json')).resolve()
  530. _LOGGER.info(f'O3DE_DEV: {_O3DE_DEV.as_posix()}')
  531. # uses a cmake file to try and find the build directory
  532. # to do: this method is not great, it doesn't work with installer
  533. # need to find a better approach
  534. # possibly the o3de.py package to retreive from manifest?
  535. _PATH_O3DE_BUILD = Path(get_o3de_build_path(_O3DE_DEV,
  536. 'CMakeCache.txt')).resolve()
  537. _LOGGER.info('PATH_O3DE_BUILD: {}'.format(_PATH_O3DE_BUILD.as_posix()))
  538. _MODULE_END = timeit.default_timer() - _MODULE_START
  539. _LOGGER.info(f'CLI {_MODULENAME} took: {_MODULE_END} sec')
  540. # custom prompt
  541. sys.ps1 = f"[{_MODULENAME}]>>"
  542. if args.exit:
  543. # return
  544. sys.exit()
  545. # --- END -----------------------------------------------------------------