assetUtils.cpp 71 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848
  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 "assetUtils.h"
  9. #include <AzCore/Component/ComponentApplication.h>
  10. #include <AzCore/Math/Sha1.h>
  11. #include <native/assetprocessor.h>
  12. #include <native/utilities/PlatformConfiguration.h>
  13. #include <native/utilities/StatsCapture.h>
  14. #include <native/AssetManager/FileStateCache.h>
  15. #include <native/AssetDatabase/AssetDatabase.h>
  16. #include <utilities/ThreadHelper.h>
  17. #include <QCoreApplication>
  18. #include <QElapsedTimer>
  19. #include <QTemporaryDir>
  20. #include <QTextStream>
  21. #include <QTimeZone>
  22. #include <QRandomGenerator>
  23. #include <AzQtComponents/Utilities/RandomNumberGenerator.h>
  24. #if defined(__APPLE__)
  25. #include <mach-o/dyld.h>
  26. #include <libgen.h>
  27. #include <unistd.h>
  28. #elif defined(AZ_PLATFORM_LINUX)
  29. #include <libgen.h>
  30. #endif
  31. #include <AzCore/JSON/document.h>
  32. #include <AzCore/Settings/SettingsRegistryMergeUtils.h>
  33. #include <AzCore/Utils/Utils.h>
  34. #include <AzFramework/API/ApplicationAPI.h>
  35. #include <AzFramework/Platform/PlatformDefaults.h>
  36. #include <AzToolsFramework/UI/Logging/LogLine.h>
  37. #include <xxhash/xxhash.h>
  38. #include <native/utilities/UuidManager.h>
  39. #if defined(AZ_PLATFORM_WINDOWS)
  40. # include <windows.h>
  41. #else
  42. # include <QFile>
  43. #endif
  44. #if defined(AZ_PLATFORM_APPLE) || defined(AZ_PLATFORM_LINUX)
  45. #include <fcntl.h>
  46. #endif
  47. #if defined(AZ_PLATFORM_LINUX)
  48. #include <sys/types.h>
  49. #include <sys/stat.h>
  50. #include <fcntl.h>
  51. #endif // defined(AZ_PLATFORM_LINUX)
  52. #include <sstream>
  53. namespace AssetUtilsInternal
  54. {
  55. static const unsigned int g_RetryWaitInterval = 250; // The amount of time that we are waiting for retry.
  56. // This is because Qt has to init random number gen on each thread.
  57. AZ_THREAD_LOCAL bool g_hasInitializedRandomNumberGenerator = false;
  58. // so that even if we do init two seeds at exactly the same msec time, theres still this extra
  59. // changing number
  60. static AZStd::atomic_int g_randomNumberSequentialSeed;
  61. bool FileCopyMoveWithTimeout(QString sourceFile, QString outputFile, bool isCopy, unsigned int waitTimeInSeconds)
  62. {
  63. bool failureOccurredOnce = false; // used for logging.
  64. bool operationSucceeded = false;
  65. QFile outFile(outputFile);
  66. QElapsedTimer timer;
  67. timer.start();
  68. do
  69. {
  70. QString normalized = AssetUtilities::NormalizeFilePath(outputFile);
  71. AssetProcessor::ProcessingJobInfoBus::Broadcast(
  72. &AssetProcessor::ProcessingJobInfoBus::Events::BeginCacheFileUpdate, normalized.toUtf8().constData());
  73. //Removing the old file if it exists
  74. if (outFile.exists())
  75. {
  76. if (!outFile.remove())
  77. {
  78. if (!failureOccurredOnce)
  79. {
  80. // This is not a warning because there is retry logic in place.
  81. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Unable to remove file %s to copy source file %s in... (We may retry)\n", outputFile.toUtf8().constData(), sourceFile.toUtf8().constData());
  82. failureOccurredOnce = true;
  83. }
  84. //not able to remove the file
  85. if (waitTimeInSeconds != 0)
  86. {
  87. //Sleep only for non zero waitTime
  88. QThread::msleep(AssetUtilsInternal::g_RetryWaitInterval);
  89. }
  90. continue;
  91. }
  92. }
  93. //ensure that the output dir is present
  94. QFileInfo outFileInfo(outputFile);
  95. if (!outFileInfo.absoluteDir().mkpath("."))
  96. {
  97. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Failed to create directory (%s).\n", outFileInfo.absolutePath().toUtf8().data());
  98. return false;
  99. }
  100. if (isCopy && QFile::copy(sourceFile, outputFile))
  101. {
  102. //Success
  103. operationSucceeded = true;
  104. break;
  105. }
  106. else if (!isCopy && QFile::rename(sourceFile, outputFile))
  107. {
  108. //Success
  109. operationSucceeded = true;
  110. break;
  111. }
  112. else
  113. {
  114. failureOccurredOnce = true;
  115. if (waitTimeInSeconds != 0)
  116. {
  117. //Sleep only for non zero waitTime
  118. QThread::msleep(AssetUtilsInternal::g_RetryWaitInterval);
  119. }
  120. }
  121. } while (!timer.hasExpired(waitTimeInSeconds * 1000)); //We will keep retrying until the timer has expired the inputted timeout
  122. // once we're done, regardless of success or failure, we 'unlock' those files for further process.
  123. // if we failed, also re-trigger them to rebuild (the bool param at the end of the ebus call)
  124. QString normalized = AssetUtilities::NormalizeFilePath(outputFile);
  125. AssetProcessor::ProcessingJobInfoBus::Broadcast(
  126. &AssetProcessor::ProcessingJobInfoBus::Events::EndCacheFileUpdate, normalized.toUtf8().constData(), !operationSucceeded);
  127. if (!operationSucceeded)
  128. {
  129. //operation failed for the given timeout
  130. AZ_Warning(AssetProcessor::ConsoleChannel, false, "WARNING: Could not %s source from %s to %s, giving up\n",
  131. isCopy ? "copy" : "move (via rename)",
  132. sourceFile.toUtf8().constData(), outputFile.toUtf8().constData());
  133. return false;
  134. }
  135. else if (failureOccurredOnce)
  136. {
  137. // if we failed once, write to log to indicate that we eventually succeeded.
  138. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "SUCCESS: after failure, we later succeeded to copy/move file %s\n", outputFile.toUtf8().constData());
  139. }
  140. return true;
  141. }
  142. static bool DumpAssetProcessorUserSettingsToFile(AZ::SettingsRegistryInterface& settingsRegistry,
  143. const AZ::IO::FixedMaxPath& setregPath)
  144. {
  145. // The AssetProcessor settings are currently under the Bootstrap object(This may change in the future
  146. constexpr AZStd::string_view AssetProcessorUserSettingsRootKey = AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey;
  147. AZStd::string apSettingsJson;
  148. AZ::IO::ByteContainerStream apSettingsStream(&apSettingsJson);
  149. AZ::SettingsRegistryMergeUtils::DumperSettings apDumperSettings;
  150. apDumperSettings.m_prettifyOutput = true;
  151. AZ_PUSH_DISABLE_WARNING(5233, "-Wunknown-warning-option") // Older versions of MSVC toolchain require to pass constexpr in the
  152. // capture. Newer versions issue unused warning
  153. apDumperSettings.m_includeFilter = [&AssetProcessorUserSettingsRootKey](AZStd::string_view path)
  154. AZ_POP_DISABLE_WARNING
  155. {
  156. // The AssetUtils only updates the following keys in the registry
  157. // Dump them all out to the setreg file
  158. auto allowedListKey = AZ::SettingsRegistryInterface::FixedValueString(AssetProcessorUserSettingsRootKey)
  159. + "/allowed_list";
  160. auto branchTokenKey = AZ::SettingsRegistryInterface::FixedValueString(AssetProcessorUserSettingsRootKey)
  161. + "/assetProcessor_branch_token";
  162. // The objects leading up to the keys to dump must be included in order the keys to be dumped
  163. return allowedListKey.starts_with(path.substr(0, allowedListKey.size()))
  164. || branchTokenKey.starts_with(path.substr(0, branchTokenKey.size()));
  165. };
  166. apDumperSettings.m_jsonPointerPrefix = AssetProcessorUserSettingsRootKey;
  167. if (AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream(settingsRegistry, AssetProcessorUserSettingsRootKey,
  168. apSettingsStream, apDumperSettings))
  169. {
  170. constexpr const char* AssetProcessorTmpSetreg = "asset_processor.setreg.tmp";
  171. // Write to a temporary file first before renaming it to the final file location
  172. // This is needed to reduce the potential of a race condition which occurs when other applications attempt to load settings registry
  173. // files from the project's user Registry folder while the AssetProcessor is writing the file out the asset_processor.setreg
  174. // at the same time
  175. QString tempDirValue;
  176. AssetUtilities::CreateTempWorkspace(tempDirValue);
  177. QDir tempDir(tempDirValue);
  178. AZ::IO::FixedMaxPath tmpSetregPath = tempDir.absoluteFilePath(QString(AssetProcessorTmpSetreg)).toUtf8().data();
  179. constexpr auto modeFlags = AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY | AZ::IO::SystemFile::SF_OPEN_CREATE
  180. | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH;
  181. if (AZ::IO::SystemFile apSetregFile; apSetregFile.Open(tmpSetregPath.c_str(), modeFlags))
  182. {
  183. size_t bytesWritten = apSetregFile.Write(apSettingsJson.data(), apSettingsJson.size());
  184. // Close the file so that it can be renamed.
  185. apSetregFile.Close();
  186. if (bytesWritten == apSettingsJson.size())
  187. {
  188. // Create the directory to contain the moved setreg file
  189. AZ::IO::SystemFile::CreateDir(AZ::IO::FixedMaxPath(setregPath.ParentPath()).c_str());
  190. return AZ::IO::SystemFile::Rename(tmpSetregPath.c_str(), setregPath.c_str(), true);
  191. }
  192. }
  193. else
  194. {
  195. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Unable to open AssetProcessor user setreg file (%s)\n", setregPath.c_str());
  196. }
  197. }
  198. else
  199. {
  200. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Dump of AssetProcessor User Settings failed at JSON pointer %.*s \n",
  201. aznumeric_cast<int>(AssetProcessorUserSettingsRootKey.size()), AssetProcessorUserSettingsRootKey.data());
  202. }
  203. return false;
  204. }
  205. }
  206. namespace AssetUtilities
  207. {
  208. constexpr AZStd::string_view AssetProcessorUserSetregRelPath = "user/Registry/asset_processor.setreg";
  209. // do not place Qt objects in global scope, they allocate and refcount threaded data.
  210. AZ::SettingsRegistryInterface::FixedValueString s_projectPath;
  211. AZ::SettingsRegistryInterface::FixedValueString s_projectName;
  212. AZ::SettingsRegistryInterface::FixedValueString s_assetRoot;
  213. AZ::SettingsRegistryInterface::FixedValueString s_cachedEngineRoot;
  214. int s_truncateFingerprintTimestampPrecision{ 1 };
  215. AZStd::optional<bool> s_fileHashOverride{};
  216. AZStd::optional<bool> s_fileHashSetting{};
  217. void SetTruncateFingerprintTimestamp(int precision)
  218. {
  219. s_truncateFingerprintTimestampPrecision = precision;
  220. }
  221. void SetUseFileHashOverride(bool override, bool enable)
  222. {
  223. if(override)
  224. {
  225. s_fileHashOverride = enable ? 1 : 0;
  226. }
  227. else
  228. {
  229. s_fileHashOverride.reset();
  230. }
  231. }
  232. void ResetAssetRoot()
  233. {
  234. s_assetRoot = {};
  235. s_cachedEngineRoot = {};
  236. }
  237. void ResetGameName()
  238. {
  239. s_projectName = {};
  240. }
  241. bool CopyDirectory(QDir source, QDir destination)
  242. {
  243. if (!destination.exists())
  244. {
  245. bool result = destination.mkpath(".");
  246. if (!result)
  247. {
  248. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Failed to create directory (%s).\n", destination.absolutePath().toUtf8().data());
  249. return false;
  250. }
  251. }
  252. QFileInfoList entries = source.entryInfoList(QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs);
  253. for (const QFileInfo& entry : entries)
  254. {
  255. if (entry.isDir())
  256. {
  257. //if the entry is a directory than recursively call the function
  258. if (!CopyDirectory(QDir(source.absolutePath() + "/" + entry.completeBaseName()), QDir(destination.absolutePath() + "/" + entry.completeBaseName())))
  259. {
  260. return false;
  261. }
  262. }
  263. else
  264. {
  265. //if the entry is a file than copy it but before than ensure that the destination file is not present
  266. QString destinationFile = destination.absolutePath() + "/" + entry.fileName();
  267. if (QFile::exists(destinationFile))
  268. {
  269. if (!QFile::remove(destinationFile))
  270. {
  271. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Unable to remove file (%s).\n", destinationFile.toUtf8().data());
  272. return false;
  273. }
  274. }
  275. QString sourceFile = source.absolutePath() + "/" + entry.fileName();
  276. if (!QFile::copy(sourceFile, destinationFile))
  277. {
  278. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Unable to copy sourcefile (%s) to destination (%s).\n", sourceFile.toUtf8().data(), destinationFile.toUtf8().data());
  279. return false;
  280. }
  281. }
  282. }
  283. return true;
  284. }
  285. bool ComputeAssetRoot(QDir& root, const QDir* rootOverride)
  286. {
  287. if (!s_assetRoot.empty())
  288. {
  289. root = QDir(QString::fromUtf8(s_assetRoot.c_str(), aznumeric_cast<int>(s_assetRoot.size())));
  290. return true;
  291. }
  292. // Use the override if supplied and not an empty string
  293. if (rootOverride && !rootOverride->path().isEmpty())
  294. {
  295. root = *rootOverride;
  296. s_assetRoot = root.absolutePath().toUtf8().constData();
  297. return true;
  298. }
  299. const AzFramework::CommandLine* commandLine = nullptr;
  300. AzFramework::ApplicationRequests::Bus::BroadcastResult(commandLine, &AzFramework::ApplicationRequests::GetCommandLine);
  301. static const char AssetRootParam[] = "assetroot";
  302. if (commandLine && commandLine->HasSwitch(AssetRootParam))
  303. {
  304. s_assetRoot = commandLine->GetSwitchValue(AssetRootParam, 0).c_str();
  305. root = QDir(s_assetRoot.c_str());
  306. return true;
  307. }
  308. AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
  309. if (settingsRegistry == nullptr)
  310. {
  311. AZ_Warning("AssetProcessor", false, "Unable to retrieve Global SettingsRegistry at this time."
  312. " Has a ComponentApplication(or a class derived from ComponentApplication) been constructed yet?");
  313. return false;
  314. }
  315. AZ::IO::FixedMaxPathString engineRootFolder;
  316. if (settingsRegistry->Get(engineRootFolder, AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
  317. {
  318. root = QDir(QString::fromUtf8(engineRootFolder.c_str(), aznumeric_cast<int>(engineRootFolder.size())));
  319. s_assetRoot = root.absolutePath().toUtf8().constData();
  320. return true;
  321. }
  322. // The EngineRootFolder Key has not been found in the SettingsRegistry
  323. auto engineRootError = AZ::SettingsRegistryInterface::FixedValueString::format("The EngineRootFolder is not set in the SettingsRegistry at key %s.",
  324. AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder);
  325. AssetProcessor::MessageInfoBus::Broadcast(&AssetProcessor::MessageInfoBusTraits::OnErrorMessage, engineRootError.c_str());
  326. return false;
  327. }
  328. //! Get the external engine root folder if the engine is external to the current root folder.
  329. //! If the current root folder is also the engine folder, then this behaves the same as ComputeEngineRoot
  330. bool ComputeEngineRoot(QDir& root, const QDir* engineRootOverride)
  331. {
  332. if (!s_cachedEngineRoot.empty())
  333. {
  334. root = QDir(QString::fromUtf8(s_cachedEngineRoot.c_str(), aznumeric_cast<int>(s_cachedEngineRoot.size())));
  335. return true;
  336. }
  337. // Compute the AssetRoot if it is empty as well
  338. if (s_assetRoot.empty())
  339. {
  340. AssetUtilities::ComputeAssetRoot(root, engineRootOverride);
  341. }
  342. AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
  343. // Use the engineRootOverride if supplied and not empty
  344. if (engineRootOverride && !engineRootOverride->path().isEmpty())
  345. {
  346. root = *engineRootOverride;
  347. s_cachedEngineRoot = root.absolutePath().toUtf8().constData();
  348. return true;
  349. }
  350. if (settingsRegistry == nullptr)
  351. {
  352. return false;
  353. }
  354. AZ::IO::FixedMaxPathString engineRootFolder;
  355. if (settingsRegistry->Get(engineRootFolder, AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder))
  356. {
  357. root = QDir(QString::fromUtf8(engineRootFolder.c_str(), aznumeric_cast<int>(engineRootFolder.size())));
  358. s_cachedEngineRoot = root.absolutePath().toUtf8().constData();
  359. return true;
  360. }
  361. return false;
  362. }
  363. bool MakeFileWritable(const QString& fileName)
  364. {
  365. #if defined WIN32
  366. DWORD fileAttributes = GetFileAttributesA(fileName.toUtf8());
  367. if (fileAttributes == INVALID_FILE_ATTRIBUTES)
  368. {
  369. //file does not exist
  370. return false;
  371. }
  372. if ((fileAttributes & FILE_ATTRIBUTE_READONLY))
  373. {
  374. fileAttributes = fileAttributes & ~(FILE_ATTRIBUTE_READONLY);
  375. if (SetFileAttributesA(fileName.toUtf8(), fileAttributes))
  376. {
  377. return true;
  378. }
  379. return false;
  380. }
  381. else
  382. {
  383. //file is writeable
  384. return true;
  385. }
  386. #else
  387. QFileInfo fileInfo(fileName);
  388. if (!fileInfo.exists())
  389. {
  390. return false;
  391. }
  392. if (fileInfo.permission(QFile::WriteUser))
  393. {
  394. // file already has the write permission
  395. return true;
  396. }
  397. else
  398. {
  399. QFile::Permissions filePermissions = fileInfo.permissions();
  400. if (QFile::setPermissions(fileName, filePermissions | QFile::WriteUser))
  401. {
  402. //write permission added
  403. return true;
  404. }
  405. return false;
  406. }
  407. #endif
  408. }
  409. bool CheckCanLock(const QString& fileName)
  410. {
  411. #if defined(AZ_PLATFORM_WINDOWS)
  412. AZStd::wstring usableFileName;
  413. usableFileName.resize(fileName.length(), 0);
  414. fileName.toWCharArray(usableFileName.data());
  415. // third parameter dwShareMode (0) prevents share access
  416. const DWORD dwShareMode = 0;
  417. HANDLE fileHandle = CreateFileW(usableFileName.c_str(), GENERIC_READ, dwShareMode, nullptr, OPEN_EXISTING, 0, 0);
  418. if (fileHandle != INVALID_HANDLE_VALUE)
  419. {
  420. CloseHandle(fileHandle);
  421. return true;
  422. }
  423. return false;
  424. #else
  425. int open_flags = O_RDONLY | O_NONBLOCK;
  426. #if defined(PLATFORM_APPLE)
  427. // O_EXLOCK only supported on APPLE
  428. open_flags |= O_EXLOCK
  429. #endif
  430. int handle = open(fileName.toUtf8().constData(), open_flags);
  431. if (handle != -1)
  432. {
  433. close(handle);
  434. return true;
  435. }
  436. return false;
  437. #endif
  438. }
  439. QString ComputeProjectName(QString gameNameOverride, bool force)
  440. {
  441. if (force || s_projectName.empty())
  442. {
  443. // Override Game Name if a non-empty override string has been supplied
  444. if (!gameNameOverride.isEmpty())
  445. {
  446. s_projectName = gameNameOverride.toUtf8().constData();
  447. }
  448. else
  449. {
  450. s_projectName = AZ::Utils::GetProjectName();
  451. }
  452. }
  453. return QString::fromUtf8(s_projectName.c_str(), aznumeric_cast<int>(s_projectName.size()));
  454. }
  455. QString ComputeProjectPath(bool resetCachedProjectPath/*=false*/)
  456. {
  457. if (resetCachedProjectPath)
  458. {
  459. // Clear any cached value if reset was requested
  460. s_projectPath.clear();
  461. }
  462. if (s_projectPath.empty())
  463. {
  464. // Check command-line args first
  465. QStringList args = QCoreApplication::arguments();
  466. for (QString arg : args)
  467. {
  468. if (arg.contains(QString("/%1=").arg(ProjectPathOverrideParameter), Qt::CaseInsensitive)
  469. || arg.contains(QString("--%1=").arg(ProjectPathOverrideParameter), Qt::CaseInsensitive))
  470. {
  471. QString rawValueString = arg.split("=")[1].trimmed();
  472. if (!rawValueString.isEmpty())
  473. {
  474. QDir path(rawValueString);
  475. if (path.isAbsolute())
  476. {
  477. s_projectPath = rawValueString.toUtf8().constData();
  478. break;
  479. }
  480. }
  481. }
  482. }
  483. }
  484. if (s_projectPath.empty())
  485. {
  486. s_projectPath = AZ::Utils::GetProjectPath();
  487. }
  488. return QString::fromUtf8(s_projectPath.c_str(), aznumeric_cast<int>(s_projectPath.size()));
  489. }
  490. bool ShouldUseFileHashing()
  491. {
  492. // Check if the settings file is overridden, if so, use the override instead
  493. if(s_fileHashOverride)
  494. {
  495. return *s_fileHashOverride;
  496. }
  497. // Check if we read the settings file already, if so, use the cached value
  498. if(s_fileHashSetting)
  499. {
  500. return *s_fileHashSetting;
  501. }
  502. auto settingsRegistry = AZ::SettingsRegistry::Get();
  503. if (settingsRegistry)
  504. {
  505. bool curValue = true;
  506. settingsRegistry->Get(curValue, AZ::SettingsRegistryInterface::FixedValueString(AssetProcessor::AssetProcessorSettingsKey)
  507. + "/Fingerprinting/UseFileHashing");
  508. AZ_TracePrintf(AssetProcessor::DebugChannel, "UseFileHashing: %s\n", curValue ? "True" : "False");
  509. s_fileHashSetting = curValue;
  510. return curValue;
  511. }
  512. AZ_TracePrintf(AssetProcessor::DebugChannel, "No UseFileHashing setting found\n");
  513. s_fileHashSetting = true;
  514. return *s_fileHashSetting;
  515. }
  516. QString ReadAllowedlistFromSettingsRegistry([[maybe_unused]] QString initialFolder)
  517. {
  518. AZ::SettingsRegistryInterface::FixedValueString allowedListKey{ AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey };
  519. allowedListKey += "/allowed_list";
  520. AZ::SettingsRegistryInterface::FixedValueString allowedListIp;
  521. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry && settingsRegistry->Get(allowedListIp, allowedListKey))
  522. {
  523. return QString::fromUtf8(allowedListIp.c_str(), aznumeric_cast<int>(allowedListIp.size()));
  524. }
  525. return {};
  526. }
  527. QString ReadRemoteIpFromSettingsRegistry([[maybe_unused]] QString initialFolder)
  528. {
  529. AZ::SettingsRegistryInterface::FixedValueString remoteIpKey{ AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey };
  530. remoteIpKey += "/remote_ip";
  531. AZ::SettingsRegistryInterface::FixedValueString remoteIp;
  532. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry && settingsRegistry->Get(remoteIp, remoteIpKey))
  533. {
  534. return QString::fromUtf8(remoteIp.c_str(), aznumeric_cast<int>(remoteIp.size()));
  535. }
  536. return {};
  537. }
  538. bool WriteAllowedlistToSettingsRegistry(const QStringList& newAllowedList)
  539. {
  540. AZ::IO::FixedMaxPath assetProcessorUserSetregPath = AZ::Utils::GetProjectPath();
  541. assetProcessorUserSetregPath /= AssetProcessorUserSetregRelPath;
  542. auto settingsRegistry = AZ::SettingsRegistry::Get();
  543. if (!settingsRegistry)
  544. {
  545. AZ_Error(AssetProcessor::ConsoleChannel, false, "Unable access Settings Registry. Branch Token cannot be updated");
  546. return false;
  547. }
  548. auto allowedListKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey)
  549. + "/allowed_list";
  550. AZStd::string currentAllowedList;
  551. if (settingsRegistry->Get(currentAllowedList, allowedListKey))
  552. {
  553. // Split the current allowedList into an array and compare against the new allowed list
  554. AZStd::vector<AZStd::string_view> allowedListArray;
  555. auto AppendAllowedIpTokens = [&allowedListArray](AZStd::string_view token) { allowedListArray.emplace_back(token); };
  556. AZ::StringFunc::TokenizeVisitor(currentAllowedList, AppendAllowedIpTokens, ',');
  557. auto CompareQListToAzVector = [](AZStd::string_view currentAllowedIp, const QString& newAllowedIp)
  558. {
  559. return currentAllowedIp == newAllowedIp.toUtf8().constData();
  560. };
  561. if (AZStd::equal(allowedListArray.begin(), allowedListArray.end(), newAllowedList.begin(), newAllowedList.end(), CompareQListToAzVector))
  562. {
  563. // no need to update, remote_ip already matches
  564. return true;
  565. }
  566. }
  567. // Update Settings Registry with new token
  568. AZStd::string azNewAllowedList{ newAllowedList.join(',').toUtf8().constData() };
  569. settingsRegistry->Set(allowedListKey, azNewAllowedList);
  570. return AssetUtilsInternal::DumpAssetProcessorUserSettingsToFile(*settingsRegistry, assetProcessorUserSetregPath);
  571. }
  572. quint16 ReadListeningPortFromSettingsRegistry(QString initialFolder /*= QString()*/)
  573. {
  574. if (initialFolder.isEmpty())
  575. {
  576. QDir engineRoot;
  577. if (!AssetUtilities::ComputeEngineRoot(engineRoot))
  578. {
  579. //return the default port
  580. return 45643;
  581. }
  582. initialFolder = engineRoot.absolutePath();
  583. }
  584. AZ::SettingsRegistryInterface::FixedValueString remotePortKey{ AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey };
  585. remotePortKey += "/remote_port";
  586. AZ::s64 portNumber;
  587. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry && settingsRegistry->Get(portNumber, remotePortKey))
  588. {
  589. return aznumeric_cast<quint16>(portNumber);
  590. }
  591. //return the default port
  592. return 45643;
  593. }
  594. QStringList ReadPlatformsFromCommandLine()
  595. {
  596. QStringList args = QCoreApplication::arguments();
  597. for (QString arg : args)
  598. {
  599. if (arg.contains("--platforms=", Qt::CaseInsensitive) || arg.contains("/platforms=", Qt::CaseInsensitive))
  600. {
  601. QString rawPlatformString = arg.split("=")[1];
  602. return rawPlatformString.split(",");
  603. }
  604. }
  605. return QStringList();
  606. }
  607. bool CopyFileWithTimeout(QString sourceFile, QString outputFile, unsigned int waitTimeInSeconds)
  608. {
  609. return AssetUtilsInternal::FileCopyMoveWithTimeout(sourceFile, outputFile, true, waitTimeInSeconds);
  610. }
  611. bool MoveFileWithTimeout(QString sourceFile, QString outputFile, unsigned int waitTimeInSeconds)
  612. {
  613. return AssetUtilsInternal::FileCopyMoveWithTimeout(sourceFile, outputFile, false, waitTimeInSeconds);
  614. }
  615. bool CreateDirectoryWithTimeout(QDir dir, unsigned int waitTimeinSeconds)
  616. {
  617. if (dir.exists())
  618. {
  619. return true;
  620. }
  621. [[maybe_unused]] int retries = 0;
  622. QElapsedTimer timer;
  623. timer.start();
  624. do
  625. {
  626. retries++;
  627. // Try to create the directory path
  628. if (dir.mkpath("."))
  629. {
  630. return true;
  631. }
  632. else
  633. {
  634. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Unable to create output directory path: %s retrying.\n", dir.absolutePath().toUtf8().data());
  635. }
  636. if (dir.exists())
  637. {
  638. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Output directory: %s created by another operation.\n", dir.absolutePath().toUtf8().data());
  639. return true;
  640. }
  641. if (waitTimeinSeconds != 0)
  642. {
  643. QThread::msleep(AssetUtilsInternal::g_RetryWaitInterval);
  644. }
  645. } while (!timer.hasExpired(waitTimeinSeconds * 1000));
  646. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Failed to create output directory: %s after %d retries.\n", dir.absolutePath().toUtf8().data(), retries);
  647. return false;
  648. }
  649. QString NormalizeAndRemoveAlias(QString path)
  650. {
  651. QString normalizedPath = NormalizeFilePath(path);
  652. if (normalizedPath.startsWith("@"))
  653. {
  654. int aliasEndIndex = normalizedPath.indexOf("@/", Qt::CaseInsensitive);
  655. if (aliasEndIndex != -1)
  656. {
  657. normalizedPath.remove(0, aliasEndIndex + 2);// adding two to remove both the percentage sign and the native separator
  658. }
  659. else
  660. {
  661. //try finding the second % index than,maybe the path is like @SomeAlias@somefolder/somefile.ext
  662. aliasEndIndex = normalizedPath.indexOf("@", 1, Qt::CaseInsensitive);
  663. if (aliasEndIndex != -1)
  664. {
  665. normalizedPath.remove(0, aliasEndIndex + 1); //adding one to remove the percentage sign only
  666. }
  667. }
  668. }
  669. return normalizedPath;
  670. }
  671. bool ComputeProjectCacheRoot(QDir& projectCacheRoot)
  672. {
  673. if (auto registry = AZ::SettingsRegistry::Get(); registry != nullptr)
  674. {
  675. AZ::SettingsRegistryInterface::FixedValueString projectCacheRootValue;
  676. if (registry->Get(projectCacheRootValue, AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder);
  677. !projectCacheRootValue.empty())
  678. {
  679. projectCacheRoot = QDir(QString::fromUtf8(projectCacheRootValue.c_str(), aznumeric_cast<int>(projectCacheRootValue.size())));
  680. return true;
  681. }
  682. }
  683. return false;
  684. }
  685. bool ComputeFenceDirectory(QDir& fenceDir)
  686. {
  687. QDir cacheRoot;
  688. if (!AssetUtilities::ComputeProjectCacheRoot(cacheRoot))
  689. {
  690. return false;
  691. }
  692. fenceDir = QDir(cacheRoot.filePath("fence"));
  693. return true;
  694. }
  695. AZStd::string_view StripAssetPlatformNoCopy(AZStd::string_view relativeProductPath, AZStd::string_view* outputPlatform)
  696. {
  697. // Skip over the assetPlatform path segment if it is matches one of the platform defaults
  698. // Otherwise return the path unchanged
  699. AZStd::string_view originalPath = relativeProductPath;
  700. AZStd::optional firstPathSegment = AZ::StringFunc::TokenizeNext(relativeProductPath, AZ_CORRECT_AND_WRONG_FILESYSTEM_SEPARATOR);
  701. if (firstPathSegment && (AzFramework::PlatformHelper::GetPlatformIdFromName(*firstPathSegment) != AzFramework::PlatformId::Invalid
  702. || firstPathSegment == AssetBuilderSDK::CommonPlatformName))
  703. {
  704. if(outputPlatform)
  705. {
  706. *outputPlatform = *firstPathSegment;
  707. }
  708. return relativeProductPath;
  709. }
  710. return originalPath;
  711. }
  712. QString StripAssetPlatform(AZStd::string_view relativeProductPath)
  713. {
  714. AZStd::string_view result = StripAssetPlatformNoCopy(relativeProductPath);
  715. return QString::fromUtf8(result.data(), aznumeric_cast<int>(result.size()));
  716. }
  717. QString NormalizeFilePath(const QString& filePath)
  718. {
  719. // do NOT convert to absolute paths here, we just want to manipulate the string itself.
  720. QString returnString = filePath;
  721. // QDir::cleanPath only replaces backslashes with forward slashes in the input string if the OS
  722. // it is currently natively running on uses backslashes as its native path separator.
  723. // see https://github.com/qt/qtbase/blob/40143c189b7c1bf3c2058b77d00ea5c4e3be8b28/src/corelib/io/qdir.cpp#L2357
  724. // This assumption is incorrect in this application - it can receive file paths from data files created on
  725. // backslash operating systems even if its a non-backslash operating system.
  726. // we can skip this step in the cases where cleanPath will do it for us:
  727. if (QDir::separator() == QLatin1Char('/'))
  728. {
  729. returnString.replace(QLatin1Char('\\'), QLatin1Char('/'));
  730. }
  731. // cleanPath to remove/resolve .. and . and any extra slashes, and remove any trailing slashes.
  732. returnString = QDir::cleanPath(returnString);
  733. #if defined(AZ_PLATFORM_WINDOWS)
  734. // windows has an additional idiosyncrasy - it returns upper and lower case drive letters
  735. // from various APIs differently. we will settle on upper case as the standard.
  736. if ((returnString.length() > 1) && (returnString.at(1) == ':'))
  737. {
  738. QCharRef firstChar = returnString[0]; // QCharRef allows you to modify the string in place.
  739. firstChar = firstChar.toUpper();
  740. }
  741. #endif
  742. return returnString;
  743. }
  744. QString NormalizeDirectoryPath(const QString& directoryPath)
  745. {
  746. QString dirPath(NormalizeFilePath(directoryPath));
  747. while ((dirPath.endsWith('/')))
  748. {
  749. dirPath.resize(dirPath.length() - 1);
  750. }
  751. return dirPath;
  752. }
  753. AZ::Uuid CreateSafeSourceUUIDFromName(const char* sourceName, bool caseInsensitive)
  754. {
  755. AZStd::string lowerVersion(sourceName);
  756. if (caseInsensitive)
  757. {
  758. AZStd::to_lower(lowerVersion.begin(), lowerVersion.end());
  759. }
  760. AzFramework::StringFunc::Replace(lowerVersion, '\\', '/');
  761. return AZ::Uuid::CreateName(lowerVersion.c_str());
  762. }
  763. AZ::Outcome<AZ::Uuid, AZStd::string> GetSourceUuid(const AssetProcessor::SourceAssetReference& sourceAsset)
  764. {
  765. if (!sourceAsset)
  766. {
  767. return {};
  768. }
  769. auto* uuidRequests = AZ::Interface<AssetProcessor::IUuidRequests>::Get();
  770. if (uuidRequests)
  771. {
  772. return uuidRequests->GetUuid(sourceAsset);
  773. }
  774. AZ_Assert(false, "Programmer Error: GetSourceUuid called before IUuidRequests interface is available.");
  775. return {};
  776. }
  777. AZ::Outcome<AZStd::unordered_set<AZ::Uuid>, AZStd::string> GetLegacySourceUuids(const AssetProcessor::SourceAssetReference& sourceAsset)
  778. {
  779. auto* uuidRequests = AZ::Interface<AssetProcessor::IUuidRequests>::Get();
  780. if (uuidRequests)
  781. {
  782. return uuidRequests->GetLegacyUuids(sourceAsset);
  783. }
  784. AZ_Assert(false, "Programmer Error: GetSourceUuid called before IUuidRequests interface is available.");
  785. return {};
  786. }
  787. void NormalizeFilePaths(QStringList& filePaths)
  788. {
  789. for (int pathIdx = 0; pathIdx < filePaths.size(); ++pathIdx)
  790. {
  791. filePaths[pathIdx] = NormalizeFilePath(filePaths[pathIdx]);
  792. }
  793. }
  794. unsigned int ComputeCRC32(const char* inString, unsigned int priorCRC)
  795. {
  796. AZ::Crc32 crc(priorCRC != -1 ? priorCRC : 0U);
  797. crc.Add(inString, ::strlen(inString), false);
  798. return crc;
  799. }
  800. unsigned int ComputeCRC32(const char* data, size_t dataSize, unsigned int priorCRC)
  801. {
  802. AZ::Crc32 crc(priorCRC != -1 ? priorCRC : 0U);
  803. crc.Add(data, dataSize, false);
  804. return crc;
  805. }
  806. unsigned int ComputeCRC32Lowercase(const char* inString, unsigned int priorCRC)
  807. {
  808. AZ::Crc32 crc(priorCRC != -1 ? priorCRC : 0U);
  809. crc.Add(inString); // note that the char* version of Add() sets lowercase to be true by default.
  810. return crc;
  811. }
  812. unsigned int ComputeCRC32Lowercase(const char* data, size_t dataSize, unsigned int priorCRC)
  813. {
  814. AZ::Crc32 crc(priorCRC != -1 ? priorCRC : 0U);
  815. crc.Add(data, dataSize, true);
  816. return crc;
  817. }
  818. bool UpdateBranchToken()
  819. {
  820. AZ::IO::FixedMaxPath assetProcessorUserSetregPath = AZ::Utils::GetProjectPath();
  821. assetProcessorUserSetregPath /= AssetProcessorUserSetregRelPath;
  822. AZStd::string appBranchToken;
  823. AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::CalculateBranchTokenForEngineRoot, appBranchToken);
  824. auto settingsRegistry = AZ::SettingsRegistry::Get();
  825. if (!settingsRegistry)
  826. {
  827. AZ_Error(AssetProcessor::ConsoleChannel, false, "Unable access Settings Registry. Branch Token cannot be updated");
  828. return false;
  829. }
  830. AZStd::string registryBranchToken;
  831. auto branchTokenKey = AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/assetProcessor_branch_token";
  832. if (settingsRegistry->Get(registryBranchToken, branchTokenKey))
  833. {
  834. if (appBranchToken == registryBranchToken)
  835. {
  836. // no need to update, branch token match
  837. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Branch token (%s) is already correct in (%s)\n", appBranchToken.c_str(), assetProcessorUserSetregPath.c_str());
  838. return true;
  839. }
  840. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Updating branch token (%s) in (%s)\n", appBranchToken.c_str(), assetProcessorUserSetregPath.c_str());
  841. }
  842. else
  843. {
  844. AZ_TracePrintf(AssetProcessor::ConsoleChannel, "Adding branch token (%s) in (%s)\n", appBranchToken.c_str(), assetProcessorUserSetregPath.c_str());
  845. }
  846. // Update Settings Registry with new token
  847. settingsRegistry->Set(branchTokenKey, appBranchToken);
  848. return AssetUtilsInternal::DumpAssetProcessorUserSettingsToFile(*settingsRegistry, assetProcessorUserSetregPath);
  849. }
  850. QString ComputeJobDescription(const AssetProcessor::AssetRecognizer* recognizer)
  851. {
  852. QString jobDescription{ recognizer->m_name.c_str() };
  853. return jobDescription.toLower();
  854. }
  855. AZStd::string ComputeJobLogFolder()
  856. {
  857. return AZStd::string::format("@log@/JobLogs");
  858. }
  859. AZStd::string ComputeJobLogFileName(const AzToolsFramework::AssetSystem::JobInfo& jobInfo)
  860. {
  861. return AZStd::string::format("%s-%u-%llu.log", jobInfo.m_sourceFile.c_str(), jobInfo.GetHash(), jobInfo.m_jobRunKey);
  862. }
  863. AZStd::string ComputeJobLogFileName(const AssetBuilderSDK::CreateJobsRequest& createJobsRequest)
  864. {
  865. return AZStd::string::format("%s-%s_createJobs.log", createJobsRequest.m_sourceFile.c_str(), createJobsRequest.m_builderid.ToString<AZStd::string>(false).c_str());
  866. }
  867. ReadJobLogResult ReadJobLog(AzToolsFramework::AssetSystem::JobInfo& jobInfo, AzToolsFramework::AssetSystem::AssetJobLogResponse& response)
  868. {
  869. AZStd::string logFile = AssetUtilities::ComputeJobLogFolder() + "/" + AssetUtilities::ComputeJobLogFileName(jobInfo);
  870. return ReadJobLog(logFile.c_str(), response);
  871. }
  872. ReadJobLogResult ReadJobLog(const char* absolutePath, AzToolsFramework::AssetSystem::AssetJobLogResponse& response)
  873. {
  874. response.m_isSuccess = false;
  875. AZ::IO::HandleType handle = AZ::IO::InvalidHandle;
  876. AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
  877. if (!fileIO)
  878. {
  879. AZ_TracePrintf("AssetProcessorManager", "Error: AssetProcessorManager: FileIO is unavailable\n", absolutePath);
  880. response.m_jobLog = "FileIO is unavailable";
  881. response.m_isSuccess = false;
  882. return ReadJobLogResult::MissingFileIO;
  883. }
  884. if (!fileIO->Open(absolutePath, AZ::IO::OpenMode::ModeRead | AZ::IO::OpenMode::ModeBinary, handle))
  885. {
  886. AZ_TracePrintf("AssetProcessorManager", "Error: AssetProcessorManager: Failed to find the log file %s for a request.\n", absolutePath);
  887. response.m_jobLog.append(AZStd::string::format("Error: No log file found for the given log (%s)", absolutePath).c_str());
  888. response.m_isSuccess = false;
  889. return ReadJobLogResult::MissingLogFile;
  890. }
  891. AZ::u64 actualSize = 0;
  892. fileIO->Size(handle, actualSize);
  893. if (actualSize == 0)
  894. {
  895. AZ_TracePrintf("AssetProcessorManager", "Error: AssetProcessorManager: Log File %s is empty.\n", absolutePath);
  896. response.m_jobLog.append(AZStd::string::format("Error: Log is empty (%s)", absolutePath).c_str());
  897. response.m_isSuccess = false;
  898. fileIO->Close(handle);
  899. return ReadJobLogResult::EmptyLogFile;
  900. }
  901. size_t currentResponseSize = response.m_jobLog.size();
  902. response.m_jobLog.resize(currentResponseSize + actualSize);
  903. fileIO->Read(handle, response.m_jobLog.data() + currentResponseSize, actualSize);
  904. fileIO->Close(handle);
  905. response.m_isSuccess = true;
  906. return ReadJobLogResult::Success;
  907. }
  908. unsigned int GenerateFingerprint(const AssetProcessor::JobDetails& jobDetail)
  909. {
  910. // it is assumed that m_fingerprintFilesList contains the original file and all dependencies, and is in a stable order without duplicates
  911. // CRC32 is not an effective hash for this purpose, so we will build a string and then use SHA1 on it.
  912. // to avoid resizing and copying repeatedly we will keep track of the largest reserved capacity ever needed for this function, and reserve that much data
  913. static size_t s_largestFingerprintCapacitySoFar = 1;
  914. AZStd::string fingerprintString;
  915. fingerprintString.reserve(s_largestFingerprintCapacitySoFar);
  916. // in general, we'll build a string which is:
  917. // (version):[Array of individual file fingerprints][Array of individual job fingerprints]
  918. // with each element of the arrays seperated by colons.
  919. fingerprintString.append(jobDetail.m_extraInformationForFingerprinting);
  920. for (const auto& fingerprintFile : jobDetail.m_fingerprintFiles)
  921. {
  922. fingerprintString.append(":");
  923. fingerprintString.append(GetFileFingerprint(fingerprintFile.first, fingerprintFile.second));
  924. }
  925. // now the other jobs, which this job depends on:
  926. for (const AssetProcessor::JobDependencyInternal& jobDependencyInternal : jobDetail.m_jobDependencyList)
  927. {
  928. if (jobDependencyInternal.m_jobDependency.m_type == AssetBuilderSDK::JobDependencyType::OrderOnce ||
  929. jobDependencyInternal.m_jobDependency.m_type == AssetBuilderSDK::JobDependencyType::OrderOnly)
  930. {
  931. // We do not want to include the fingerprint of dependent jobs if the job dependency type is OrderOnce or OrderOnly.
  932. continue;
  933. }
  934. AssetProcessor::JobDesc jobDesc(AssetProcessor::SourceAssetReference(jobDependencyInternal.m_jobDependency.m_sourceFile.m_sourceFileDependencyPath.c_str()),
  935. jobDependencyInternal.m_jobDependency.m_jobKey, jobDependencyInternal.m_jobDependency.m_platformIdentifier);
  936. for (auto builderIter = jobDependencyInternal.m_builderUuidList.begin(); builderIter != jobDependencyInternal.m_builderUuidList.end(); ++builderIter)
  937. {
  938. AZ::u32 dependentJobFingerprint;
  939. AssetProcessor::ProcessingJobInfoBus::BroadcastResult(dependentJobFingerprint, &AssetProcessor::ProcessingJobInfoBusTraits::GetJobFingerprint, AssetProcessor::JobIndentifier(jobDesc, *builderIter));
  940. if (dependentJobFingerprint != 0)
  941. {
  942. fingerprintString.append(AZStd::string::format(":%u", dependentJobFingerprint));
  943. }
  944. }
  945. }
  946. s_largestFingerprintCapacitySoFar = AZStd::GetMax(fingerprintString.capacity(), s_largestFingerprintCapacitySoFar);
  947. if (fingerprintString.empty())
  948. {
  949. AZ_Assert(false, "GenerateFingerprint was called but no input files were requested for fingerprinting.");
  950. return 0;
  951. }
  952. AZ::Sha1 sha;
  953. sha.ProcessBytes(AZStd::as_bytes(AZStd::span(fingerprintString)));
  954. AZ::u32 digest[5];
  955. sha.GetDigest(digest);
  956. return digest[0]; // we only currently use 32-bit hashes. This could be extended if collisions still occur.
  957. }
  958. std::uint64_t AdjustTimestamp(QDateTime timestamp, int overridePrecision)
  959. {
  960. if (timestamp.isDaylightTime())
  961. {
  962. int offsetTimeinSecs = timestamp.timeZone().daylightTimeOffset(timestamp);
  963. timestamp = timestamp.addSecs(-1 * offsetTimeinSecs);
  964. }
  965. timestamp = timestamp.toUTC();
  966. auto timeMilliseconds = timestamp.toMSecsSinceEpoch();
  967. int checkPrecision = (overridePrecision ? overridePrecision : s_truncateFingerprintTimestampPrecision);
  968. // Reduce the precision from milliseconds to the specified precision (default is 1, so no change)
  969. timeMilliseconds /= checkPrecision;
  970. timeMilliseconds *= checkPrecision;
  971. return timeMilliseconds;
  972. }
  973. AZ::u64 GetFileHash(const char* filePath, bool force, AZ::IO::SizeType* bytesReadOut, int hashMsDelay)
  974. {
  975. #ifndef AZ_TESTS_ENABLED
  976. // Only used for unit tests, speed is critical for GetFileHash.
  977. hashMsDelay = 0;
  978. #endif
  979. bool useFileHashing = ShouldUseFileHashing();
  980. if(!useFileHashing || !filePath)
  981. {
  982. return 0;
  983. }
  984. AZ::u64 hash = 0;
  985. if(!force)
  986. {
  987. auto* fileStateInterface = AZ::Interface<AssetProcessor::IFileStateRequests>::Get();
  988. if (fileStateInterface && fileStateInterface->GetHash(filePath, &hash))
  989. {
  990. return hash;
  991. }
  992. }
  993. // keep track of how much time we spend actually hashing files.
  994. AZStd::string statName = AZStd::string::format("HashFile,%s", filePath);
  995. AssetProcessor::StatsCapture::BeginCaptureStat(statName.c_str());
  996. hash = AssetBuilderSDK::GetFileHash(filePath, bytesReadOut, hashMsDelay);
  997. AssetProcessor::StatsCapture::EndCaptureStat(statName.c_str());
  998. return hash;
  999. }
  1000. AZ::u64 AdjustTimestamp(QDateTime timestamp)
  1001. {
  1002. timestamp = timestamp.toUTC();
  1003. auto timeMilliseconds = timestamp.toMSecsSinceEpoch();
  1004. // Reduce the precision from milliseconds to the specified precision (default is 1, so no change)
  1005. timeMilliseconds /= s_truncateFingerprintTimestampPrecision;
  1006. timeMilliseconds *= s_truncateFingerprintTimestampPrecision;
  1007. return timeMilliseconds;
  1008. }
  1009. AZStd::string GetFileFingerprint(const AZStd::string& absolutePath, const AZStd::string& nameToUse)
  1010. {
  1011. bool fileFound = false;
  1012. AssetProcessor::FileStateInfo fileStateInfo;
  1013. auto* fileStateInterface = AZ::Interface<AssetProcessor::IFileStateRequests>::Get();
  1014. if (fileStateInterface)
  1015. {
  1016. fileFound = fileStateInterface->GetFileInfo(QString::fromUtf8(absolutePath.c_str()), &fileStateInfo);
  1017. }
  1018. QDateTime lastModifiedTime = fileStateInfo.m_modTime;
  1019. if (!fileFound || !lastModifiedTime.isValid())
  1020. {
  1021. // we still use the name here so that when missing files change, it still counts as a change.
  1022. // we also don't use '0' as the placeholder, so that there is a difference between files that do not exist
  1023. // and files which have 0 bytes size.
  1024. return AZStd::string::format("-:-:%s", nameToUse.c_str());
  1025. }
  1026. else
  1027. {
  1028. bool useHash = ShouldUseFileHashing();
  1029. AZ::u64 fileIdentifier;
  1030. if(useHash)
  1031. {
  1032. fileIdentifier = GetFileHash(absolutePath.c_str());
  1033. }
  1034. else
  1035. {
  1036. fileIdentifier = AdjustTimestamp(lastModifiedTime);
  1037. }
  1038. // its possible that the dependency has moved to a different file with the same modtime/hash
  1039. // so we add the size of it too.
  1040. // its also possible that it moved to a different file with the same modtime/hash AND size,
  1041. // but with a different name. So we add that too.
  1042. return AZStd::string::format("%llX:%llu:%s", fileIdentifier, fileStateInfo.m_fileSize, nameToUse.c_str());
  1043. }
  1044. }
  1045. AZStd::string ComputeJobLogFileName(const AssetProcessor::JobEntry& jobEntry)
  1046. {
  1047. return AZStd::string::format(
  1048. "%s-%u-%llu.log", jobEntry.m_sourceAssetReference.RelativePath().c_str(), jobEntry.GetHash(), jobEntry.m_jobRunKey);
  1049. }
  1050. bool CreateTempRootFolder(QString startFolder, QDir& tempRoot)
  1051. {
  1052. tempRoot.setPath(startFolder);
  1053. if (!tempRoot.exists("AssetProcessorTemp"))
  1054. {
  1055. if (!tempRoot.mkpath("AssetProcessorTemp"))
  1056. {
  1057. AZ_WarningOnce("Asset Utils", false, "Could not create a temp folder at %s", startFolder.toUtf8().constData());
  1058. return false;
  1059. }
  1060. }
  1061. if (!tempRoot.cd("AssetProcessorTemp"))
  1062. {
  1063. AZ_WarningOnce("Asset Utils", false, "Could not access temp folder at %s/AssetProcessorTemp", startFolder.toUtf8().constData());
  1064. return false;
  1065. }
  1066. return true;
  1067. }
  1068. bool CreateTempWorkspace(QString startFolder, QString& result)
  1069. {
  1070. if (!AssetUtilsInternal::g_hasInitializedRandomNumberGenerator)
  1071. {
  1072. AssetUtilsInternal::g_hasInitializedRandomNumberGenerator = true;
  1073. // seed the random number generator a different seed as the main thread. random numbers are thread-specific.
  1074. // note that 0 is an invalid random seed.
  1075. AzQtComponents::GetRandomGenerator()->seed(QTime::currentTime().msecsSinceStartOfDay() + AssetUtilsInternal::g_randomNumberSequentialSeed.fetch_add(1) + 1);
  1076. }
  1077. QDir tempRoot;
  1078. if (!CreateTempRootFolder(startFolder, tempRoot))
  1079. {
  1080. result.clear();
  1081. return false;
  1082. }
  1083. // try multiple times in the very low chance that its going to be a collision:
  1084. for (int attempts = 0; attempts < 3; ++attempts)
  1085. {
  1086. QTemporaryDir tempDir(tempRoot.absoluteFilePath("JobTemp-XXXXXX"));
  1087. tempDir.setAutoRemove(false);
  1088. if ((tempDir.path().isEmpty()) || (!QDir(tempDir.path()).exists()))
  1089. {
  1090. QByteArray errorData = tempDir.errorString().toUtf8();
  1091. AZ_WarningOnce("Asset Utils", false, "Could not create new temp folder in %s - error from OS is '%s'", tempRoot.absolutePath().toUtf8().constData(), errorData.constData());
  1092. result.clear();
  1093. AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(100));
  1094. continue;
  1095. }
  1096. result = tempDir.path();
  1097. break;
  1098. }
  1099. return !result.isEmpty();
  1100. }
  1101. bool CreateTempWorkspace(QString& result)
  1102. {
  1103. // Use the project user folder as a temp workspace folder
  1104. // The benefits are
  1105. // * It's on the same drive as the Cache/ so we will be moving files instead of copying from drive to drive
  1106. // * It is discoverable by the user and thus deletable and we can also tell people to send us that folder without them having to go digging for it
  1107. QDir rootDir;
  1108. bool foundValidPath{};
  1109. if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
  1110. {
  1111. if (AZ::IO::Path userPath; settingsRegistry->Get(userPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath))
  1112. {
  1113. rootDir.setPath(QString::fromUtf8(userPath.c_str(), aznumeric_cast<int>(userPath.Native().size())));
  1114. foundValidPath = true;
  1115. }
  1116. }
  1117. if (!foundValidPath)
  1118. {
  1119. foundValidPath = ComputeAssetRoot(rootDir);
  1120. }
  1121. if (foundValidPath)
  1122. {
  1123. QString tempPath = rootDir.absolutePath();
  1124. return CreateTempWorkspace(tempPath, result);
  1125. }
  1126. result.clear();
  1127. return false;
  1128. }
  1129. QString GuessProductNameInDatabase(QString path, QString platform, AssetProcessor::AssetDatabaseConnection* databaseConnection)
  1130. {
  1131. QString productName;
  1132. QString inputName;
  1133. QString platformName;
  1134. QString jobDescription;
  1135. using namespace AzToolsFramework::AssetDatabase;
  1136. productName = AssetUtilities::NormalizeAndRemoveAlias(path);
  1137. // most of the time, the incoming request will be for an actual product name, so optimize this by assuming that is the case
  1138. // and do an optimized query for it
  1139. if (platform.isEmpty())
  1140. {
  1141. platform = AzToolsFramework::AssetSystem::GetHostAssetPlatform();
  1142. }
  1143. QString platformPrepend = QString("%1/").arg(platform);
  1144. QString productNameWithPlatform = productName;
  1145. if (!productName.startsWith(platformPrepend, Qt::CaseInsensitive))
  1146. {
  1147. productNameWithPlatform = productName = QString("%1/%2").arg(platform, productName);
  1148. }
  1149. ProductDatabaseEntryContainer products;
  1150. if (databaseConnection->GetProductsByProductName(productNameWithPlatform, products))
  1151. {
  1152. // if we find stuff, then return immediately, productName is already a productName.
  1153. return productName;
  1154. }
  1155. // if that fails, see at least if it starts with the given product name.
  1156. if (databaseConnection->GetProductsLikeProductName(productName, AssetDatabaseConnection::LikeType::StartsWith, products))
  1157. {
  1158. return productName;
  1159. }
  1160. if (!databaseConnection->GetProductsLikeProductName(productNameWithPlatform, AssetDatabaseConnection::LikeType::StartsWith, products))
  1161. {
  1162. return {};
  1163. }
  1164. return productName.toLower();
  1165. }
  1166. bool UpdateToCorrectCase(const QString& rootPath, QString& relativePathFromRoot, bool checkEntirePath /* = true*/)
  1167. {
  1168. // normalize the input string:
  1169. relativePathFromRoot = NormalizeFilePath(relativePathFromRoot);
  1170. // the File State Cache is itself case-insensitive on all operating systems an is warmed up as the application starts
  1171. // from a quick iteration of all files that exist, before any real logic is created. It is safe to check
  1172. // it for the existence of a file and early out to save time.
  1173. auto* fileStateInterface = AZ::Interface<AssetProcessor::IFileStateRequests>::Get();
  1174. if (fileStateInterface)
  1175. {
  1176. AssetProcessor::FileStateInfo fsInfo;
  1177. // use AZ::IO::Path here to combine the strings. Qt will otherwise potentially make assumptions about the
  1178. // working directory and give weird results that differ depending on the operating system.
  1179. AZ::IO::Path fullPath = AZ::IO::Path(rootPath.toUtf8().constData()) / relativePathFromRoot.toUtf8().constData();
  1180. if (!fileStateInterface->GetFileInfo(QString::fromUtf8(fullPath.c_str()), &fsInfo))
  1181. {
  1182. return false; // file does not exist according to the cache, which itself, is case insensitive.
  1183. }
  1184. // fsInfo contains the absolute path, but we need to update only the relative path part.
  1185. if (!rootPath.isEmpty()) // rootpath could be empty and relativePathFromRoot could be a full path
  1186. {
  1187. // to get here, length of rootpath will be at LEAST one.
  1188. relativePathFromRoot = fsInfo.m_absolutePath.mid(rootPath.length() + 1);
  1189. }
  1190. else
  1191. {
  1192. relativePathFromRoot = fsInfo.m_absolutePath;
  1193. }
  1194. return true;
  1195. }
  1196. // If we get here, there is no cache, and we fall back on the actual update to correct case logic.
  1197. // The reason we have to do this is that it could be possible that the file has a different
  1198. // case than expected, so it won't "exist" on disk with that exact name.
  1199. AZStd::string relPathFromRoot = relativePathFromRoot.toUtf8().constData();
  1200. if(AzToolsFramework::AssetUtils::UpdateFilePathToCorrectCase(rootPath.toUtf8().constData(), relPathFromRoot, checkEntirePath))
  1201. {
  1202. relativePathFromRoot = QString::fromUtf8(relPathFromRoot.c_str(), aznumeric_cast<int>(relPathFromRoot.size()));
  1203. return true;
  1204. }
  1205. return false;
  1206. }
  1207. bool IsInCacheFolder(AZ::IO::PathView path, AZ::IO::Path cachePath)
  1208. {
  1209. if(cachePath.empty())
  1210. {
  1211. QDir cacheDir;
  1212. [[maybe_unused]] bool result = ComputeProjectCacheRoot(cacheDir);
  1213. AZ_Error("AssetUtils", result, "Failed to get cache root for IsInCacheFolder");
  1214. cachePath = cacheDir.absolutePath().toUtf8().constData();
  1215. }
  1216. return path.IsRelativeTo(cachePath) && !IsInIntermediateAssetsFolder(path, cachePath);
  1217. }
  1218. bool IsInIntermediateAssetsFolder(AZ::IO::PathView path, AZ::IO::PathView cachePath)
  1219. {
  1220. AZ::IO::FixedMaxPath fixedCachedPath = cachePath;
  1221. if (fixedCachedPath.empty())
  1222. {
  1223. QDir cacheDir;
  1224. [[maybe_unused]] bool result = ComputeProjectCacheRoot(cacheDir);
  1225. AZ_Error("AssetUtils", result, "Failed to get cache root for IsInCacheFolder");
  1226. fixedCachedPath = cacheDir.absolutePath().toUtf8().constData();
  1227. }
  1228. AZ::IO::FixedMaxPath intermediateAssetsPath = GetIntermediateAssetsFolder(cachePath);
  1229. return path.IsRelativeTo(intermediateAssetsPath);
  1230. }
  1231. AZ::IO::FixedMaxPath GetIntermediateAssetsFolder(AZ::IO::PathView cachePath)
  1232. {
  1233. AZ::IO::FixedMaxPath path(cachePath);
  1234. return path / AssetProcessor::IntermediateAssetsFolderName;
  1235. }
  1236. AZStd::string GetIntermediateAssetDatabaseName(AZ::IO::PathView relativePath)
  1237. {
  1238. // For intermediate assets, the platform must always be common, we don't support anything else for intermediate assets
  1239. AZ::IO::Path platformPrefix = AssetBuilderSDK::CommonPlatformName;
  1240. return (platformPrefix / relativePath).LexicallyNormal().StringAsPosix();
  1241. }
  1242. AZStd::optional<AzToolsFramework::AssetDatabase::SourceDatabaseEntry> GetTopLevelSourceForIntermediateAsset(
  1243. const AssetProcessor::SourceAssetReference& sourceAsset, AZStd::shared_ptr<AssetProcessor::AssetDatabaseConnection> db)
  1244. {
  1245. AzToolsFramework::AssetDatabase::SourceDatabaseEntryContainer sources;
  1246. db->GetSourcesByProductName(GetIntermediateAssetDatabaseName(sourceAsset.RelativePath()).c_str(), sources);
  1247. if (sources.empty())
  1248. {
  1249. return {};
  1250. }
  1251. if (sources.size() > 1)
  1252. {
  1253. AZ_Error(AssetProcessor::ConsoleChannel, false, "GetTopLevelSourceForProduct found multiple sources for product %s", sourceAsset.AbsolutePath().c_str());
  1254. return {};
  1255. }
  1256. AzToolsFramework::AssetDatabase::SourceDatabaseEntry source;
  1257. do
  1258. {
  1259. source = sources[0];
  1260. sources = {}; // Clear the array, otherwise it keeps accumulating the results
  1261. } while (db->GetSourcesByProductName(GetIntermediateAssetDatabaseName(source.m_sourceName.c_str()).c_str(), sources));
  1262. return source;
  1263. }
  1264. AZStd::optional<AZ::IO::Path> GetTopLevelSourcePathForIntermediateAsset(
  1265. const AssetProcessor::SourceAssetReference& sourceAsset, AZStd::shared_ptr<AssetProcessor::AssetDatabaseConnection> db)
  1266. {
  1267. auto topLevelSourceDbEntry = GetTopLevelSourceForIntermediateAsset(sourceAsset, db);
  1268. if (!topLevelSourceDbEntry)
  1269. {
  1270. return {};
  1271. }
  1272. AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanfolderForTopLevelSource;
  1273. if(!db->GetScanFolderByScanFolderID(topLevelSourceDbEntry->m_scanFolderPK, scanfolderForTopLevelSource))
  1274. {
  1275. return {};
  1276. }
  1277. AZ::IO::Path fullPath = scanfolderForTopLevelSource.m_scanFolder;
  1278. fullPath /= topLevelSourceDbEntry->m_sourceName;
  1279. return fullPath;
  1280. }
  1281. AZStd::vector<AssetProcessor::SourceAssetReference> GetAllIntermediateSources(
  1282. const AssetProcessor::SourceAssetReference& sourceAsset, AZStd::shared_ptr<AssetProcessor::AssetDatabaseConnection> db)
  1283. {
  1284. AZStd::vector<AssetProcessor::SourceAssetReference> sources;
  1285. auto topLevelSource = GetTopLevelSourceForIntermediateAsset(sourceAsset, db);
  1286. if (!topLevelSource)
  1287. {
  1288. AzToolsFramework::AssetDatabase::SourceDatabaseEntry source;
  1289. if(!db->GetSourceBySourceNameScanFolderId(sourceAsset.RelativePath().c_str(), sourceAsset.ScanFolderId(), source))
  1290. {
  1291. return {};
  1292. }
  1293. topLevelSource = source;
  1294. }
  1295. AzToolsFramework::AssetDatabase::ScanFolderDatabaseEntry scanFolder;
  1296. db->GetScanFolderByScanFolderID(topLevelSource->m_scanFolderPK, scanFolder);
  1297. sources.emplace_back(scanFolder.m_scanFolder.c_str(), topLevelSource->m_sourceName.c_str());
  1298. AzToolsFramework::AssetDatabase::ProductDatabaseEntryContainer products;
  1299. db->GetProductsBySourceID(topLevelSource->m_sourceID, products);
  1300. auto size = products.size();
  1301. for (int i = 0; i < size; ++i)
  1302. {
  1303. const auto& product = products[i];
  1304. if ((static_cast<AssetBuilderSDK::ProductOutputFlags>(product.m_flags.to_ullong()) & AssetBuilderSDK::ProductOutputFlags::IntermediateAsset) == AssetBuilderSDK::ProductOutputFlags::IntermediateAsset)
  1305. {
  1306. auto productPath = ProductPath::FromDatabasePath(product.m_productName);
  1307. sources.emplace_back(productPath.GetIntermediatePath().c_str());
  1308. // Note: This call is intentionally re-using the products array. The new results will be appended to the end (via push_back).
  1309. // The array will not be cleared. We're essentially using products as a queue
  1310. db->GetProductsBySourceNameScanFolderID(sources.back().RelativePath().c_str(), sources.back().ScanFolderId(), products);
  1311. size = products.size(); // Update the loop size since the array grew
  1312. }
  1313. }
  1314. return sources;
  1315. }
  1316. BuilderFilePatternMatcher::BuilderFilePatternMatcher(const AssetBuilderSDK::AssetBuilderPattern& pattern, const AZ::Uuid& builderDescID)
  1317. : AssetBuilderSDK::FilePatternMatcher(pattern)
  1318. , m_builderDescID(builderDescID)
  1319. {
  1320. }
  1321. BuilderFilePatternMatcher::BuilderFilePatternMatcher(const BuilderFilePatternMatcher& copy)
  1322. : AssetBuilderSDK::FilePatternMatcher(copy)
  1323. , m_builderDescID(copy.m_builderDescID)
  1324. {
  1325. }
  1326. const AZ::Uuid& BuilderFilePatternMatcher::GetBuilderDescID() const
  1327. {
  1328. return this->m_builderDescID;
  1329. };
  1330. QuitListener::QuitListener()
  1331. : m_requestedQuit(false)
  1332. {
  1333. }
  1334. QuitListener::~QuitListener()
  1335. {
  1336. BusDisconnect();
  1337. }
  1338. void QuitListener::ApplicationShutdownRequested()
  1339. {
  1340. m_requestedQuit = true;
  1341. }
  1342. bool QuitListener::WasQuitRequested() const
  1343. {
  1344. return m_requestedQuit;
  1345. }
  1346. JobLogTraceListener::JobLogTraceListener(const AZStd::string& logFileName, AZ::s64 jobKey, bool overwriteLogFile /* = false */)
  1347. {
  1348. m_logFileName = AssetUtilities::ComputeJobLogFolder() + "/" + logFileName;
  1349. m_runKey = jobKey;
  1350. m_forceOverwriteLog = overwriteLogFile;
  1351. AZ::Debug::TraceMessageBus::Handler::BusConnect();
  1352. }
  1353. JobLogTraceListener::JobLogTraceListener(const AzToolsFramework::AssetSystem::JobInfo& jobInfo, bool overwriteLogFile /* = false */)
  1354. {
  1355. m_logFileName = AssetUtilities::ComputeJobLogFolder() + "/" + AssetUtilities::ComputeJobLogFileName(jobInfo);
  1356. m_runKey = jobInfo.m_jobRunKey;
  1357. m_forceOverwriteLog = overwriteLogFile;
  1358. AZ::Debug::TraceMessageBus::Handler::BusConnect();
  1359. }
  1360. JobLogTraceListener::JobLogTraceListener(const AssetProcessor::JobEntry& jobEntry, bool overwriteLogFile /* = false */)
  1361. {
  1362. m_logFileName = AssetUtilities::ComputeJobLogFolder() + "/" + AssetUtilities::ComputeJobLogFileName(jobEntry);
  1363. m_runKey = jobEntry.m_jobRunKey;
  1364. m_forceOverwriteLog = overwriteLogFile;
  1365. AZ::Debug::TraceMessageBus::Handler::BusConnect();
  1366. }
  1367. JobLogTraceListener::~JobLogTraceListener()
  1368. {
  1369. BusDisconnect();
  1370. }
  1371. bool JobLogTraceListener::OnAssert(const char* message)
  1372. {
  1373. if (AssetProcessor::GetThreadLocalJobId() == m_runKey)
  1374. {
  1375. AppendLog(AzFramework::LogFile::SEV_ASSERT, "ASSERT", message);
  1376. return true;
  1377. }
  1378. return false;
  1379. }
  1380. bool JobLogTraceListener::OnException(const char* message)
  1381. {
  1382. if (AssetProcessor::GetThreadLocalJobId() == m_runKey)
  1383. {
  1384. m_inException = true;
  1385. AppendLog(AzFramework::LogFile::SEV_EXCEPTION, "EXCEPTION", message);
  1386. // we return false here so that the main app can also trace it, exceptions are bad enough
  1387. // that we want them to show up in all the logs.
  1388. }
  1389. return false;
  1390. }
  1391. // we want no trace of errors to show up from jobs, inside the console app
  1392. // only in explicit usages, so we return true for pre-error here too
  1393. bool JobLogTraceListener::OnPreError(const char* window, const char* /*file*/, int /*line*/, const char* /*func*/, const char* message)
  1394. {
  1395. if (AssetProcessor::GetThreadLocalJobId() == m_runKey)
  1396. {
  1397. AppendLog(m_inException ? AzFramework::LogFile::SEV_EXCEPTION : AzFramework::LogFile::SEV_ERROR, window, message);
  1398. return true;
  1399. }
  1400. return false;
  1401. }
  1402. bool JobLogTraceListener::OnWarning(const char* window, const char* message)
  1403. {
  1404. if (AssetProcessor::GetThreadLocalJobId() == m_runKey)
  1405. {
  1406. AppendLog(m_inException ? AzFramework::LogFile::SEV_EXCEPTION : AzFramework::LogFile::SEV_WARNING, window, message);
  1407. return true;
  1408. }
  1409. return false;
  1410. }
  1411. bool JobLogTraceListener::OnPrintf(const char* window, const char* message)
  1412. {
  1413. if (AssetProcessor::GetThreadLocalJobId() == m_runKey)
  1414. {
  1415. if(azstrnicmp(message, "S: ", 3) == 0)
  1416. {
  1417. std::string dummy;
  1418. std::istringstream stream(message);
  1419. AZ::s64 errorCount, warningCount;
  1420. stream >> dummy >> errorCount >> dummy >> warningCount;
  1421. m_errorCount += errorCount;
  1422. m_warningCount += warningCount;
  1423. }
  1424. if (azstrnicmp(window, "debug", 5) == 0)
  1425. {
  1426. AppendLog(AzFramework::LogFile::SEV_DEBUG, window, message);
  1427. }
  1428. else
  1429. {
  1430. AppendLog(m_inException ? AzFramework::LogFile::SEV_EXCEPTION : AzFramework::LogFile::SEV_NORMAL, window, message);
  1431. }
  1432. return true;
  1433. }
  1434. return false;
  1435. }
  1436. void JobLogTraceListener::AppendLog(AzFramework::LogFile::SeverityLevel severity, const char* window, const char* message)
  1437. {
  1438. if (m_isLogging)
  1439. {
  1440. return;
  1441. }
  1442. m_isLogging = true;
  1443. if (!m_logFile)
  1444. {
  1445. m_logFile.reset(new AzFramework::LogFile(m_logFileName.c_str(), m_forceOverwriteLog));
  1446. }
  1447. m_logFile->AppendLog(severity, window, message);
  1448. m_isLogging = false;
  1449. }
  1450. void JobLogTraceListener::AppendLog(AzToolsFramework::Logging::LogLine& logLine)
  1451. {
  1452. using namespace AzToolsFramework;
  1453. using namespace AzFramework;
  1454. if (m_isLogging)
  1455. {
  1456. return;
  1457. }
  1458. m_isLogging = true;
  1459. if (!m_logFile)
  1460. {
  1461. m_logFile.reset(new LogFile(m_logFileName.c_str(), m_forceOverwriteLog));
  1462. }
  1463. LogFile::SeverityLevel severity;
  1464. switch (logLine.GetLogType())
  1465. {
  1466. case Logging::LogLine::TYPE_MESSAGE:
  1467. severity = LogFile::SEV_NORMAL;
  1468. break;
  1469. case Logging::LogLine::TYPE_WARNING:
  1470. severity = LogFile::SEV_WARNING;
  1471. break;
  1472. case Logging::LogLine::TYPE_ERROR:
  1473. severity = LogFile::SEV_ERROR;
  1474. break;
  1475. default:
  1476. severity = LogFile::SEV_DEBUG;
  1477. }
  1478. m_logFile->AppendLog(severity, logLine.GetLogMessage().c_str(), (int)logLine.GetLogMessage().length(),
  1479. logLine.GetLogWindow().c_str(), (int)logLine.GetLogWindow().length(), logLine.GetLogThreadId(), logLine.GetLogTime());
  1480. m_isLogging = false;
  1481. }
  1482. AZ::s64 JobLogTraceListener::GetErrorCount() const
  1483. {
  1484. return m_errorCount;
  1485. }
  1486. AZ::s64 JobLogTraceListener::GetWarningCount() const
  1487. {
  1488. return m_warningCount;
  1489. }
  1490. void JobLogTraceListener::AddError()
  1491. {
  1492. ++m_errorCount;
  1493. }
  1494. void JobLogTraceListener::AddWarning()
  1495. {
  1496. ++m_warningCount;
  1497. }
  1498. AZStd::string GetRelativeProductPathForIntermediateSourcePath(AZStd::string_view relativeSourcePath)
  1499. {
  1500. AZStd::string productPath((AZ::IO::FixedMaxPath(AssetBuilderSDK::CommonPlatformName) / relativeSourcePath).StringAsPosix());
  1501. // Product paths are always lowercase
  1502. AZStd::to_lower(productPath.begin(), productPath.end());
  1503. return productPath;
  1504. }
  1505. ProductPath::ProductPath(AZStd::string scanfolderRelativeProductPath, AZStd::string platformIdentifier)
  1506. {
  1507. AZ_Assert(AZ::IO::PathView(scanfolderRelativeProductPath).IsRelative(), "scanfolderRelativeProductPath is not relative: %s", scanfolderRelativeProductPath.c_str());
  1508. QDir cacheDir;
  1509. [[maybe_unused]] bool result = ComputeProjectCacheRoot(cacheDir);
  1510. AZ_Error("AssetUtils", result, "Failed to get cache root");
  1511. AZ::IO::FixedMaxPath cachePath = cacheDir.absolutePath().toUtf8().constData();
  1512. // Lowercase the inputs. The cache path is always lowercased, which means the database path is lowercased,
  1513. // and for consistency, the intermediate path is also lowercased.
  1514. // All the other parts of the path must remain properly cased.
  1515. AZStd::to_lower(scanfolderRelativeProductPath.begin(), scanfolderRelativeProductPath.end());
  1516. AZStd::to_lower(platformIdentifier.begin(), platformIdentifier.end());
  1517. m_relativePath = NormalizeFilePath(scanfolderRelativeProductPath.c_str()).toUtf8().constData();
  1518. m_cachePath = cachePath / platformIdentifier / scanfolderRelativeProductPath;
  1519. m_intermediatePath = AssetUtilities::GetIntermediateAssetsFolder(cachePath) / scanfolderRelativeProductPath;
  1520. m_databasePath = AZ::IO::FixedMaxPath(platformIdentifier) / scanfolderRelativeProductPath;
  1521. }
  1522. ProductPath ProductPath::FromDatabasePath(AZStd::string_view databasePath, AZStd::string_view* platformOut)
  1523. {
  1524. AZStd::string_view platform;
  1525. AZStd::string_view relativeProductPath = AssetUtilities::StripAssetPlatformNoCopy(databasePath, &platform);
  1526. if(platformOut)
  1527. {
  1528. *platformOut = platform;
  1529. }
  1530. return ProductPath{ relativeProductPath, platform };
  1531. }
  1532. ProductPath ProductPath::FromAbsoluteProductPath(AZ::IO::PathView absolutePath, AZStd::string& outPlatform)
  1533. {
  1534. QDir cacheDir;
  1535. [[maybe_unused]] bool result = ComputeProjectCacheRoot(cacheDir);
  1536. AZ_Error("AssetUtils", result, "Failed to get cache root for IsInCacheFolder");
  1537. AZ::IO::FixedMaxPath parentFolder = cacheDir.absolutePath().toUtf8().constData();
  1538. bool intermediateAsset = IsInIntermediateAssetsFolder(absolutePath, parentFolder);
  1539. if (intermediateAsset)
  1540. {
  1541. parentFolder = AssetUtilities::GetIntermediateAssetsFolder(parentFolder);
  1542. outPlatform = AssetBuilderSDK::CommonPlatformName;
  1543. }
  1544. auto relativePath = absolutePath.LexicallyRelative(parentFolder);
  1545. if (!intermediateAsset)
  1546. {
  1547. AZStd::string_view platform;
  1548. auto fixedString = relativePath.FixedMaxPathStringAsPosix();
  1549. relativePath = StripAssetPlatformNoCopy(fixedString, &platform);
  1550. outPlatform = platform;
  1551. }
  1552. return ProductPath{ relativePath.StringAsPosix(), outPlatform };
  1553. }
  1554. } // namespace AssetUtilities