assetUtils.cpp 71 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849
  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