PythonLogSymbolsComponent.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  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 <PythonLogSymbolsComponent.h>
  9. #include <EditorPythonBindings/PythonCommon.h>
  10. #include <EditorPythonBindings/PythonUtility.h>
  11. #include <Source/PythonProxyBus.h>
  12. #include <Source/PythonProxyObject.h>
  13. #include <Source/PythonTypeCasters.h>
  14. #include <pybind11/embed.h>
  15. #include <AzCore/IO/FileIO.h>
  16. #include <AzCore/IO/SystemFile.h>
  17. #include <AzCore/PlatformDef.h>
  18. #include <AzCore/RTTI/AttributeReader.h>
  19. #include <AzCore/RTTI/BehaviorContext.h>
  20. #include <AzCore/Serialization/Utils.h>
  21. #include <AzCore/std/smart_ptr/make_shared.h>
  22. #include <AzCore/std/sort.h>
  23. #include <AzFramework/CommandLine/CommandRegistrationBus.h>
  24. #include <AzFramework/StringFunc/StringFunc.h>
  25. namespace EditorPythonBindings
  26. {
  27. namespace Internal
  28. {
  29. struct FileHandle final
  30. {
  31. explicit FileHandle(AZ::IO::HandleType handle)
  32. : m_handle(handle)
  33. {
  34. }
  35. ~FileHandle()
  36. {
  37. Close();
  38. }
  39. void Close()
  40. {
  41. if (IsValid())
  42. {
  43. AZ::IO::FileIOBase::GetInstance()->Close(m_handle);
  44. }
  45. m_handle = AZ::IO::InvalidHandle;
  46. }
  47. bool IsValid() const
  48. {
  49. return m_handle != AZ::IO::InvalidHandle;
  50. }
  51. operator AZ::IO::HandleType() const
  52. {
  53. return m_handle;
  54. }
  55. AZ::IO::HandleType m_handle;
  56. };
  57. } // namespace Internal
  58. void PythonLogSymbolsComponent::Reflect(AZ::ReflectContext* context)
  59. {
  60. if (auto&& serialize = azrtti_cast<AZ::SerializeContext*>(context))
  61. {
  62. serialize->Class<PythonLogSymbolsComponent, AZ::Component>()->Version(0);
  63. }
  64. }
  65. void PythonLogSymbolsComponent::Activate()
  66. {
  67. PythonSymbolEventBus::Handler::BusConnect();
  68. EditorPythonBindingsNotificationBus::Handler::BusConnect();
  69. AZ::Interface<AzToolsFramework::EditorPythonConsoleInterface>::Register(this);
  70. if (PythonSymbolEventBus::GetTotalNumOfEventHandlers() > 1)
  71. {
  72. OnPostInitialize();
  73. }
  74. }
  75. void PythonLogSymbolsComponent::Deactivate()
  76. {
  77. AZ::Interface<AzToolsFramework::EditorPythonConsoleInterface>::Unregister(this);
  78. PythonSymbolEventBus::Handler::BusDisconnect();
  79. EditorPythonBindingsNotificationBus::Handler::BusDisconnect();
  80. }
  81. void PythonLogSymbolsComponent::OnPostInitialize()
  82. {
  83. m_basePath.clear();
  84. if (AZ::IO::FileIOBase::GetInstance()->GetAlias("@user@"))
  85. {
  86. // clear out the previous symbols path
  87. char pythonSymbolsPath[AZ_MAX_PATH_LEN];
  88. AZ::IO::FileIOBase::GetInstance()->ResolvePath("@user@/python_symbols", pythonSymbolsPath, AZ_MAX_PATH_LEN);
  89. AZ::IO::FileIOBase::GetInstance()->CreatePath(pythonSymbolsPath);
  90. m_basePath = pythonSymbolsPath;
  91. }
  92. EditorPythonBindingsNotificationBus::Handler::BusDisconnect();
  93. PythonSymbolEventBus::ExecuteQueuedEvents();
  94. }
  95. AZStd::string_view PythonLogSymbolsComponent::FetchPythonTypeAndTraits(const AZ::TypeId& typeId, AZ::u32 traits)
  96. {
  97. return m_pythonBehaviorDescription.FetchPythonTypeAndTraits(typeId, traits);
  98. }
  99. AZStd::string PythonLogSymbolsComponent::FetchPythonTypeName(const AZ::BehaviorParameter& param)
  100. {
  101. return m_pythonBehaviorDescription.FetchPythonTypeName(param);
  102. }
  103. void PythonLogSymbolsComponent::WriteMethod(
  104. AZ::IO::HandleType handle,
  105. AZStd::string_view methodName,
  106. const AZ::BehaviorMethod& behaviorMethod,
  107. const AZ::BehaviorClass* behaviorClass)
  108. {
  109. AZStd::string buffer = m_pythonBehaviorDescription.MethodDefinition(methodName, behaviorMethod, behaviorClass);
  110. AZ::IO::FileIOBase::GetInstance()->Write(handle, buffer.c_str(), buffer.size());
  111. }
  112. void PythonLogSymbolsComponent::WriteProperty(
  113. AZ::IO::HandleType handle,
  114. int level,
  115. AZStd::string_view propertyName,
  116. const AZ::BehaviorProperty& property,
  117. const AZ::BehaviorClass* behaviorClass)
  118. {
  119. AZStd::string buffer = m_pythonBehaviorDescription.PropertyDefinition(propertyName, level, property, behaviorClass);
  120. AZ::IO::FileIOBase::GetInstance()->Write(handle, buffer.c_str(), buffer.size());
  121. }
  122. void PythonLogSymbolsComponent::LogClass(const AZStd::string moduleName, const AZ::BehaviorClass* behaviorClass)
  123. {
  124. LogClassWithName(moduleName, behaviorClass, behaviorClass->m_name.c_str());
  125. }
  126. void PythonLogSymbolsComponent::LogClassWithName(
  127. const AZStd::string moduleName, const AZ::BehaviorClass* behaviorClass, const AZStd::string className)
  128. {
  129. auto fileHandle = OpenModuleAt(moduleName);
  130. if (fileHandle->IsValid())
  131. {
  132. AZStd::string buffer = m_pythonBehaviorDescription.ClassDefinition(behaviorClass, className);
  133. AZ::IO::FileIOBase::GetInstance()->Write(*fileHandle, buffer.c_str(), buffer.size());
  134. }
  135. }
  136. void PythonLogSymbolsComponent::LogClassMethod(
  137. const AZStd::string moduleName,
  138. const AZStd::string globalMethodName,
  139. [[maybe_unused]] const AZ::BehaviorClass* behaviorClass,
  140. const AZ::BehaviorMethod* behaviorMethod)
  141. {
  142. auto fileHandle = OpenModuleAt(moduleName);
  143. if (fileHandle->IsValid())
  144. {
  145. WriteMethod(*fileHandle, globalMethodName, *behaviorMethod, nullptr);
  146. }
  147. }
  148. void PythonLogSymbolsComponent::LogBus(
  149. const AZStd::string moduleName, const AZStd::string busName, const AZ::BehaviorEBus* behaviorEBus)
  150. {
  151. if (!behaviorEBus || behaviorEBus->m_events.empty())
  152. {
  153. return;
  154. }
  155. auto fileHandle = OpenModuleAt(moduleName);
  156. if (fileHandle->IsValid())
  157. {
  158. AZStd::string buffer = m_pythonBehaviorDescription.BusDefinition(busName, behaviorEBus);
  159. AZ::IO::FileIOBase::GetInstance()->Write(*fileHandle, buffer.c_str(), buffer.size());
  160. }
  161. }
  162. void PythonLogSymbolsComponent::LogGlobalMethod(
  163. const AZStd::string moduleName, const AZStd::string methodName, const AZ::BehaviorMethod* behaviorMethod)
  164. {
  165. auto fileHandle = OpenModuleAt(moduleName);
  166. if (fileHandle->IsValid())
  167. {
  168. WriteMethod(*fileHandle, methodName, *behaviorMethod, nullptr);
  169. }
  170. auto functionMapIt = m_globalFunctionMap.find(moduleName);
  171. if (functionMapIt == m_globalFunctionMap.end())
  172. {
  173. auto moduleSetIt = m_moduleSet.find(moduleName);
  174. if (moduleSetIt != m_moduleSet.end())
  175. {
  176. m_globalFunctionMap[*moduleSetIt] = { AZStd::make_pair(behaviorMethod, methodName) };
  177. }
  178. }
  179. else
  180. {
  181. GlobalFunctionList& globalFunctionList = functionMapIt->second;
  182. globalFunctionList.emplace_back(AZStd::make_pair(behaviorMethod, methodName));
  183. }
  184. }
  185. void PythonLogSymbolsComponent::LogGlobalProperty(
  186. const AZStd::string moduleName, const AZStd::string propertyName, const AZ::BehaviorProperty* behaviorProperty)
  187. {
  188. if (!behaviorProperty || !behaviorProperty->m_getter || !behaviorProperty->m_getter->GetResult())
  189. {
  190. return;
  191. }
  192. auto fileHandle = OpenModuleAt(moduleName);
  193. if (fileHandle->IsValid())
  194. {
  195. // add header
  196. AZ::u64 filesize = 0;
  197. AZ::IO::FileIOBase::GetInstance()->Size(fileHandle->m_handle, filesize);
  198. const bool needsHeader = (filesize == 0);
  199. AZStd::string buffer =
  200. m_pythonBehaviorDescription.GlobalPropertyDefinition(moduleName, propertyName, *behaviorProperty, needsHeader);
  201. AZ::IO::FileIOBase::GetInstance()->Write(*fileHandle, buffer.c_str(), buffer.size());
  202. }
  203. }
  204. void PythonLogSymbolsComponent::Finalize()
  205. {
  206. auto fileHandle = OpenInitFileAt("azlmbr.bus");
  207. if (fileHandle->IsValid())
  208. {
  209. AZStd::string buffer;
  210. AzFramework::StringFunc::Append(buffer, "# Bus dispatch types:\n");
  211. AzFramework::StringFunc::Append(buffer, "from typing_extensions import Final\n");
  212. AzFramework::StringFunc::Append(buffer, "Broadcast: Final[int] = 0\n");
  213. AzFramework::StringFunc::Append(buffer, "Event: Final[int] = 1\n");
  214. AzFramework::StringFunc::Append(buffer, "QueueBroadcast: Final[int] = 2\n");
  215. AzFramework::StringFunc::Append(buffer, "QueueEvent: Final[int] = 3\n");
  216. AZ::IO::FileIOBase::GetInstance()->Write(*fileHandle, buffer.c_str(), buffer.size());
  217. }
  218. fileHandle->Close();
  219. }
  220. void PythonLogSymbolsComponent::GetModuleList(AZStd::vector<AZStd::string_view>& moduleList) const
  221. {
  222. moduleList.clear();
  223. moduleList.reserve(m_moduleSet.size());
  224. AZStd::copy(m_moduleSet.begin(), m_moduleSet.end(), AZStd::back_inserter(moduleList));
  225. }
  226. void PythonLogSymbolsComponent::GetGlobalFunctionList(GlobalFunctionCollection& globalFunctionCollection) const
  227. {
  228. globalFunctionCollection.clear();
  229. for (const auto& globalFunctionMapEntry : m_globalFunctionMap)
  230. {
  231. const AZStd::string_view moduleName{ globalFunctionMapEntry.first };
  232. const GlobalFunctionList& moduleFunctionList = globalFunctionMapEntry.second;
  233. AZStd::transform(
  234. moduleFunctionList.begin(),
  235. moduleFunctionList.end(),
  236. AZStd::back_inserter(globalFunctionCollection),
  237. [moduleName](auto& entry) -> auto
  238. {
  239. const GlobalFunctionEntry& globalFunctionEntry = entry;
  240. const AZ::BehaviorMethod* behaviorMethod = entry.first;
  241. return AzToolsFramework::EditorPythonConsoleInterface::GlobalFunction(
  242. { moduleName, globalFunctionEntry.second, behaviorMethod->m_debugDescription });
  243. });
  244. }
  245. }
  246. PythonLogSymbolsComponent::FileHandlePtr PythonLogSymbolsComponent::OpenInitFileAt(AZStd::string_view moduleName)
  247. {
  248. if (m_basePath.empty())
  249. {
  250. return AZStd::make_shared<Internal::FileHandle>(AZ::IO::InvalidHandle);
  251. }
  252. // creates the __init__.py file in this path
  253. AZStd::string modulePath(moduleName);
  254. AzFramework::StringFunc::Replace(modulePath, ".", AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
  255. AZStd::string initFile;
  256. AzFramework::StringFunc::Path::Join(m_basePath.c_str(), modulePath.c_str(), initFile);
  257. AzFramework::StringFunc::Append(initFile, AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
  258. AzFramework::StringFunc::Append(initFile, "__init__.pyi");
  259. AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeText | AZ::IO::OpenMode::ModeWrite;
  260. AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
  261. AZ::IO::Result result = AZ::IO::FileIOBase::GetInstance()->Open(initFile.c_str(), openMode, fileHandle);
  262. if (result)
  263. {
  264. return AZStd::make_shared<Internal::FileHandle>(fileHandle);
  265. }
  266. return AZStd::make_shared<Internal::FileHandle>(AZ::IO::InvalidHandle);
  267. }
  268. PythonLogSymbolsComponent::FileHandlePtr PythonLogSymbolsComponent::OpenModuleAt(AZStd::string_view moduleName)
  269. {
  270. if (m_basePath.empty())
  271. {
  272. return AZStd::make_shared<Internal::FileHandle>(AZ::IO::InvalidHandle);
  273. }
  274. bool resetFile = false;
  275. if (m_moduleSet.find(moduleName) == m_moduleSet.end())
  276. {
  277. m_moduleSet.insert(moduleName);
  278. resetFile = true;
  279. }
  280. AZStd::vector<AZStd::string> moduleParts;
  281. AzFramework::StringFunc::Tokenize(moduleName.data(), moduleParts, '.');
  282. // prepare target PYI file
  283. AZStd::string targetModule = moduleParts.back();
  284. moduleParts.pop_back();
  285. AzFramework::StringFunc::Append(targetModule, ".pyi");
  286. // create an __init__.py file as the base module path
  287. AZStd::string initModule;
  288. AzFramework::StringFunc::Join(initModule, moduleParts.begin(), moduleParts.end(), '.');
  289. OpenInitFileAt(initModule);
  290. AZStd::string modulePath;
  291. AzFramework::StringFunc::Append(modulePath, m_basePath.c_str());
  292. AzFramework::StringFunc::Append(modulePath, AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
  293. AzFramework::StringFunc::Join(modulePath, moduleParts.begin(), moduleParts.end(), AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
  294. // prepare the path
  295. AZ::IO::FileIOBase::GetInstance()->CreatePath(modulePath.c_str());
  296. // assemble the file path
  297. AzFramework::StringFunc::Append(modulePath, AZ_CORRECT_FILESYSTEM_SEPARATOR_STRING);
  298. AzFramework::StringFunc::Append(modulePath, targetModule.c_str());
  299. AzFramework::StringFunc::AssetDatabasePath::Normalize(modulePath);
  300. AZ::IO::OpenMode openMode = AZ::IO::OpenMode::ModeText;
  301. if (AZ::IO::SystemFile::Exists(modulePath.c_str()))
  302. {
  303. openMode |= (resetFile) ? AZ::IO::OpenMode::ModeWrite : AZ::IO::OpenMode::ModeAppend;
  304. }
  305. else
  306. {
  307. openMode |= AZ::IO::OpenMode::ModeWrite;
  308. }
  309. AZ::IO::HandleType fileHandle = AZ::IO::InvalidHandle;
  310. AZ::IO::Result result = AZ::IO::FileIOBase::GetInstance()->Open(modulePath.c_str(), openMode, fileHandle);
  311. if (result)
  312. {
  313. return AZStd::make_shared<Internal::FileHandle>(fileHandle);
  314. }
  315. return AZStd::make_shared<Internal::FileHandle>(AZ::IO::InvalidHandle);
  316. }
  317. } // namespace EditorPythonBindings