2
0

PythonSystemComponent.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851
  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 <PythonSystemComponent.h>
  9. #include <EditorPythonBindings/EditorPythonBindingsBus.h>
  10. #include <Source/PythonCommon.h>
  11. #include <Source/PythonSymbolsBus.h>
  12. #include <pybind11/pybind11.h>
  13. #include <pybind11/embed.h>
  14. #include <pybind11/eval.h>
  15. #include <osdefs.h> // for DELIM
  16. #include <AzCore/Component/EntityId.h>
  17. #include <AzCore/IO/SystemFile.h>
  18. #include <AzCore/Module/DynamicModuleHandle.h>
  19. #include <AzCore/Module/Module.h>
  20. #include <AzCore/Module/ModuleManagerBus.h>
  21. #include <AzCore/PlatformDef.h>
  22. #include <AzCore/Serialization/EditContext.h>
  23. #include <AzCore/Serialization/SerializeContext.h>
  24. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  25. #include <AzCore/std/string/conversions.h>
  26. #include <AzCore/std/smart_ptr/make_shared.h>
  27. #include <AzCore/StringFunc/StringFunc.h>
  28. #include <AzCore/Utils/Utils.h>
  29. #include <AzFramework/API/ApplicationAPI.h>
  30. #include <AzFramework/Asset/AssetSystemComponent.h>
  31. #include <AzFramework/IO/LocalFileIO.h>
  32. #include <AzFramework/CommandLine/CommandRegistrationBus.h>
  33. #include <AzFramework/StringFunc/StringFunc.h>
  34. #include <AzToolsFramework/API/EditorPythonConsoleBus.h>
  35. #include <AzToolsFramework/API/EditorPythonScriptNotificationsBus.h>
  36. #include <AzToolsFramework/API/PythonLoader.h>
  37. // this is called the first time a Python script contains "import azlmbr"
  38. PYBIND11_EMBEDDED_MODULE(azlmbr, m)
  39. {
  40. EditorPythonBindings::EditorPythonBindingsNotificationBus::Broadcast(&EditorPythonBindings::EditorPythonBindingsNotificationBus::Events::OnImportModule, m.ptr());
  41. }
  42. namespace RedirectOutput
  43. {
  44. using RedirectOutputFunc = AZStd::function<void(const char*)>;
  45. struct RedirectOutput
  46. {
  47. PyObject_HEAD
  48. RedirectOutputFunc write;
  49. };
  50. PyObject* RedirectWrite(PyObject* self, PyObject* args)
  51. {
  52. std::size_t written(0);
  53. RedirectOutput* selfimpl = reinterpret_cast<RedirectOutput*>(self);
  54. if (selfimpl->write)
  55. {
  56. char* data;
  57. if (!PyArg_ParseTuple(args, "s", &data))
  58. {
  59. return PyLong_FromSize_t(0);
  60. }
  61. selfimpl->write(data);
  62. written = strlen(data);
  63. }
  64. return PyLong_FromSize_t(written);
  65. }
  66. PyObject* RedirectFlush([[maybe_unused]] PyObject* self, [[maybe_unused]] PyObject* args)
  67. {
  68. // no-op
  69. return Py_BuildValue("");
  70. }
  71. PyMethodDef RedirectMethods[] =
  72. {
  73. {"write", RedirectWrite, METH_VARARGS, "sys.stdout.write"},
  74. {"flush", RedirectFlush, METH_VARARGS, "sys.stdout.flush"},
  75. {"write", RedirectWrite, METH_VARARGS, "sys.stderr.write"},
  76. {"flush", RedirectFlush, METH_VARARGS, "sys.stderr.flush"},
  77. {0, 0, 0, 0} // sentinel
  78. };
  79. PyTypeObject RedirectOutputType =
  80. {
  81. PyVarObject_HEAD_INIT(0, 0)
  82. "azlmbr_redirect.RedirectOutputType", // tp_name
  83. sizeof(RedirectOutput), /* tp_basicsize */
  84. 0, /* tp_itemsize */
  85. 0, /* tp_dealloc */
  86. 0, /* tp_print */
  87. 0, /* tp_getattr */
  88. 0, /* tp_setattr */
  89. 0, /* tp_reserved */
  90. 0, /* tp_repr */
  91. 0, /* tp_as_number */
  92. 0, /* tp_as_sequence */
  93. 0, /* tp_as_mapping */
  94. 0, /* tp_hash */
  95. 0, /* tp_call */
  96. 0, /* tp_str */
  97. 0, /* tp_getattro */
  98. 0, /* tp_setattro */
  99. 0, /* tp_as_buffer */
  100. Py_TPFLAGS_DEFAULT, /* tp_flags */
  101. "azlmbr_redirect objects", /* tp_doc */
  102. 0, /* tp_traverse */
  103. 0, /* tp_clear */
  104. 0, /* tp_richcompare */
  105. 0, /* tp_weaklistoffset */
  106. 0, /* tp_iter */
  107. 0, /* tp_iternext */
  108. RedirectMethods, /* tp_methods */
  109. 0, /* tp_members */
  110. 0, /* tp_getset */
  111. 0, /* tp_base */
  112. 0, /* tp_dict */
  113. 0, /* tp_descr_get */
  114. 0, /* tp_descr_set */
  115. 0, /* tp_dictoffset */
  116. 0, /* tp_init */
  117. 0, /* tp_alloc */
  118. 0 /* tp_new */
  119. };
  120. PyModuleDef RedirectOutputModule = { PyModuleDef_HEAD_INIT, "azlmbr_redirect", 0, -1, 0, };
  121. // Internal state
  122. PyObject* g_redirect_stdout = nullptr;
  123. PyObject* g_redirect_stdout_saved = nullptr;
  124. PyObject* g_redirect_stderr = nullptr;
  125. PyObject* g_redirect_stderr_saved = nullptr;
  126. PyMODINIT_FUNC PyInit_RedirectOutput(void)
  127. {
  128. g_redirect_stdout = nullptr;
  129. g_redirect_stdout_saved = nullptr;
  130. g_redirect_stderr = nullptr;
  131. g_redirect_stderr_saved = nullptr;
  132. RedirectOutputType.tp_new = PyType_GenericNew;
  133. if (PyType_Ready(&RedirectOutputType) < 0)
  134. {
  135. return 0;
  136. }
  137. PyObject* m = PyModule_Create(&RedirectOutputModule);
  138. if (m)
  139. {
  140. Py_INCREF(&RedirectOutputType);
  141. PyModule_AddObject(m, "Redirect", reinterpret_cast<PyObject*>(&RedirectOutputType));
  142. }
  143. return m;
  144. }
  145. void SetRedirection(const char* funcname, PyObject*& saved, PyObject*& current, RedirectOutputFunc func)
  146. {
  147. if (PyType_Ready(&RedirectOutputType) < 0)
  148. {
  149. AZ_Warning("python", false, "RedirectOutputType not ready!");
  150. return;
  151. }
  152. if (!current)
  153. {
  154. saved = PySys_GetObject(funcname); // borrowed
  155. current = RedirectOutputType.tp_new(&RedirectOutputType, 0, 0);
  156. }
  157. RedirectOutput* redirectOutput = reinterpret_cast<RedirectOutput*>(current);
  158. redirectOutput->write = func;
  159. PySys_SetObject(funcname, current);
  160. }
  161. void ResetRedirection(const char* funcname, PyObject*& saved, PyObject*& current)
  162. {
  163. if (current)
  164. {
  165. PySys_SetObject(funcname, saved);
  166. }
  167. Py_XDECREF(current);
  168. current = nullptr;
  169. }
  170. PyObject* s_RedirectModule = nullptr;
  171. void Intialize(PyObject* module)
  172. {
  173. using namespace AzToolsFramework;
  174. s_RedirectModule = module;
  175. SetRedirection("stdout", g_redirect_stdout_saved, g_redirect_stdout, [](const char* msg)
  176. {
  177. EditorPythonConsoleNotificationBus::Broadcast(&EditorPythonConsoleNotificationBus::Events::OnTraceMessage, msg);
  178. });
  179. SetRedirection("stderr", g_redirect_stderr_saved, g_redirect_stderr, [](const char* msg)
  180. {
  181. EditorPythonConsoleNotificationBus::Broadcast(&EditorPythonConsoleNotificationBus::Events::OnErrorMessage, msg);
  182. });
  183. PySys_WriteStdout("RedirectOutput installed");
  184. }
  185. void Shutdown()
  186. {
  187. ResetRedirection("stdout", g_redirect_stdout_saved, g_redirect_stdout);
  188. ResetRedirection("stderr", g_redirect_stderr_saved, g_redirect_stderr);
  189. Py_XDECREF(s_RedirectModule);
  190. s_RedirectModule = nullptr;
  191. }
  192. } // namespace RedirectOutput
  193. namespace EditorPythonBindings
  194. {
  195. // A stand in bus to capture the log symbol queue events
  196. // so that when/if the PythonLogSymbolsComponent becomes
  197. // active it can write out the python symbols to disk
  198. class PythonSystemComponent::SymbolLogHelper final
  199. : public PythonSymbolEventBus::Handler
  200. {
  201. public:
  202. SymbolLogHelper()
  203. {
  204. PythonSymbolEventBus::Handler::BusConnect();
  205. }
  206. ~SymbolLogHelper()
  207. {
  208. PythonSymbolEventBus::ExecuteQueuedEvents();
  209. PythonSymbolEventBus::Handler::BusDisconnect();
  210. }
  211. void LogClass(const AZStd::string, const AZ::BehaviorClass*) override {}
  212. void LogClassWithName(const AZStd::string, const AZ::BehaviorClass*, const AZStd::string) override {}
  213. void LogClassMethod(
  214. const AZStd::string,
  215. const AZStd::string,
  216. const AZ::BehaviorClass*,
  217. const AZ::BehaviorMethod*) override {}
  218. void LogBus(const AZStd::string, const AZStd::string, const AZ::BehaviorEBus*) override {}
  219. void LogGlobalMethod(const AZStd::string, const AZStd::string, const AZ::BehaviorMethod*) override {}
  220. void LogGlobalProperty(const AZStd::string, const AZStd::string, const AZ::BehaviorProperty*) override {}
  221. void Finalize() override {}
  222. };
  223. // Manages the acquisition and release of the Python GIL (Global Interpreter Lock).
  224. // Used by PythonSystemComponent to lock the GIL when executing python.
  225. class PythonSystemComponent::PythonGILScopedLock final
  226. {
  227. public:
  228. PythonGILScopedLock(AZStd::recursive_mutex& lock, int& lockRecursiveCounter, bool tryLock = false);
  229. ~PythonGILScopedLock();
  230. bool IsLocked() const;
  231. protected:
  232. void Lock(bool tryLock);
  233. void Unlock();
  234. AZStd::recursive_mutex& m_lock;
  235. int& m_lockRecursiveCounter;
  236. bool m_locked = false;
  237. AZStd::unique_ptr<pybind11::gil_scoped_release> m_releaseGIL;
  238. AZStd::unique_ptr<pybind11::gil_scoped_acquire> m_acquireGIL;
  239. };
  240. PythonSystemComponent::PythonGILScopedLock::PythonGILScopedLock(AZStd::recursive_mutex& lock, int& lockRecursiveCounter, bool tryLock)
  241. : m_lock(lock)
  242. , m_lockRecursiveCounter(lockRecursiveCounter)
  243. {
  244. Lock(tryLock);
  245. }
  246. PythonSystemComponent::PythonGILScopedLock::~PythonGILScopedLock()
  247. {
  248. Unlock();
  249. }
  250. bool PythonSystemComponent::PythonGILScopedLock::IsLocked() const
  251. {
  252. return m_locked;
  253. }
  254. void PythonSystemComponent::PythonGILScopedLock::Lock(bool tryLock)
  255. {
  256. if (tryLock)
  257. {
  258. if (!m_lock.try_lock())
  259. {
  260. return;
  261. }
  262. }
  263. else
  264. {
  265. m_lock.lock();
  266. }
  267. m_locked = true;
  268. m_lockRecursiveCounter++;
  269. // Only Acquire the GIL when there is no recursion. If there is
  270. // recursion that means it's the same thread (because the mutex was able
  271. // to be locked) and therefore it's already got the GIL acquired.
  272. if (m_lockRecursiveCounter == 1)
  273. {
  274. m_releaseGIL = AZStd::make_unique<pybind11::gil_scoped_release>();
  275. m_acquireGIL = AZStd::make_unique<pybind11::gil_scoped_acquire>();
  276. }
  277. }
  278. void PythonSystemComponent::PythonGILScopedLock::Unlock()
  279. {
  280. if (!m_locked)
  281. {
  282. return;
  283. }
  284. m_acquireGIL.reset();
  285. m_releaseGIL.reset();
  286. m_lockRecursiveCounter--;
  287. m_locked = false;
  288. m_lock.unlock();
  289. }
  290. void PythonSystemComponent::Reflect(AZ::ReflectContext* context)
  291. {
  292. if (auto serialize = azrtti_cast<AZ::SerializeContext*>(context))
  293. {
  294. serialize->Class<PythonSystemComponent, AZ::Component>()
  295. ->Version(1)
  296. ->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector<AZ::Crc32>{AZ_CRC_CE("AssetBuilder")})
  297. ;
  298. if (AZ::EditContext* ec = serialize->GetEditContext())
  299. {
  300. ec->Class<PythonSystemComponent>("PythonSystemComponent", "The Python interpreter")
  301. ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
  302. ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
  303. ;
  304. }
  305. }
  306. PythonActionManagerHandler::Reflect(context);
  307. }
  308. void PythonSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
  309. {
  310. provided.push_back(PythonEmbeddedService);
  311. }
  312. void PythonSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
  313. {
  314. incompatible.push_back(PythonEmbeddedService);
  315. }
  316. void PythonSystemComponent::Activate()
  317. {
  318. AZ::Interface<AzToolsFramework::EditorPythonEventsInterface>::Register(this);
  319. AzToolsFramework::EditorPythonRunnerRequestBus::Handler::BusConnect();
  320. }
  321. void PythonSystemComponent::Deactivate()
  322. {
  323. StopPython(true);
  324. AzToolsFramework::EditorPythonRunnerRequestBus::Handler::BusDisconnect();
  325. AZ::Interface<AzToolsFramework::EditorPythonEventsInterface>::Unregister(this);
  326. }
  327. bool PythonSystemComponent::StartPython([[maybe_unused]] bool silenceWarnings)
  328. {
  329. struct ReleaseInitalizeWaiterScope final
  330. {
  331. using ReleaseFunction = AZStd::function<void(void)>;
  332. ReleaseInitalizeWaiterScope(ReleaseFunction releaseFunction)
  333. {
  334. m_releaseFunction = AZStd::move(releaseFunction);
  335. }
  336. ~ReleaseInitalizeWaiterScope()
  337. {
  338. m_releaseFunction();
  339. }
  340. ReleaseFunction m_releaseFunction;
  341. };
  342. ReleaseInitalizeWaiterScope scope([this]()
  343. {
  344. m_initalizeWaiter.release(m_initalizeWaiterCount);
  345. m_initalizeWaiterCount = 0;
  346. });
  347. if (Py_IsInitialized())
  348. {
  349. AZ_Warning("python", silenceWarnings, "Python is already active!");
  350. return false;
  351. }
  352. PythonPathStack pythonPathStack;
  353. DiscoverPythonPaths(pythonPathStack);
  354. EditorPythonBindingsNotificationBus::Broadcast(&EditorPythonBindingsNotificationBus::Events::OnPreInitialize);
  355. if (StartPythonInterpreter(pythonPathStack))
  356. {
  357. // initialize internal base module and bootstrap scripts
  358. ExecuteByString("import azlmbr", false);
  359. ExecuteBootstrapScripts(pythonPathStack);
  360. EditorPythonBindingsNotificationBus::Broadcast(&EditorPythonBindingsNotificationBus::Events::OnPostInitialize);
  361. return true;
  362. }
  363. return false;
  364. }
  365. bool PythonSystemComponent::StopPython([[maybe_unused]] bool silenceWarnings)
  366. {
  367. if (!Py_IsInitialized())
  368. {
  369. AZ_Warning("python", silenceWarnings, "Python is not active!");
  370. return false;
  371. }
  372. bool result = false;
  373. EditorPythonBindingsNotificationBus::Broadcast(&EditorPythonBindingsNotificationBus::Events::OnPreFinalize);
  374. result = StopPythonInterpreter();
  375. EditorPythonBindingsNotificationBus::Broadcast(&EditorPythonBindingsNotificationBus::Events::OnPostFinalize);
  376. return result;
  377. }
  378. bool PythonSystemComponent::IsPythonActive()
  379. {
  380. return Py_IsInitialized() != 0;
  381. }
  382. void PythonSystemComponent::WaitForInitialization()
  383. {
  384. m_initalizeWaiterCount++;
  385. m_initalizeWaiter.acquire();
  386. }
  387. void PythonSystemComponent::ExecuteWithLock(AZStd::function<void()> executionCallback)
  388. {
  389. PythonGILScopedLock lock(m_lock, m_lockRecursiveCounter);
  390. executionCallback();
  391. }
  392. bool PythonSystemComponent::TryExecuteWithLock(AZStd::function<void()> executionCallback)
  393. {
  394. PythonGILScopedLock lock(m_lock, m_lockRecursiveCounter, true /*tryLock*/);
  395. if (lock.IsLocked())
  396. {
  397. executionCallback();
  398. return true;
  399. }
  400. return false;
  401. }
  402. void PythonSystemComponent::DiscoverPythonPaths(PythonPathStack& pythonPathStack)
  403. {
  404. // the order of the Python paths is the order the Python bootstrap scripts will execute
  405. auto settingsRegistry = AZ::SettingsRegistry::Get();
  406. if (!settingsRegistry)
  407. {
  408. return;
  409. }
  410. AZ::IO::FixedMaxPathString projectPath = AZ::Utils::GetProjectPath();
  411. if (projectPath.empty())
  412. {
  413. return;
  414. }
  415. auto resolveScriptPath = [&pythonPathStack](AZStd::string_view path)
  416. {
  417. auto editorScriptsPath = AZ::IO::Path(path) / "Editor" / "Scripts";
  418. if (AZ::IO::SystemFile::Exists(editorScriptsPath.c_str()))
  419. {
  420. pythonPathStack.emplace_back(AZStd::move(editorScriptsPath.LexicallyNormal().Native()));
  421. }
  422. };
  423. // The discovery order will be:
  424. // 1 - The python venv site-packages
  425. // 2 - engine-root/EngineAsets
  426. // 3 - gems
  427. // 4 - project
  428. // 5 - user(dev)
  429. // 1 - The python venv site-packages
  430. AZ::IO::FixedMaxPath thirdPartyFolder = AzToolsFramework::EmbeddedPython::PythonLoader::GetDefault3rdPartyPath(false);
  431. AzToolsFramework::EmbeddedPython::PythonLoader::ReadPythonEggLinkPaths(
  432. thirdPartyFolder,
  433. AZ::Utils::GetEnginePath().c_str(),
  434. [&pythonPathStack](AZ::IO::PathView path)
  435. {
  436. pythonPathStack.emplace_back(path.Native());
  437. });
  438. // 2 - engine
  439. AZ::IO::FixedMaxPath engineRoot;
  440. if (settingsRegistry->Get(engineRoot.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder); !engineRoot.empty())
  441. {
  442. resolveScriptPath((engineRoot / "Assets").Native());
  443. }
  444. // 3 - gems
  445. AZStd::vector<AZ::IO::Path> gemSourcePaths;
  446. auto AppendGemPaths = [&gemSourcePaths](AZStd::string_view, AZStd::string_view gemPath)
  447. {
  448. gemSourcePaths.emplace_back(gemPath);
  449. };
  450. AZ::SettingsRegistryMergeUtils::VisitActiveGems(*settingsRegistry, AppendGemPaths);
  451. for (const AZ::IO::Path& gemSourcePath : gemSourcePaths)
  452. {
  453. resolveScriptPath(gemSourcePath.Native());
  454. }
  455. // 4 - project
  456. resolveScriptPath(AZStd::string_view{ projectPath });
  457. // 5 - user
  458. AZStd::string assetsType;
  459. AZ::SettingsRegistryMergeUtils::PlatformGet(*settingsRegistry, assetsType,
  460. AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey, AzFramework::AssetSystem::Assets);
  461. if (!assetsType.empty())
  462. {
  463. AZ::IO::FixedMaxPath userCachePath;
  464. if (settingsRegistry->Get(userCachePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheRootFolder);
  465. !userCachePath.empty())
  466. {
  467. userCachePath /= "user";
  468. resolveScriptPath(userCachePath.Native());
  469. }
  470. }
  471. }
  472. void PythonSystemComponent::ExecuteBootstrapScripts(const PythonPathStack& pythonPathStack)
  473. {
  474. for(const auto& path : pythonPathStack)
  475. {
  476. AZStd::string bootstrapPath;
  477. AzFramework::StringFunc::Path::Join(path.c_str(), "bootstrap.py", bootstrapPath);
  478. if (AZ::IO::SystemFile::Exists(bootstrapPath.c_str()))
  479. {
  480. [[maybe_unused]] bool success = ExecuteByFilename(bootstrapPath);
  481. AZ_Assert(success, "Error while executing bootstrap script: %s", bootstrapPath.c_str());
  482. }
  483. }
  484. }
  485. bool PythonSystemComponent::StartPythonInterpreter(const PythonPathStack& pythonPathStack)
  486. {
  487. AZStd::unordered_set<AZStd::string> pyPackageSites(pythonPathStack.begin(), pythonPathStack.end());
  488. AZ::IO::FixedMaxPath engineRoot = AZ::Utils::GetEnginePath();
  489. // set PYTHON_HOME
  490. AZStd::string pyBasePath = AzToolsFramework::EmbeddedPython::PythonLoader::GetPythonHomePath(engineRoot.c_str()).StringAsPosix();
  491. if (!AZ::IO::SystemFile::Exists(pyBasePath.c_str()))
  492. {
  493. AZ_Warning("python", false, "Python home path must exist! path:%s", pyBasePath.c_str());
  494. return false;
  495. }
  496. AZStd::wstring pyHomePath;
  497. AZStd::to_wstring(pyHomePath, pyBasePath);
  498. Py_SetPythonHome(pyHomePath.c_str());
  499. // display basic Python information
  500. AZ_TracePrintf("python", "Py_GetVersion=%s \n", Py_GetVersion());
  501. AZ_TracePrintf("python", "Py_GetPath=%ls \n", Py_GetPath());
  502. AZ_TracePrintf("python", "Py_GetExecPrefix=%ls \n", Py_GetExecPrefix());
  503. AZ_TracePrintf("python", "Py_GetProgramFullPath=%ls \n", Py_GetProgramFullPath());
  504. PyImport_AppendInittab("azlmbr_redirect", RedirectOutput::PyInit_RedirectOutput);
  505. try
  506. {
  507. // ignore system location for sites site-packages
  508. Py_IsolatedFlag = 1; // -I - Also sets Py_NoUserSiteDirectory. If removed PyNoUserSiteDirectory should be set.
  509. Py_IgnoreEnvironmentFlag = 1; // -E
  510. Py_InspectFlag = 1; // unhandled SystemExit will terminate the process unless Py_InspectFlag is set
  511. const bool initializeSignalHandlers = true;
  512. pybind11::initialize_interpreter(initializeSignalHandlers);
  513. // Add custom site packages after initializing the interpreter above. Calling Py_SetPath before initialization
  514. // alters the behavior of the initializer to not compute default search paths. See https://docs.python.org/3/c-api/init.html#c.Py_SetPath
  515. if (pyPackageSites.size())
  516. {
  517. ExtendSysPath(pyPackageSites);
  518. }
  519. RedirectOutput::Intialize(PyImport_ImportModule("azlmbr_redirect"));
  520. // Acquire GIL before calling Python code
  521. PythonGILScopedLock lock(m_lock, m_lockRecursiveCounter);
  522. if (EditorPythonBindings::PythonSymbolEventBus::GetTotalNumOfEventHandlers() == 0)
  523. {
  524. m_symbolLogHelper = AZStd::make_shared<PythonSystemComponent::SymbolLogHelper>();
  525. }
  526. // print Python version using AZ logging
  527. const int verRet = PyRun_SimpleStringFlags("import sys \nprint (sys.version) \n", nullptr);
  528. AZ_Error("python", verRet == 0, "Error trying to fetch the version number in Python!");
  529. return verRet == 0 && !PyErr_Occurred();
  530. }
  531. catch ([[maybe_unused]] const std::exception& e)
  532. {
  533. AZ_Warning("python", false, "Py_Initialize() failed with %s!", e.what());
  534. return false;
  535. }
  536. }
  537. bool PythonSystemComponent::ExtendSysPath(const AZStd::unordered_set<AZStd::string>& extendPaths)
  538. {
  539. AZStd::unordered_set<AZStd::string> oldPathSet;
  540. auto SplitPath = [&oldPathSet](AZStd::string_view pathPart)
  541. {
  542. oldPathSet.emplace(pathPart);
  543. };
  544. AZ::StringFunc::TokenizeVisitor(Py_EncodeLocale(Py_GetPath(), nullptr), SplitPath, DELIM);
  545. bool appended{ false };
  546. AZStd::string pathAppend{ "import sys\n" };
  547. for (const auto& thisStr : extendPaths)
  548. {
  549. if (!oldPathSet.contains(thisStr))
  550. {
  551. pathAppend.append(AZStd::string::format("sys.path.append(r'%s')\n", thisStr.c_str()));
  552. appended = true;
  553. }
  554. }
  555. if (appended)
  556. {
  557. ExecuteByString(pathAppend.c_str(), false);
  558. return true;
  559. }
  560. return false;
  561. }
  562. bool PythonSystemComponent::StopPythonInterpreter()
  563. {
  564. if (Py_IsInitialized())
  565. {
  566. RedirectOutput::Shutdown();
  567. pybind11::finalize_interpreter();
  568. }
  569. else
  570. {
  571. AZ_Warning("python", false, "Did not finalize since Py_IsInitialized() was false.");
  572. }
  573. return true;
  574. }
  575. void PythonSystemComponent::ExecuteByString(AZStd::string_view script, bool printResult)
  576. {
  577. if (!Py_IsInitialized())
  578. {
  579. AZ_Error("python", false, "Can not ExecuteByString() since the embeded Python VM is not ready.");
  580. return;
  581. }
  582. if (!script.empty())
  583. {
  584. AzToolsFramework::EditorPythonScriptNotificationsBus::Broadcast(
  585. &AzToolsFramework::EditorPythonScriptNotificationsBus::Events::OnStartExecuteByString, script);
  586. // Acquire GIL before calling Python code
  587. PythonGILScopedLock lock(m_lock, m_lockRecursiveCounter);
  588. // Acquire scope for __main__ for executing our script
  589. pybind11::object scope = pybind11::module::import("__main__").attr("__dict__");
  590. bool shouldPrintValue = false;
  591. if (printResult)
  592. {
  593. // Attempt to compile our code to determine if it's an expression
  594. // i.e. a Python code object with only an rvalue
  595. // If it is, it can be evaled to produce a PyObject
  596. // If it's not, we can't evaluate it into a result and should fall back to exec
  597. shouldPrintValue = true;
  598. using namespace pybind11::literals;
  599. // codeop.compile_command is a thin wrapper around the Python compile builtin
  600. // We attempt to compile using symbol="eval" to see if the string is valid for eval
  601. // This is similar to what the Python REPL does internally
  602. pybind11::object codeop = pybind11::module::import("codeop");
  603. pybind11::object compileCommand = codeop.attr("compile_command");
  604. try
  605. {
  606. compileCommand(script.data(), "symbol"_a="eval");
  607. }
  608. catch (const pybind11::error_already_set&)
  609. {
  610. shouldPrintValue = false;
  611. }
  612. }
  613. try
  614. {
  615. if (shouldPrintValue)
  616. {
  617. // We're an expression, run and print the result
  618. pybind11::object result = pybind11::eval(script.data(), scope);
  619. pybind11::print(result);
  620. }
  621. else
  622. {
  623. // Just exec the code block
  624. pybind11::exec(script.data(), scope);
  625. }
  626. }
  627. catch (pybind11::error_already_set& pythonError)
  628. {
  629. // Release the exception stack and let Python print it to stderr
  630. pythonError.restore();
  631. PyErr_Print();
  632. }
  633. }
  634. }
  635. bool PythonSystemComponent::ExecuteByFilename(AZStd::string_view filename)
  636. {
  637. AZStd::vector<AZStd::string_view> args;
  638. AzToolsFramework::EditorPythonScriptNotificationsBus::Broadcast(
  639. &AzToolsFramework::EditorPythonScriptNotificationsBus::Events::OnStartExecuteByFilename, filename);
  640. return ExecuteByFilenameWithArgs(filename, args);
  641. }
  642. bool PythonSystemComponent::ExecuteByFilenameAsTest(AZStd::string_view filename, AZStd::string_view testCase, const AZStd::vector<AZStd::string_view>& args)
  643. {
  644. AZ_TracePrintf("python", "Running automated test: %.*s (testcase %.*s)", AZ_STRING_ARG(filename), AZ_STRING_ARG(testCase))
  645. AzToolsFramework::EditorPythonScriptNotificationsBus::Broadcast(
  646. &AzToolsFramework::EditorPythonScriptNotificationsBus::Events::OnStartExecuteByFilenameAsTest, filename, testCase, args);
  647. const Result evalResult = EvaluateFile(filename, args);
  648. return evalResult == Result::Okay;
  649. }
  650. bool PythonSystemComponent::ExecuteByFilenameWithArgs(AZStd::string_view filename, const AZStd::vector<AZStd::string_view>& args)
  651. {
  652. AzToolsFramework::EditorPythonScriptNotificationsBus::Broadcast(
  653. &AzToolsFramework::EditorPythonScriptNotificationsBus::Events::OnStartExecuteByFilenameWithArgs, filename, args);
  654. const Result result = EvaluateFile(filename, args);
  655. return result == Result::Okay;
  656. }
  657. PythonSystemComponent::Result PythonSystemComponent::EvaluateFile(AZStd::string_view filename, const AZStd::vector<AZStd::string_view>& args)
  658. {
  659. if (!Py_IsInitialized())
  660. {
  661. AZ_Error("python", false, "Can not evaluate file since the embedded Python VM is not ready.");
  662. return Result::Error_IsNotInitialized;
  663. }
  664. if (filename.empty())
  665. {
  666. AZ_Error("python", false, "Invalid empty filename detected.");
  667. return Result::Error_InvalidFilename;
  668. }
  669. // support the alias version of a script such as @engroot@/Editor/Scripts/select_story_anim_objects.py
  670. AZStd::string theFilename(filename);
  671. {
  672. char resolvedPath[AZ_MAX_PATH_LEN] = { 0 };
  673. AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(theFilename.c_str(), resolvedPath, AZ_MAX_PATH_LEN);
  674. theFilename = resolvedPath;
  675. }
  676. if (!AZ::IO::FileIOBase::GetInstance()->Exists(theFilename.c_str()))
  677. {
  678. AZ_Error("python", false, "Missing Python file named (%s)", theFilename.c_str());
  679. return Result::Error_MissingFile;
  680. }
  681. FILE* file = nullptr;
  682. azfopen(&file, theFilename.c_str(), "rb");
  683. if (!file)
  684. {
  685. AZ_Error("python", false, "Missing Python file named (%s)", theFilename.c_str());
  686. return Result::Error_FileOpenValidation;
  687. }
  688. Result pythonScriptResult = Result::Okay;
  689. try
  690. {
  691. // Acquire GIL before calling Python code
  692. PythonGILScopedLock lock(m_lock, m_lockRecursiveCounter);
  693. // Create standard "argc" / "argv" command-line parameters to pass in to the Python script via sys.argv.
  694. // argc = number of parameters. This will always be at least 1, since the first parameter is the script name.
  695. // argv = the list of parameters, in wchar format.
  696. // Our expectation is that the args passed into this function does *not* already contain the script name.
  697. int argc = aznumeric_cast<int>(args.size()) + 1;
  698. // Note: This allocates from PyMem to ensure that Python has access to the memory.
  699. wchar_t** argv = static_cast<wchar_t**>(PyMem_Malloc(argc * sizeof(wchar_t*)));
  700. // Python 3.x is expecting wchar* strings for the command-line args.
  701. argv[0] = Py_DecodeLocale(theFilename.c_str(), nullptr);
  702. for (int arg = 0; arg < args.size(); arg++)
  703. {
  704. AZStd::string argString(args[arg]);
  705. argv[arg + 1] = Py_DecodeLocale(argString.c_str(), nullptr);
  706. }
  707. // Tell Python the command-line args.
  708. // Note that this has a side effect of adding the script's path to the set of directories checked for "import" commands.
  709. const int updatePath = 1;
  710. PySys_SetArgvEx(argc, argv, updatePath);
  711. PyCompilerFlags flags;
  712. flags.cf_flags = 0;
  713. const int bAutoCloseFile = true;
  714. const int returnCode = PyRun_SimpleFileExFlags(file, theFilename.c_str(), bAutoCloseFile, &flags);
  715. if (returnCode != 0)
  716. {
  717. AZStd::string message = AZStd::string::format("Detected script failure in Python script(%s); return code %d!", theFilename.c_str(), returnCode);
  718. AZ_Warning("python", false, message.c_str());
  719. using namespace AzToolsFramework;
  720. EditorPythonConsoleNotificationBus::Broadcast(&EditorPythonConsoleNotificationBus::Events::OnExceptionMessage, message.c_str());
  721. pythonScriptResult = Result::Error_PythonException;
  722. }
  723. // Free any memory allocated for the command-line args.
  724. for (int arg = 0; arg < argc; arg++)
  725. {
  726. PyMem_RawFree(argv[arg]);
  727. }
  728. PyMem_Free(argv);
  729. }
  730. catch ([[maybe_unused]] const std::exception& e)
  731. {
  732. AZ_Error("python", false, "Detected an internal exception %s while running script (%s)!", e.what(), theFilename.c_str());
  733. return Result::Error_InternalException;
  734. }
  735. return pythonScriptResult;
  736. }
  737. } // namespace EditorPythonBindings